Friday 19 August 2016

First person melee combat

I believe it's time for some more combat mechanics. In this section, we'll see how we can create a simple but effective melee combat system for a first person controller. The idea is to create something that resembles the combat system you saw in Skyrim or other RPGs of similar fashion.

I am going to use the first person controller found in the standard asset, plus additional assets found here and a nice skybox downloadable here. As for our foe, I found this asset which suits our needs perfectly.

Setting up the scene

Start by creating the scene. I dropped the first person character controller on a plane, placed a skybox to make the level look pretty and finally position the axe as a child of the first person camera in the controller. This is what the game window looks like:



And below is a srceenshot of the hierarchy as it looks after placing these objects in the scene:


You can see that the axe model is placed as a child of the FirstPersonCharacter, which is where the camera is.

Place a capsule collider on it and set it to trigger. You don't need to be super accurate, something like this should do it:


Create now a new layer, which we can call Axe, and set the axe model to this layer.

Since we are here, let's drag the goblin model in the scene too. That particular model looks a bit too big, so I scaled it down to 0.45 for each axis in its transform. Once in the scene, you can remove the script attached to it and place a capsule collider on it and make it fit nicely around the model. Also, the rigidbody should be checked as isKinematic.


This is pretty much all we need in the scene now. The goblin model already comes with some animation, which we are going to script later.

We should now have the player, with the axe, and our goblin, and they both should collide with each other. However, this is our first problem:



When we get close to the goblin model, the axe passes through it. Unless this is something you want to have, Also, if we had to use the collider on our weapon and not set it to trigger, it could be a little problematic for the player to move around, as it might get stuck in other models when rotating and so on.

There are a couple of ways we can fix this: we could write a shader for the axe in order to have it rendered on top of everything, or we can use 2 cameras. We are going with the latter.

Create a new camera and attach it to the FirstPersonCharacter and set the transform to 0,0,0. Let's call it OverlayCamera. The camera should now be in the exact position as the first person camera that comes with the FPScontroller.Now, in the inspector for this camera, set the Clear Flags to Depth Only, the Culling Mask to Axe, and the Depth to 1.



Now, the other camera, which is a component of FirstPersonCharacter. Leave the Clear flags to Skybox, change the Culling mask by removing the Axe layer, and leave the Depth to 0.

At this point, the OverlayCamera is rendering only the axe (or anything on the Axe layer) and it always draws on top of the FirstPersonCharacter camera, because of the Depth parameter of value 1.


No matter how close we get to the goblin, the axe will always be drawn on top.

Axe animation

We should now create an attack animation for the axe. Honestly, I'm really not the best person to show you how to properly create an animation in Unity, I'm still trying to master the art myself. However, I will show you a GIF picture of the final result so you can have an idea of what I am trying to achieve:


This is the attack animation. I also created a couple of idle animation, you will see them in the playable scene. Later on, we will need to add animation events.

The animation controller for the axe is shown below:


There is a sub state machine which I use to trigger some random idle animations, not something we should be concerned about for our purpose. We should in fact focus on the Axe_Attack state, which holds the animation just created. This state can be triggered by Any State via the trigger Attack and will return to the Axe_Idle state once the animation has reached the end. And don't forget to remove Loop from the Axe_Attack animation.

Here are the inspector screenshots of the 2 transition states side by side:


We can now start writing some scripts.

Scripting time


Create a C# script called Axe.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Axe : MonoBehaviour {

 Animator anim;

 CapsuleCollider cc;


 // Use this for initialization
 void Start () {

  anim = GetComponent<Animator> ();

  cc = GetComponent<CapsuleCollider> ();
  cc.enabled = false;
 
 }


 // Update is called once per frame
 void Update () {

  if (Input.GetMouseButtonDown (0))    
   anim.SetTrigger ("Attack");  
 
 }

 public void ActivateCollider(int active)
 {
  if (active == 0)
   cc.enabled = false;
  else
   cc.enabled = true;
 }
  



 void OnTriggerEnter(Collider c)
 {
  if (c.gameObject.GetComponent<IAxeHittable>()!=null)
{                               cc.enabled = false;
    c.gameObject.SendMessage ("OnGetHitByAxe", 5);

 }
}
}

Pretty simple, after getting all our references we check in the Update function for any mouse click to trigger the attack animation.

The ActivateCollider method is called by animation events: when the axe is about half way extended, we activate the collider in order to trigger the collision. As the axe is animating back to idle position, we deactivate it.


This is necessary, otherwise every time we get close to the goblin it will register a collision, even if we are not attacking.

Also, we deactivate the collider immediately after hitting.

To detect whether the weapon has hit something we use interfaces. If the object collided with implements the interface IAxeHittable, we send a message to it, calling the method OnGetHitByAxe.

This is the interface:

1
2
3
4
public interface IAxeHittable {

 void OnGetHitByAxe(float hitValue);
}

