Coding Quests
The Scroll Library
Tutorials

Godot 4 Health and Damage System (with a Health Bar)

June 16, 20266 min read

Almost every game needs health. The player takes damage, an enemy dies, a bar on screen ticks down. It sounds trivial, and the naive version is, but the naive version also tangles your player script into your UI in a way you'll regret. Here's how to build it so it stays clean.

Put health in its own component

The instinct is to dump health into your player script. Don't. Make a small reusable component instead, and now your player, your enemies, and that breakable barrel all share the exact same code.

Create a Node called HealthComponent with this script:

GDScript
extends Node
class_name HealthComponent
signal health_changed(current: int, max: int)
signal died
@export var max_health: int = 100
var current_health: int
func _ready() -> void:
current_health = max_health
func take_damage(amount: int) -> void:
current_health = clampi(current_health - amount, 0, max_health)
health_changed.emit(current_health, max_health)
if current_health <= 0:
died.emit()
func heal(amount: int) -> void:
current_health = clampi(current_health + amount, 0, max_health)
health_changed.emit(current_health, max_health)

Two things make this work. clampi() keeps health between 0 and max, so you never overheal or show negative numbers. And the signals mean this component announces what happened without knowing or caring who's listening. That decoupling is the whole point.

Wire it into a character

Add a HealthComponent as a child of your player or enemy, then react to its signals:

GDScript
extends CharacterBody2D
@onready var health: HealthComponent = $HealthComponent
func _ready() -> void:
health.died.connect(_on_died)
func _on_died() -> void:
queue_free() # or play a death animation, drop loot, respawn, etc.

When something hits this character, it calls health.take_damage(25). The character doesn't manage its own health number at all. It just owns a component and listens for the one event it cares about.

Connect a health bar

This is where the component pays off. Drop a TextureProgressBar (or a plain ProgressBar) into your UI and connect it to the same health_changed signal:

GDScript
extends TextureProgressBar
@onready var health: HealthComponent = $"../Player/HealthComponent"
func _ready() -> void:
health.health_changed.connect(_on_health_changed)
# Set the starting state
_on_health_changed(health.current_health, health.max_health)
func _on_health_changed(current: int, maximum: int) -> void:
max_value = maximum
value = current

The bar updates itself whenever health changes, and the player script has no idea the bar exists. Want a damage number popup or a screen flash too? Connect another listener to the same signal. Zero changes to the player or the component.

A smooth bar instead of a snapping one

A bar that jumps instantly reads as cheap. Tween it instead so it slides down:

GDScript
func _on_health_changed(current: int, maximum: int) -> void:
max_value = maximum
var tween := create_tween()
tween.tween_property(self, "value", current, 0.2)

Two hundred milliseconds of slide and suddenly it feels like a real game.

Take it further

Once health is a clean component, everything else slots in: armor that reduces incoming damage, invincibility frames after a hit, regeneration over time, damage types. That's exactly the territory our Stats and Leveling System quest covers, building a full component-based stat system with modifiers, equipment bonuses, and damage formulas on top of this same idea.

If you want the deeper version of the architecture behind this, the RPG stat system guide goes further on modifiers and damage math.

FAQ

Where should I store health in Godot?

In a dedicated HealthComponent node, not directly in your player script. A component is reusable across the player, enemies, and destructible objects, and it lets your UI and other systems react through signals without coupling to the character.

How do I make a health bar in Godot 4?

Use a ProgressBar or TextureProgressBar, set its max_value and value, and update them from a health_changed signal. Connecting the bar to a signal means it refreshes automatically whenever health changes, with no per-frame polling.

How do I keep health from going below zero or above the max?

Use clampi() when you change it: current_health = clampi(current_health - amount, 0, max_health). That caps the value at both ends in one call, so you never display negative health or heal past the maximum.

godottutorialhealthrpg

Reading is the map. The quest is the territory.

Build this for real in the Stats & Leveling System Quest.

Start the Quest
Written by Coding Quests

We teach Godot 4 by making you build complete systems: inventories, save systems, souls-like controllers, enemy AI. The scrolls are free. The quests are where it sticks.