Lose health by touching a hazard. Mixes a signal with a direct function call, and shows when to use each.
Loading the game engine. This takes a moment the first time.
This file has no code at all. Everything happens in the other three, and they talk using signals plus one direct function call.
hazard.gd calls body.take_damage(1) right on the player. It can do that because body_entered already handed it the player.player.gd lowers "health", then emits health_changed.hud.gd connects to health_changed and redraws the number.■ UI node · ■ 2D node · % unique name · .tscn instanced scene
extends Node2Dextends Area2D
## How much health the player loses on a touch.
@export var damage: int = 1
## How many seconds before this hazard is allowed to hurt again.
@export var cooldown: float = 1.0
# True when this hazard is ready to hurt. False during the cooldown.
var _can_hurt: bool = true
func _ready() -> void:
body_entered.connect(_on_body_entered)
# "body" is whatever touched the hazard.
func _on_body_entered(body: Node2D) -> void:
# BOTH must be true: it is the player, AND we are not cooling down.
if body is HealthPlayer and _can_hurt:
# "body_entered" already handed us the player as "body", so we just
# call a function on it. No signal needed going back. This is how
# damage works in most games.
body.take_damage(damage)
# Start the cooldown so we cannot hurt again straight away.
_can_hurt = false
# "create_timer()" makes a one-time timer without adding a node.
var timer: SceneTreeTimer = get_tree().create_timer(cooldown)
# The "func() -> void:" bit is a lambda, a tiny function with no name.
# When the timer runs out it flips "_can_hurt" back to true.
timer.timeout.connect(func() -> void: _can_hurt = true)extends CanvasLayer
# "%HealthLabel" means "the node named HealthLabel in this scene."
@onready var health_label: Label = %HealthLabel
func _ready() -> void:
# player.gd emits BOTH of these signals, inside its "take_damage()".
GameEvents.health_changed.connect(_on_health_changed)
GameEvents.player_died.connect(_on_player_died)
%HomeButton.pressed.connect(_on_home_pressed)
# Loads the lesson list screen, which replaces this whole scene.
func _on_home_pressed() -> void:
get_tree().change_scene_to_file("res://shared/ui/lesson_select.tscn")
# "new_health" is the number player.gd sent along with the signal.
func _on_health_changed(new_health: int) -> void:
# "%d" is a blank spot that the number drops into.
health_label.text = "Health: %d" % new_health
func _on_player_died() -> void:
health_label.text = "You died!"class_name HealthPlayer
extends CharacterBody2D
## How fast the player moves.
@export var speed: float = 200.0
## How much health the player starts with, and the most they can hold.
@export var max_health: int = 5
# The health right now. "_ready()" fills it in when the level starts.
var health: int
func _ready() -> void:
# Begin the level at full health.
health = max_health
func _physics_process(_delta: float) -> void:
var direction: Vector2 = Input.get_vector(
"move_left", "move_right", "move_up", "move_down"
)
velocity = direction * speed
move_and_slide()
# The warrior picture only faces right. Walking left mirrors it.
if direction.x != 0.0:
$Sprite2D.flip_h = direction.x < 0.0
## Called BY hazard.gd, not by anything in this file. Any script holding
## this player can call "take_damage(1)" to hurt it.
func take_damage(amount: int) -> void:
health -= amount
# Never let a negative number reach the screen.
if health <= 0:
health = 0
# hud.gd connects to "player_died" and shows the death message.
GameEvents.player_died.emit()
# Emit this one LAST, so the HUD draws the corrected 0 instead of -1.
GameEvents.health_changed.emit(health)extends HealthPlayer
## How hard the player jumps. Negative is up.
@export var jump_velocity: float = -620.0
## How hard gravity pulls down.
@export var gravity: float = 1800.0
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
# "speed" is inherited from HealthPlayer.
velocity.x = Input.get_axis("move_left", "move_right") * speed
move_and_slide()
# The warrior picture only faces right. Walking left mirrors it.
if velocity.x != 0.0:
$Sprite2D.flip_h = velocity.x < 0.0