Hitboxes that Feel Good: Reinventing the Goomba Stomp

If you’re in a hurry, skip to the approach I used.


We’re developing a game that utilizes an age old mechanic – the Goomba Stomp.

Ready Set Goat gameplay clips

There’s a common classical approach to this mechanic. A single, monolithic hitbox representing the physical space of the character. And that space is used for everything – stomping, getting hurt, and bumping into walls. (If you can’t tell, I’m foreshadowing here.)

Here’s what the Goomba Stomp looks like in Mario, with an overlay of the hitboxes next to it:

Mario stomping Mario stomping with hitboxes

By the way, there’s a great video on YouTube that overlays the hitboxes for an entire speedrun. This is just fun to watch; check it out:

Courtesy user qvidz on Youtube

The first thing you’ll notice is, you suck at Mario.

Just kidding, this is an AI playing the game. At least I think it is…

The second thing you might notice is, the hitboxes are tight. They are typically smaller than the character, which gives the player some breathing room. You can overlap a few pixels with an enemy – maybe a hair on a mustache, a cheek, or a toe – and not be damaged.

There’s some fudge factor here, and that makes the game slightly more forgiving. This is generally considered a good approach and should help produce a good Game Feel. Mario’s movement and feel were largely responsible for its success back in the 80s.

Tight hitboxes are a tried and true method for implementing this mechanic. So why devote a whole blog post to them?

In the course of implementing the Goomba Stomp in Ready Set Goat, I learned that just because it worked in one game, doesn’t mean it would work in mine. And after weeks of playtesting, we decided that something needed to be changed.

1. Tight Hitboxes

With tight hitboxes, Mario can stand next to a Goomba and not get punished for being a few pixels off. This is great. It’s forgiving to the player; they probably won’t even notice when it happens. They’d just notice if it wasn’t there.

But here’s the problem:

Mario visually stomps an enemy, but gets killed

See that? That extra breathing room works both ways. Now it benefits the Goomba.

In this scenario, Mario appears to stomp some of the enemy’s pixels, yet the player receives no reward. What’s worse is that the Goomba continues moving, eventually slamming into Mario, causing the plumber’s untimely demise.

In Ready Set Goat, the action is much more fast paced than Mario; there’s less time for the player to react to a misstep. So these weird situations can happen way more often. Let’s look at some other hitbox approaches and see if they’re any better.

2. Loose Hitboxes

Loose hitboxes work the opposite way. They give the player a huge footprint, allowing them to stomp from greater distances. In fact, the player might earn a stomp without any pixels touching. Right out of thin air.

Mario doesn’t quite earn a stomp yet, but is awarded one

There are a few downsides to this approach. The first is that it might be too easy to stomp. You might think that that makes the game less challenging. But it actually makes it less predictable. In some cases, the player might not want to stomp because the implications could lead them to their death. So by trying to make things easier, we’ve just made the game more challenging.

In Ready Set Goat, choosing when to stomp is just as important as choosing when not to stomp.

Stomping an enemy at the wrong time could accidentally bounce the goat off into a different enemy.

The worst problem though, is that the player might time a jump really close to the enemy, and clip them on the way up, resulting in the player’s death.

Mario should safely jump over the Goomba with this trajectory. But the hitboxes are too big and he gets owned.

3. Pixel Perfect

Pixel Perfect hitboxes are an opinionated response to the question “How much breathing room should I give the player?” Its answer is exactly none. The player’s stomps will connect predictably, but the player gets no leeway if they make a mistake.

Unforgiving pixel-perfect collisions

While the community over at /r/hitboxporn might disagree, I believe this approach leads to an unforgiving game. While testing it out, I found myself frustrated with the exactness of Pixel Perfection.

Of course, there are simple ways to correct this. For example, you could only count a collision if more than a certain percentage of pixel overlap. But that still suffers from the problem of being unpredictable.

4. Composite Hitboxes

The last common approach I can think of is Composite Hitboxes. However this approach doesn’t answer the question “How much breathing room should I give the player?” It gives you a more accurate representation of the player’s volume (just like Pixel Perfect), but the developer is still left to decide whether that space should be Tight or Loose.

Example of composite hitboxes

The approach by itself doesn’t solve anything for us. But what about the idea in general? Multiple hitboxes… maybe we’re on to something here.

The Elephant… Er…. Goat in the Room

