Sunday, May 13, 2018

Hitboxes and Hurtboxes in Unity


Explanation

So what are hitboxes and hurtboxes anyway? Aren’t they the same thing?
Well... The answer can differ depending on who you ask, but here we will abide to the notion that hitboxes and hurtboxes are two different things and have different uses, as any fighting game worth mentioning does.
A Hitbox is an invisible box (or sphere) that determines where an attack hits.
A Hurtbox on the other side is also an invisible box (or sphere), but it determines where a player or object can be hit by a Hitbox.

In this image from SFIV, the red box is the hitbox and the green one the hurtbox


It’s worth mentioning that the size and position of both hitboxes and hurtboxes change depending on the frame of animation that is playing:


Gif from Killer Instinct. Look how the hitboxes only appear on hitting frames and move with the sword


In the Killer Instinct example we can also see a third type of box, the Pushbox (the yellow one, the Hurtbox is the empty green box). A Pushbox is a box that represents the physical occupied space by a character, and keeps characters from overlapping.
In most fighting games or brawlers there’s two other types of boxes that for simplicity we will not cover:
The grab or throw box, that determines where a character can be grabbed or thrown, and the block box that determines where an attacked player that’s pressing the back button will start blocking a certain attack instead of walking backwards.

All these boxes are really important from a design point of view. Hitboxes and Hurtboxes of an attack determine not only how many frames an attack hits but also the blind spots of that attack and how vulnerable it leaves the player.
For a great explanation on this focused on Street Fighter check this video

Does this only apply to fighting games?

No! Almost every game has Hitboxes and Hurtboxes, from Dark Souls to Rayman, they can be three dimensional or two dimensional.

So now that we have our terminology straight, let’s start working.


What we want

Let’s check every type of box we’ll cover and see what we want from them:

Pushbox: We need for two pushboxes to collide with each other and not overlap (that’s why it’s called a pushbox, it pushes the other character). Pushboxes should only interact with other pushboxes.
Hurtbox: It can register a hit, but it should not collide in the physical sense. Hurtboxes should only interact with Hitboxes.
Hitbox: It should be able to check if it’s overlapping a Hurtbox in arbitrary frames. It should only interact with Hurtboxes.


Using Unity default components

The first approach one could do is map every kind of box that we talked about to a Unity default component. The obvious choice is some type of Collider.
The Pushbox can be directly mapped to a Collider plus a Rigidbody. That behaves exactly as we want it, it collides with things and doesn’t overlap.
The only part we need to worry about (besides setting our Rigidbody as we want it) is the part about only colliding with other Pushboxes. If you’re familiar with Unity physics system, you already know that the solution is to use Layers and the Layer Collision Matrix. For clarity we can create a layer called Pushbox, assign it to our object and set the collision matrix so Pushbox only collides with Pushbox.







For Hurtboxes we can use Collider using isTrigger. This ensures that it won’t collide in a physical sense and will only register other colliders entering it’s area. For actually registering the hit we will need to add a script in the same object that implements OnTriggerEnter, probably check the tag of the incoming collider to check that the one that triggered the event is the one we want and then do whatever damage and health calculations our game needs. You are probably familiar with this approach.
We also need to create the layers Hurtbox and Hitbox, and use the Layer Collision Matrix again to make Hurtbox only collide with Hitbox and vice versa.

  • Note that we don’t need a Rigidbody, but only because I’m assuming that every trigger we will add is a child object of the Pushbox object that already has a Rigidbody. This is important because Colliders without a Rigidbody in itself or some of its parents will be set as Static by Unity and moving them will be really inefficient.
  • Also, we will probably need to distinguish between a Hitbox from the player or one from the enemies. The same goes for Hurtboxes. This way we can make the Hitbox from the player only hit the Hurtbox from the enemies, and the Hitboxes of the enemies only hit the Hurtboxes of the player. You don’t need this if you want to allow friendly fire, but you gotta be careful to avoid a player hitting its own Hurtbox.

Hitboxes are maybe the least clear in how we should implement them. What we can use is a Collider using isTrigger to avoid a physical collision, but there are really no colliders that “enter” a Hitbox. Actually is the other way around: A Hitbox “enters” (or checks if it’s overlapping) a Hurtbox. Nevertheless, we need a Collider or Unity will never call OnTriggerEnter in our Hurtbox.
For dealing damage to the Hurtbox we will need to add a script in the same object, so that our Hurtbox can use GetComponent<T> and get it to know how much damage needs to be dealt. You can also do it the other way around, OnTriggerEnter gets called for both Colliders. We also need a way to make our Hitbox active only when we want to and not in every frame or when the character is not attacking for example. For this we can’t just disable our script because as the documentation says: “Trigger events will be sent to disabled MonoBehaviours, to allow enabling Behaviours in response to collisions”.
What we can do is enable and disable the collider, or add a boolean property to our script that handles if it should hit or not.


