Handling mêlée attacks and damage with hitboxes and hurtboxes
2026/02/01
- Type
- Learning Resource
- Format
- Video Update
- Version
- Godot 4.x
- Subject Tags
- Update/Code Patch
- FAQ/Troubleshooting
- Downloadable Demo
- Bonus
- Created
- Updated
- 2025/10/03
- 2026/02/01
This guide is a Godot 4 update to a popular video tutorial on coding mêlée attacks using hitboxes and hurtboxes, (originally recorded using Godot 3).
Since some things are done a little bit differently in Godot 4, you can watch the video without trying to repeat the steps verbatim, just to get the general idea.
The study guide that follows and the associated demo project, including the code and assets, are all up-to-date and compatible with Godot 4.5 or later. You can directly skip to the updated guide if you feel you don't really need the video.
@export annotation instead of the export keyword. We also use @onready instead of the onready keyword to initialize a variable when the node is added to the scene tree.@icon annotation. Those appear in the scene tree and when you search for a node. This step is optional and not shown in the video. (In Godot 3, we used to declare an icon like this: class_name MyClass, "res://path/to/optional/icon.svg").area_entered.connect(_area_entered), instead of plain strings like: connect("area_entered", self, "_on_area_entered"). This gives you better autocompletion, error detection, and lets you connect to any function, including lambdas._ready() instead of _init(): In the updated demo, you will notice we removed the _init() function definitions in favor of using _ready(). This makes it clearer that the assignments happen when the node is added to the scene tree, and it's a more familiar function for Godot users.On collision detection in Godot 3 vs Godot 4
In Godot 3 collision detection worked both ways even when you didn't want it to. Let's say you had a player looking for collectible coins. The coins would also be scanning for the player by default. Whereas in Godot 4, an object only detects collisions if it's actively set to look for them through its collision_mask. This means collision detection can work in one direction only, which is both more intuitive AND more efficient!
In many games, dealing and taking damage is part of the core gameplay loop. To set this up, we use areas that are conventionally called hitboxes and hurtboxes.
Hitboxes are the areas you place on or around weapons or enemies to define the parts that deal damage (what parts can "hit" other things).
Hurtboxes are the areas you place on or around characters or objects to define where they take damage (what parts can be "hurt" by other things).
We usually create dedicated physics shapes to represent hitboxes and hurtboxes for each character or entity that can deal or take damage.
In Godot, we implement them using Area2D nodes for 2D games and Area3D for 3D games because they detect overlaps without affecting physics. This means your character will detect the impact but it won't get pushed around by it.
First, I recommend downloading the demo project to explore the code and see how it works. There are two important scenes in the demo:
In this demo, we named physics layer 1 Game World and physics layer 2 as hitboxes.
Collision layers tell Godot what layer an area is on. Collision masks tell Godot which layers an area should look for.
Here's how we set up our two types of areas:
collision_mask to 0. But it can be detected by other areas on layer 2 so we set its collision_layer to 2 (hitboxes). 2 so we set collision_mask to 2. But other areas can't detect it so we set its collision_layer to 0.You may also want hitboxes to detect when they hit something, for example, to play a different sound or visual effect depending on what they hit.
In that case, you can put hitboxes and hurtboxes on different layers and masks and make them detect each other.
In our demo, the player has a sword that attacks the enemy.
You can see in the scene tree that Sword2D has a HitArea2D node (with a custom icon!) as a child of the Sprite2D node. This way, the hitbox rotates with the sprite when the attack animation plays.

The hitbox carries the damage information. Here's the code for our HitArea2D class:
class_name HitArea2D extends Area2D
@export var damage := 10By making damage an export variable you turn it into a property that can be changed directly in the editor without touching the code. This is very practical for reusing the hitbox to set up different weapons or weapon upgrades.
Since the hitbox doesn't need to detect anything in this case, we can turn off its Monitoring
Turning off collision masks, monitoring and monitorable properties, or disabling collision shapes (when it makese sense to!) is a good way to improve physics performance.
If you don't do that, Godot will check for collisions even when you don't need the results and that can quickly get costly the more entities you have.
Finally, since we only want the hitbox to be active during the attack animation, we check the Disabled
Instead, we enable the CollisionShape2D directly in the animation player.

