2025/10/21

- Type
- Learning Resource
- Format
- Study Guide
- Version
- Godot 4.x
- Subject Tags
- Downloadable Demo
- FAQ/Troubleshooting
- Created
- Updated
- 2025/10/03
- 2025/10/21
In many games, dealing and taking damage is part of the core gameplay loop. To make this work, we use hit boxes and hurt boxes. Hit boxes are placed on weapons to deal damage (they hit things). Hurt boxes are placed on characters or objects that can take damage (they're the ones that get "hurt").
We usually create dedicated physics shapes to represent hit and hurt boxes for each character or entity that can deal or take damage. In Godot, we implement them using nodes for 2D games and Area2D for 3D games. Area3D
In this guide, you'll learn:
owner
property and duck-typing to make hurt boxes work as componentsNot all games need hit and hurt boxes separate from character physics: in a platformer you could just detect when your player collides with the enemy CharacterBody2D to damage them. CharacterBody2D
Hit and hurt boxes 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 hit boxes you need depends on the combat mechanics of your game. A sword swing, for example, can be a single hit box that can hit one or multiple hurt boxes.
If combat mechanics are central to your game, you'll often need many hit and hurt boxes that you'll want to tweak often during development. In that case, it's useful to implement them in a way that makes it easy to add, remove, and change them separately from movement physics.
This section is a quick reference for the video above, which was made with Godot 3. Here's what changed in Godot 4 compared to the video:
Annotations over keywords: To make a property available in the editor, we now use the @export
annotation instead of the export
keyword. Also, we now use @onready
instead of the onready
keyword to initialize a variable when the node is added to the scene tree.
Custom icons: In the demo code, you'll see the @icon
annotation to set a custom icon that appears in the scene tree. In Godot 3, we used to declare an icon like this: class_name MyClass, "res://path/to/optional/icon.svg"
.
Signal connections: Signals and functions are now first-class citizens. We connect signals using objects, like area_entered.connect(_area_entered)
, instead of using plain strings like before: connect("area_entered", self, "_on_area_entered")
. This gives you better autocompletion, error detection, and lets you connect to any function, including lambdas.
Collision detection: In Godot 3, collision detection worked both ways even when you didn't want it to. Let's say you have a player looking for collectible coins. In Godot 3, those coins would also detect the player, even if the coins weren't set up to scan for anything. In Godot 4, an object only detects collisions if it's actively looking for them through its collision_mask
. This means collision detection can work in one direction only, which is both easier to understand and more efficient.
Removed _init()
functions: In this updated demo, 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.
The rest of this guide uses Godot 4 syntax and conventions.
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 layers 1
and 2
in the project settings as Game World and Hit Boxes, respectively.
Here's how we set up our two types of areas:
collision_layer
on layer 2
(Hit Boxes). We set its collision_mask
to 0
so it doesn't detect anything.collision_mask
to 2
so it can detect hit boxes. We set its collision_layer
to 0
so other areas don't detect it.You may also want hit boxes 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 hit and hurt boxes on different layers and masks and make them detect each other.
Collision layers tell Godot what layer an area is on. Collision masks tell Godot which layers an area should look for.
In this demo, we want the hurt boxes to detect when they get hit. We put hit boxes on layer 2 and tell hurt boxes to look for layer 2 through their collision mask. This way, each object that can take damage handles its own damage calculations.
In a larger game project, you'll most likely want to put your damage code on the receiver's end like this. It's easier to have each character or enemy manage how much health it has and what happens when it takes damage (if there are multiple types of damage, resistances, status effects, knockback, etc.).
We use nodes for hit and hurt boxes because they detect overlaps without affecting physics. This means your character won't get pushed around when you hit something. Area2D
In our demo, the player has a sword that attacks the enemy.
The sword has a HitArea2D node as a child of the node. This way, the hit box rotates with the sprite when the attack animation plays. Sprite2D
The hit box carries the damage information. Here's the code for our HitArea2D
class:
class_name HitArea2D extends Area2D
@export var damage := 10
Making damage
an export variable means you can change it in the editor for different weapons without touching the code.
I've also turned off Monitoring
Turning off collision masks, monitoring and monitorable properties, or disabling collision shapes is a great way to improve physics performance as Godot doesn't need to check for collisions between objects that don't need to collide.
If you don't do that, Godot will check for collisions even if you don't use the results, which gets really costly when you have a lot of entities.
The is disabled by checking its CollisionShape2DDisabled
The hurt box receives damage from hit boxes. In our demo, the enemy has a HurtArea2D node that represents where it can take damage.
Like the HitArea2D, we make the HurtArea2D a child of the node so it moves and rotates with the sprite. Sprite2D
The hurt box connects to the area_entered
signal. When a hit box 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)
)
The owner
property is a reference to the root of the scene in which the HurtArea2D is placed. It's a convenient way to access the scene's root node and make a node work as a component, independently of the scene's structure.
Here, I've used duck typing: any scene with a HurtArea2D child can react to damage as long as it has a take_damage()
method.
Notice we use HitArea2D
as the parameter type instead of . This gives us two benefits: Area2D
hit_area
parameter.HitArea2D
nodes will meet the condition hit_area != null
. This helps filter out other nodes. Area2DWhen we want to connect a signal to a function, the function must have a matching signature. For example, the area_entered
signal passes an as an argument to the connected function, so the function must accept an Area2D as a parameter. Area2D
Instead of using as the parameter type, we can use any type that extends Area2D, like our custom Area2DHitArea2D
. If the area that entered is not an instance of HitArea2D
, the function will still be called, but hit_area
will be null
.
This is equivalent to writing the connected function like this, using the as
keyword to cast the to a Area2DHitArea2D
:
func _on_hit_area_area_entered(area: Area2D) -> void:
var hit_area := area as HitArea2D
if hit_area != null:
take_damage(hit_area.damage)
The size of your hit and hurt boxes is a design choice that depends on your game. For example, in single-player games, you might make enemy hurt boxes larger than their sprites to make them easier to hit.
At the same time, you could make the player's hurt box smaller than the sprite to make attacks easier to dodge. This makes the game more forgiving and fun for the player.
Import the zip file in Godot 4.5 or later to explore the code and see how everything works together.
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…