Now, we can create a Goblin script which will implement the interface and attach it to our goblin model.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Goblin : MonoBehaviour, IAxeHittable {

 // Use this for initialization
 void Start () {
 
 }
 
 // Update is called once per frame
 void Update () {
 
 }

 public void OnGetHitByAxe(float hitValue)
 {
  Debug.Log ("Goblin got hit by Axe!");
 }
}

As you can see, we MUST implement the method OnGetHitByAxe, which we'll use for handling damage to the enemy. At this point, all we do is to print to the console.

Conclusion

This is the basic structure on how to create some simple melee combat mechanics. As always, this can be improved to become a much more efficient and flexible system, which can be as complex or simple as your project would require.

Example scene here

21 comments:

  1. It may seem noone is benefiting from this - but they are most likely just not typing. Thank you for this, this really did help lay the foundations for me.

    ReplyDelete
  2. Im wondering, if we want that 1 player hit could damage 2 or more enemies, then we need to rework that system that collision of "Axe" will not be disabled immediately after damaging first target (if it will be disabled, there will be no collision for second target).

    So we need to make some bool flag (like "isHitted = false" in enemy class, so when enemy hitted make that flag "true". And after animation is over, we should find all enemies which was hitted (maybe record then into "List hittedByLastAttackEnemies", and send them message "RestoreFlag()" which will reset all enemies flag to "false" state.

    Or it can be implemented in some better way?

    ReplyDelete
  3. Hi Alex. I guess you could use a flag like you said, or you could create, for example, an array of GameObject to keep track of which enemy you have hit: maybe create an animation event that points to a method that creates the array at the start of the axe swinging animation, activating the collider. At this point, all objects that you hit can be places inside the array (in the OnTriggerEnter method). Here you can also check which enemies you have hit already and avoid hitting the same enemy twice (unless that is what you want of course). You can then create another animation event at the end of the axe swinging animation to disable the collider (you don't want to be hitting enemies if the axe is not swinging).

    It might not be as efficient (you iterate through the array of enemies every time you hit one) but at least it should be accurate.

    I'll let you know if I come up with some other way, this was the first method that came to my mind.

    ReplyDelete
  4. Hello! Great tutorial, but where do i put this? :)

    public interface IAxeHittable {

    void OnGetHitByAxe(float hitValue);
    }

    ReplyDelete
    Replies
    1. Simply create a new script with those 2 lines of code in it. That's all.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. I did follow the tutorial all the way through, but now i get this error: The type or namespace name "IAxeHittable" could not be found (are you missing using directive or an assembly reference?

      Delete
  5. Ok, that's strange. It seems like the script cannot be found somehow.

    Have you tried google? On stack overflow there are different solutions to this problem, maybe try to have a look if there's anything that can help.

    This one for instance: https://stackoverflow.com/questions/4764978/the-type-or-namespace-name-could-not-be-found.

    Let me know how it goes :)

    ReplyDelete
    Replies
    1. Can't seem to fix the issue, i'm kinda new to programming and such but it seems to be something wrong with the "IAxehittable" in the Goblin and Axe script.
      https://gyazo.com/344a82e38b4c1762c9e8dc5e28ac88c5
      https://gyazo.com/bf2b475c85c2cfcb6aed8a12284a5f1c

      Delete
    2. Ok I see, first thing that I would do is check the spelling for the actual interface (the script "IAxeHittable"). You probably already know that C# is case sensitive so, if you called the interface "IAxehittable" and you refer to it as "IAxeHittable" in other scripts (like the Goblin script), the compiler will complain. Double check spelling and case, if that doesn't work, post a screenshot of the IAxeHittable please so I can take a look.

      Delete
    3. I seem to have fixed that issue, but now i got another one.. Haha
      https://gyazo.com/fb33fbd3c5182f1abacb97358eb53723

      Delete
    4. Ok that's an easy one :)

      An interface is in fact a class so you should delete line 5.
      Remove the outer brackets and the "public class IAxeHittable: Monobehavior".

      All you need in the script is

      public interface IAxeHittable
      {
      void OnGetHitByAxe(float hitValue)
      }

      Delete
    5. Thanks alot! Now it's working except nothing is happening when i hit the goblin (3D capsule in my cape).

      Delete
    6. Well, nothing should happen in fact. All you should be able to see is a comment in the console that says "Goblin got hit by Axe!". That means that the system is working correctly: when the goblin is hit, the method OnGetHitByAxe(...) in the Goblin class is called.

      Here you could call other methods, used for example to decrease the goblin's health or play a hit animation or dead animation.

      This tutorial is meant to show how to get the basics working.

      Delete
    7. Yeah, i understood that, however the comment doesn't show up in the console.

      Delete
    8. Ok, that's strange. If you can put the project on GitHub or package it somehow and send it to me I'm happy to have a look at it.

      Delete
  6. This has been lots of help, thankyou very much :)

    ReplyDelete