After considering these four approaches, finally, it hit me like a ton of brick blocks. The hitboxes in Mario (and games like it) are having an identity crisis.

The space where Mario attacks from is the same space in which he’s vulnerable. To complicate things further, it’s also his physical space in the world; platforms and walls use this hitbox to determine if Mario bumped into them.

To some, this sort of ambiguity might be an obvious problem. To me, it was an epiphany.

A New Player Has Entered the Game

To solve the problem, I looked outside the platformer genre for inspiration. A common pattern from a different genre gave me some ideas.

Fighting games use a different concept; a hurtbox and a hitbox. A hurtbox is the space where the player is vulnerable; where they get hurt. A hitbox is where a player’s hits land.

Fighter games break out character space into two types: Hitboxes and Hurtboxes

A quick note on terminology: I come from a First Person Shooter background. In FPS’s, we define a hitbox as the area where a character can be hit. If you shoot a bullet at their hitbox, you hit them.

But fighting games would call that a hurtbox.

Regardless, in Mario, there is no differentiation. Your hurtbox is your hitbox, and vice versa.

The swapped terminology is bound to cause confusion, possibly anger. I’m sticking to the FPS-friendly terminology for the rest of the article, because that’s what I know best. But the point of this article is less about terminology, and more about the concept. Call these boxes whatever works best for you in your own projects. AttackBox and DefenseBox are one suggestion.

The different identification of these boxes led me to my final approach: multiple boxes, each defining a different spatial representation of the player.

Hitboxes and Hurtboxes and Bounding Boxes, Oh My!

I categorized and defined the three different spatial representations of the characters:

  1. Hitbox
    The area where the character can get hit (where they are vulnerable)
  2. Hurtbox
    The area where the character hurts others (where they attack)
  3. Bounding Box
    The space the character occupies according to obstacles (platforms, walls)

Then I organized my colliders in game to represent this scheme.

Final box design of my Goat character

And I did the same thing with the enemies.

Then I set up my game engine (Unity) with the following rules:

  1. Player Hurtboxes should collide with Enemy Hitboxes
  2. Enemy Hurtboxes should collide with Player Hitboxes
  3. Players or Enemies bump into the environment

(I explain how to do all of this in Unity further below)

The Results

I’m really happy with this solution, it solves a lot of the problems of the other four.

The player can get a stomp even when the Goat is a few pixels away. This fixes the problem with Tight Hitboxes. See how the Goat’s Hurtbox and the Enemy’s Hitbox connect even from some distance:

But they can still narrowly avoid an Enemy on the upward jump. This is an improvement over the Loose Hitboxes. Check out how the Goat’s Hitbox and Enemy’s Hurtbox near-miss eachother in this gif:

The player gets room to breath, but can still stomp without feeling cheated.

I didn’t want my game to be too easy, but now I at least have control over how easy it is. For example, I could increase the width of the Goat’s Hurtbox and the Enemy’s Hitbox with no negative side effects. This would make it easier for the Goat to get a Stomp in, and still stay safe while jumping upward.

It’s pretty easy to get this all set up.

Implementation in Unity

Step 1: Setup your layers

For a one-size-fits-all solution to this problem in Unity, you’ll need six layers. You could probably get away with less, but this really makes it obvious what’s happening under the hood.

6 Layers for Hitboxes That Feel Good™️

Step 2: Setup the collision matrix

You’ll need to tweak the collision matrix to support this scheme, as follows:

Notice how few checkmarks there are. There’s really only 3 collisions we need to worry about

  1. Player hurts an Enemy
  2. Player gets hurt by an Enemy
  3. Player or Enemy bump into the environment

Step 3: Setup your characters’ boxes

Now for the actual characters. Each box will be represented as a Collider2d (this concept works in 3d too however). Since Unity only allows one Collider per GameObject, we need to store 2 of the Colliders on child GameObjects. This makes the solution a bit more complex, but it’s manageable.

Here’s an example of my player prefab, Goat’s, hierarchy:

My parent GameObject gets the Bounding Box Collider. Therefore, it’s also on the BoundingBoxes layer.

The two children, Hitbox and Hurtbox, get their Colliders on the appropriate layers as well.