Problems

  • Hierarchy: we need to have a script in every object with a Collider to be able to respond to OnTriggerEnter. If you like to have your scripts in the same place for organizational reasons, you will need to create a script just to delegate the call to your other object. 
  • Overhead: With this approach our Hitboxes have a lot of functionality we don’t need. 
  • Events: We rely on OnTriggerEnter for our functionality. Using Unity events may not be a problem, but there’s reasons to at least think if we should. You can also check this (in the section“Avoiding expensive calls to the Unity API”) to know more.
  • Visuals: If you want to use different Hitboxes for different attacks, not only will the aforementioned problems repeat a lot, but you will have a lot of visual clutter on your editor window.
  • Little flexibility: Using Colliders as Hitboxes means that if you want to change the shape of the collider, for example from a Box to a Sphere, you’ll have to remove the BoxCollider and add a SphereCollider manually (or implement an editor script to do this for you)


Rolling our own

As you probably realized while reading the previous section, Pushboxes and Hurtboxes map pretty much okay to Unity default components.
Hurtboxes still have the problems mentioned and we will solve some of them, but the entity that seems to need its own abstraction is the Hitbox.
If you’re making a combat heavy game with a lot of attacks and combos, you probably want to have all your attacks neatly organized in an object and use several combinations of Hitboxes for any of them.To do that you would need a script that strictly delegates OnTriggerEnter calls to your active attack or something along those lines.

You don’t want to create a specific Hitbox object for every attack, here we can reuse the same changing the size!


Hitboxes

Our new component will need to cover the following:
  1. Have Hitbox behaviour: It should be able to check if it’s overlapping a Hurtbox in arbitrary frames. It should only interact with Hurtboxes. 
  2. Have a visual representation in the Scene view. 
  3. Be customizable and flexible. 
  4. Ideally, not depend on Unity API events. 
  5. Be independent enough to allow an script in another object to use it. 
  6. Not be coupled to a specific attack, Hitboxes should be usable by several different attacks.


Behaviour

First off, how do we check if some area is overlapping a Collider? The answer is to use UnityEngine.Physics.
Physics has a lot of methods that can do what we want. We can specify the shape we want (Box, Sphere, Capsule) and also if we want to get the Colliders we hit (if any) as an array or pass an array to be filled with them. You shouldn’t worry right now about this, but the first one is allocating a new array, the other just fills the one you already had.

Let’s start by checking a rectangular area and see if we hit something. For this we can use OverlapBox.

We need to define the dimensions of the box we want to check. For this we need the center of the box, its half-extents, its rotation and the layers that it should hit. Half-extents are the half of the size in every direction, for example if you have a box with size (2, 6, 8) its half extents would be (1, 3, 4).
For the center you can use the GameObject transform position and for the rotation the GameObject’s transform rotation or you can add public variables to set them specifically.
Half-extents is simply a Vector3 so expose it and use it.
For the layers to hit you can expose a public property of type LayerMask. That will let you select layers via the inspector.

Collider[] colliders = Physics.OverlapBox(position, boxSize, rotation, mask);

if (colliders.Length > 0) {
Debug.Log("We hit something");
}

If you set that correctly and the box we are projecting is overlapping a Collider in the correct mask when you call this, you should see the message on the console.


Visual Representation

That’s cool but... It’s not very functional. Right now we can’t see the box we are defining anywhere, it would be really difficult to set correct sizes and positions of Hitboxes this way.
So how do we draw our box on the Scene view but not in-game? Using OnDrawGizmos.
As its documentation says: “Gizmos are used to give visual debugging or setup aids in the scene view.”, just what we were looking for!
We need to give our Gizmo a color and a transformation matrix. Don’t worry about it, we’ll just create a matrix with the position, rotation and scale of our transform.


private void OnDrawGizmos() {
Gizmos.color = Color.red;
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, transform.localScale);
Gizmos.DrawCube(Vector3.zero, new Vector3(boxSize.x * 2, boxSize.y * 2, boxSize.z * 2)); // Because size is halfExtents
}


If you want, you can use OnDrawGizmosSelected instead to only draw the box when you select the object.


Customization and flexibility