If you inspect the animation player, you can see it has 2 tracks, reset and slash. In the slash track, we uncheck the Disabled

In our demo, the enemy takes damage from the sword.
You can see in the scene tree that Enemy2D has a HurtArea2D node (again with a custom icon!) as a child of the Sprite2D node. This way, the hurtbox moves and rotates with the sprite as the enemy moves.

The hurtbox connects to the area_entered signal. When a hitbox enters, it calls take_damage() on the owner:
class_name HurtArea2D
extends Area2D
func _ready() -> void:
area_entered.connect(
func _on_area_entered(hit_area: HitArea2D) -> void:
if hit_area != null and owner.has_method("take_damage"):
owner.take_damage(hit_area.damage)
)Here, I've used what we call "duck typing": any scene with a HurtArea2D child can react to damage as long as it has a take_damage() method.
The owner property is a reference to the root of the scene that contains the HurtArea2D. It's a convenient way to access the scene's root node and make it work as a component, independently of the scene's structure.
The area_entered signal passes an argument of type Area2D to the connected function, so the function must accept a matching Area2D type as a parameter.
Instead of Area2D, we can use any parameter type that extends Area2D, like our custom HitArea2D.
This gives us two benefits:
HitArea2D, the function will still be called, but hit_area will be null.hit_area parameter.It's equivalent to writing the connected function like this, using the as keyword to cast the Area2D to a HitArea2D:
func _on_hit_area_area_entered(area: Area2D) -> void:
var hit_area := area as HitArea2D
if hit_area != null:
take_damage(hit_area.damage)Not all games need hitboxes and hurtboxes separate from character physics: in a platformer, in order to take damage, the player's CharacterBody2D would just need to detect collision with the enemy's CharacterBody2D.
Hitboxes and hurtboxes become necessary for games with more complex combat mechanics. One of the most common use cases is when a character can take damage to multiple body parts, like a headshot or a leg shot. For that, you need different physics entities for each body part. Depending on the part that gets hit, you can apply different damage amounts or cripple the character.
How many hitboxes you need depends on the combat mechanics of your game. A sword swing, for example, can be a single hitbox that can hit one or multiple hurtboxes.
If combat mechanics are central to your game, you'll often need many hitboxes and hurtboxes that you'll want to tweak often during development. For this reason, it's useful to implement them in a way that makes it easy to add, remove, and adjust their properties separately from movement physics.
The size of your hitboxes and hurtboxes is a design choice that depends on your game. For example, in single-player games, you might make enemy hurtboxes to be larger than their sprites to make them easier to hit.
At the same time, you could make the player's hurtbox smaller than the sprite to make attacks easier to dodge. This makes the game more forgiving and fun for the player.
This demo project was created with Godot 4.5 and should be usable for any Godot 4.x version.
Import the zip file in Godot 4.5 or later to explore the code and see how everything works together.
It's pretty easy! You can create a simple vector shape in any software that supports vector creation like Inkscape or Adobe Illustrator or even Figma. Remember to keep the shape simple enough that it can be recognizable at a small size. Then save it in your project folder and add it to your scripts using @icon:
@icon("res://path/to/class/your_own_icon.svg")In the demo project, you can see we did this for both the HitArea2D and the HurtArea2D scripts. There is no path in our case because we placed the .svg files next to the scripts in the project folders.
In Module 10 of Learn 2D Gamedev From Zero and Module 4 of Learn 3D Gamedev, you become able to set up your projects and code mêlée and ranged attacks including different types of enemies, on your own without depending on tutorials.
Don't stop here. Step-by-step tutorials are fun but they only take you so far.
Try one of our proven study programs to become an independent Gamedev truly capable of realizing the games you’ve always wanted to make.
Get help from peers and pros on GDQuest's Discord server!
20,000 membersJoin ServerThere are multiple ways you can join our effort to create free and open source gamedev resources that are accessible to everyone!
Sponsor this library by learning gamedev with us onGDSchool
Learn MoreImprove and build on assets or suggest edits onGithub
Contributeshare this page and talk about GDQUest onRedditYoutubeTwitter…