Note that all my colliders have Is Trigger checked. That’s because I don’t use the built in Unity Physics system for Ready Set Goat. I handle all of the collision interactions manually, and triggers simply notify me when they happen. Triggers don’t simulate physics reactions, they just alert you when an overlap occurs. If you want to use Unity Physics with this approach, you might want to uncheck the Is Trigger box, and add some RigidBody’s to the root GameObject.

Anyway, here’s that gif of the goat again, showing what these 3 colliders look like in one image.

The same approach is taken with my enemy characters, except I use the EnemyHitboxes and EnemyHurtboxes layers instead:

Step 4: Attack!

For the final step, you’ll need to add code that reacts to collisions. I like to put “attack” code to the attacker itself. A simple way to do that is to put a script on your Hurtboxes’ GameObjects.

For example, on my Player’s Hurtbox, I want to handle Stomps.

A script that detects when a Player stomps on an Enemy, and applies damage as necessary

Your script might look something like this

using UnityEngine;

public class Stomp : MonoBehaviour {
    private float _prevY;

    private void FixedUpdate() {
        _prevY = transform.position.y;
    }

    private bool IsDescending() {
        return transform.position.y < _prevY;
    }

    private void OnTriggerEnter2D(Collider2D collider) {
        if (IsDescending()) {
            Debug.Log("Player stomped an Enemy. Destroying the Enemy.");
            Destroy(collider.gameObject);
        }
    }
}
C#

One thing to note here: Stomps can only occur if the player is descending vertically. They can’t stomp upward. For simplicity’s sake, I’m calculating that by tracking their previous y coordinate and ensuring that their current y coordinate is lower.

Your enemies will need to attack too. Let’s add a script for that:

The attack script that enemies have

And the code is pretty similar, except we don’t need to check if the enemy is descending vertically:

using UnityEngine;

public class KillPlayerOnTouch : MonoBehaviour {
    private void OnTriggerEnter2D(Collider2D collider) {
        Debug.Log("Enemy touched a Player. Killing Player.");
        Destroy(collider.gameObject);
    }
}

Step 5: Report

There’s one thing missing here. In both the Stomp and KillPlayerOnTouch scripts, the colliding object (a Hitbox) gets destroyed. If you recall, that object is actually a child of the character. We want to destroy the entire character, not just their Hitbox. To do this, one more script can help.

Note that I’m no longer working on the Hurtboxes. I’m placing this script on the Hitbox. Here’s an example of my Player’s Hitbox:

The InformParentOnDestroy script will simply call Destroy on a specific object when the Hitbox itself is destroyed. We could guess what the Parent is, say, by calling transform.parent.gameObject. But in practice, your character prefab hierarchies will be much deeper and complex. It’s better to be explicit here. Here’s an example of what that code looks like:

using UnityEngine;

public class InformParentOnDestroy : MonoBehaviour {
    public GameObject Parent;

    private void OnDestroy() {
        Destroy(Parent);
    }
}

Simple enough.

Next Steps

This example makes a lot of assumptions, and there’s still a lot of work to do to get this production ready. But if you’re working on a similar game mechanic, I hope this sets you in the right direction.

Some other things I’ve done to make sure Ready Set Goat feels good to the player.

Choose hit/hurtboxes that favor the Player

The favored character (usually the Player), should have a larger hurtbox than their hitbox. The unfavored character (the Enemy), should have the opposite; a larger hitbox than hurtbox. That will ensure that they Player’s Stomp gets detected before the Enemy’s touch. You can experiment with other hitbox/hurtbox sizes to accomplish something similar though.

Fix Your Timestep!

This can help ensure that every subtle movement the characters make is processed, independent of the framerate. It’ll ensure your characters don’t end up moving through eachother, leaving you with a chicken and egg problem (did the player’s stomp land first or did the enemy’s hit land first?).

Execute Stomp code before KillPlayerOnTouch code

There’s no great way to ensure one component’s OnTriggersEnter methods get executed before another’s in Unity.

My personal approach is to create a new Monobehaviour (let’s call it CollisionResolver) and add it to the stage. Now Stomp and KillPlayerOnTouch must no longer Destroy any GameObjects directly. Instead they must inform CollisionResolver that a collision occurred. CollisionResolver can then process all the collisions it heard about on Update, in whatever order it chooses. In my game, it would process all Stomp reports before KillPlayerOnTouch‘s, because I want to favor the Player’s attack.

That approach is just a bit outside the scope of this post, so I leave it as an exercise to the reader.