Customization is a broad subject and it’ll depend a lot on what kind of game you’re making and what functionality you are looking for.
In this case we will allow for a quick change in hitbox shape and color. If you are using Anima2D or some kind of bone oriented animation, you’ll probably want to also allow for the Hitbox to scale following a bone scale.
Changing the shape it’s as easy as adding a boolean and changing OverlapBox to some other shape, for example OverlapSphere. You need to add a public radius property to configure the sphere. Remember that you’ll also need to change what’s inside OnDrawGizmos to actually draw the new shape (in our example, DrawSphere).
Note that we are not adding a new component or removing anything, it’s just a boolean that will select the shape to overlap when it checks collision. That allows us to change the shape of our hitbox basically for free depending on the attack (or even in the same attack if we want to).
For color, I would like for a Hitbox to change color depending on if it’s inactive, if it’s checking for collisions or if it’s actually colliding with something. We will also need those states for our logic later so let’s add them.

We’ll create an enum for the state and 3 colors and add them as properties of our Hitbox.

public enum ColliderState {

Closed,
Open,
Colliding
}


Your class probably looks something like this:

public class Hitbox: MonoBehaviour {
public LayerMask mask;
public bool useSphere = false;
public Vector3 hitboxSize = Vector3.one;
public float radius = 0.5f;
public Color inactiveColor;
public Color collisionOpenColor;
public Color collidingColor;

private ColliderState _state;

/*
and your methods
*/
}


And now you can update your gizmos replacing the line Gizmos.color = Color.red; with a call to a new method:

private void checkGizmoColor() {
switch(_state) {
case ColliderState.Closed:
Gizmos.color = inactiveColor;
break;
case ColliderState.Open:
Gizmos.color = collisionOpenColor;
break;
case ColliderState.Colliding:
Gizmos.color = collidingColor;
break;
}
}

So where are we going to change our state? We will need three things:
  1. a way to tell the Hitbox to start checking for collisions 
  2. a way to tell it to stop 
  3. a way to actually check the collision
The first two are obvious:

public void startCheckingCollision() {
_state = ColliderState.Open;
}

public void stopCheckingCollision() {
_state = ColliderState.Closed;
}

Now, while a Hitbox is active we want to check every frame if it’s colliding with something until it stops being active. That brings us to the next point…


Independence of Unity Events API

As you probably know, to check something every frame you can use Update (for sake of simplicity I didn’t add the check to change the shape):

private void Update() {
if (_state == ColliderState.Closed) { return; }
Collider[] colliders = Physics.OverlapBox(position, boxSize, rotation, mask);

if (colliders.Length > 0) {
_state =  ColliderState.Colliding;
// We should do something with the colliders
} else {
_state =  ColliderState.Open;
}

}

You can see we are returning only if the current state is “Closed”. This means we are still checking collisions if the Hitbox is colliding, wich allows the Hitbox to hit several objects at the same time and not only the first it hits. It depends on your game how you want this to work.
We are using Update and we said we didn’t want to depend on Unity Events API! Well, depending on how you want to structure your code the solution is to make your own public update method, we could name it hitboxUpdate (the contents would be the same as our Update method above), and call it only in the Hitboxes used by the current attack.
Obviously we would have a call to Update() in some object higher in the hierarchy, but we certainly don’t need to use it on every Hitbox all the time just because it’s there.


Allowing an script in another object to use a Hitbox

Remember that a problem of using a Collider was that you needed a script in the same GameObject to implement OnTriggerEnter? As we are using our own script and can add to it whatever we want, the solution is pretty clear.
We will add an object as a property so we could call some method on it when the Hitbox collides with something.
There are several approaches to this:
  • You can add a public GameObject and use SendMessage. (This is not performant at all) 
  • You can do the same with a Monobehaviour that has a specific method to be called when a Hitbox collides. This has the disadvantage that if you want several different scripts to use Hitboxes you would need to add all those properties or inherit from a base script that has the method to be called
  • You can create an interface with the method you want to call and implement it in every class you want to use Hitboxes. 
Design wise, an interface is the clear choice for me. The only problem in Unity is that the editor by default does not renders interfaces as public properties, so you can’t assign it via the editor. We’ll cover why that is not a real problem in the next point.

Let’s create and use the interface:

public interface IHitboxResponder {
void collisionedWith(Collider collider);
}


Let’s add it as a property of our Hitbox…

public class Hitbox : MonoBehaviour {
...
private IHitboxResponder _responder = null;
...

/*
and the rest of the class
*/
}

You could also use an array instead of a single responder if you want.
Let’s use the responder:

public void hitboxUpdate() {
if (_state == ColliderState.Closed) { return; }
Collider[] colliders = Physics.OverlapBox(position, boxSize, rotation, mask);

for (int i = 0; i < colliders.Length; i++) {
Collider aCollider = colliders[i];
_responder?.collisionedWith(aCollider);
}

_state = colliders.Length > 0 ? ColliderState.Colliding : ColliderState.Open;

}

If you’re not familiar with the ‘?’ operator, check this.
Cool! But how do we set the _responder property? Let’s add a setter and cover that in the next point.

public void useResponder(IHitboxResponder responder) {
_responder = responder;
}


Not be coupled to a specific attack, Hitboxes should be usable by several different attacks

This section should make it clear why it doesn’t matter that we can’t set our HitboxResponders using the editor.
First let’s talk about these “responders”. In the approach we took to implement our Hitboxes, a responder is any class that should do something when a Hitbox collides with a Collider, and therefore implements IHitboxResponder. An example would be an attack script, you probably want to do damage to the thing you hit.
As we want to be uncoupled from any particular attack and be reusable, setting responders from the editor would not accomplish anything because we want to be able to swap responders on the fly.
As an example, let’s suppose we have two types of attack, a straight punch and an uppercut with the same arm, and each one of them has its own script that says in which frames of the animation they should hit, how much damage they should do and those kind of things. As they both are attacks of the same extremity, let’s reuse the same Hitbox.

public class Attack: Monobehaviour, IHitboxResponder {
...
public int damage;
public Hitbox hitbox;
...

public void attack() {
hitbox.setResponder(this);
// and do the rest of your attack
}

void collisionedWith(Collider collider) {
Hurtbox hurtbox = collider.GetComponent<Hurtbox>();
hurtbox?.getHitBy(damage);
}

}

Great! We got some working hitboxes. As you can see in the code above, we added a method getHitBy(int damage) to our hurtboxes. Let’s check if we can improve those.


Improving our Hurtboxes

Ideally we would want to cover more or less the same points we did with the Hitboxes. It should be easier because a Collider actually has the behaviour we want. Also we need to use a Collider or Physics.Overlap… won’t report a hit.
Note that because of the way we structured our code, we don’t need to use OnTriggerEnter for anything, we are getting our script using GetComponent.
That leaves us with customization and flexibility. To be as flexible as our Hitboxes we would need to add and remove colliders on the fly, and for customization we can draw a color over the collider depending on the state.

public class Hurtbox : MonoBehaviour {
public Collider collider;
private ColliderState _state = ColliderState.Open;

public bool getHitBy(int damage) {
// Do something with the damage and the state
}

private void OnDrawGizmos() {
// You can simply reuse the code from the hitbox,
// but taking the size, rotation and scale from the collider
}

}

Adding and removing Colliders on the fly is not as easy as with our Hitboxes. I haven’t found a satisfactory way to do this. What you can do is add several different Colliders to your script and select which one you want using a boolean as we did with the Hitboxes. the problem with this is that you need to add every Collider you want as a component and you end up with a lot of visual clutter in your editor window and in your object.
Another approach is to add and remove the components via code, but that would add a lot of unnecessary garbage and probably not be as precise.
What would be ideal is to be able to make Hurtbox inherit from Collider and make all the shape logic internal and only draw the one we are currently using, but I couldn’t make this work the way I wanted.


So now what?

If you followed the post until here, you now have hitboxes, hurtboxes and pushboxes implemented in Unity. But most importantly, you now know those abstractions if you didn’t before and that will simplify everything you need to build on top of them.
Your script inspector probably looks awful though, but don’t worry, we will cover that in another post:

You can convert this…

Into something like this!


Any feedback or questions? Let me know in the comments!
If you enjoyed the post, remember to check the Patreon!

You can also follow Strangewire on Facebook, Tumblr

5 comments:

  1. Hi Strangewire,
    I tried to follow up your blog by email, but when I press submit I'm getting error. Just wanted to let you know something is not right there.
    Best regards!~
    Adam

    ReplyDelete
  2. I really enjoyed reading this post, I always appreciate topics like this being discussed to us. Information very nice. I will follow post Thanks for sharing.
    friv300
    friv200
    friv400
    friv500

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Do you have a github repo for this? I would love to see it in action. I have some issues on my end.

    ReplyDelete
  5. Hello. Could you please explain how the blocking mechanic works? At the moment when we defend ourselves, if the hurtbox touches both the blockbox and the hitbox, what happens in this case? How to determine that we have successfully blocked the strike?

    ReplyDelete