Two zones, two endings, one full-screen panel. Uses a flag so the game can only end once.
Loading the game engine. This takes a moment the first time.
Two zones. Green wins, red loses. Either one ends the game and brings up the full screen panel.
None of the earlier lessons use these. Each one shows the line where it appears here.
Makes a node appear or disappear, along with everything under it.
overlay.hide()A color made of red, green and blue, each from 0 to 1. A fourth number sets how see-through it is.
_end_game("You Win!", Color(0.4, 1.0, 0.5))Tints a node and everything under it. Its fourth number fades it out.
message_label.modulate = colorSet it to true and the whole game freezes, apart from nodes told to keep running.
get_tree().paused = trueSelects a button, so Enter or Space presses it straight away.
play_again_button.grab_focus()Starts the current scene over from the beginning.
get_tree().reload_current_scene()■ UI node · ■ 2D node · % unique name · .tscn instanced scene
extends Node2D
# "$GameOverPanel" is the node named GameOverPanel, sitting under this one.
@onready var game_over_panel: GameOverPanel = $GameOverPanel
## Once the game is over, ignore any more zone touches.
var is_game_over: bool = false
@onready var win_zone: Area2D = %WinZone
@onready var lose_zone: Area2D = %LoseZone
func _ready() -> void:
win_zone.body_entered.connect(_on_win_zone_entered)
lose_zone.body_entered.connect(_on_lose_zone_entered)
func _on_win_zone_entered(body: Node2D) -> void:
# Two reasons to quit early: the game already ended, or the thing that
# touched the zone was not the player. "not (...)" flips true and false.
if is_game_over or not (body is Player):
return
# Set the flag FIRST, so a second touch cannot get past the line above.
is_game_over = true
# GameOverPanel is our child, so we skip signals and call its function.
game_over_panel.show_win()
func _on_lose_zone_entered(body: Node2D) -> void:
if is_game_over or not (body is Player):
return
is_game_over = true
game_over_panel.show_lose()class_name GameOverPanel
extends CanvasLayer
@onready var message_label: Label = %MessageLabel
@onready var overlay: Control = %Overlay
@onready var play_again_button: Button = %PlayAgainButton
@onready var home_button: Button = %HomeButton
func _ready() -> void:
# Start hidden. The level shows it only when the game ends.
overlay.hide()
play_again_button.pressed.connect(_on_play_again_pressed)
home_button.pressed.connect(_on_home_pressed)
## Called BY game.gd when the player reaches the green zone.
func show_win() -> void:
_end_game("You Win!", Color(0.4, 1.0, 0.5))
## Called BY game.gd when the player reaches the red zone.
func show_lose() -> void:
_end_game("You Lose!", Color(1.0, 0.4, 0.4))
# Both show_win() and show_lose() do the same three things, so the shared
# work lives here and they each just pass in their own words and colour.
func _end_game(message: String, color: Color) -> void:
message_label.text = message
# "modulate" tints the label. Color(red, green, blue), 0.0 to 1.0 each.
message_label.modulate = color
overlay.show()
# Freeze the whole game behind the panel.
get_tree().paused = true
# "grab_focus()" puts the keyboard on this button, so pressing Enter
# works without touching the mouse.
play_again_button.grab_focus()
func _on_play_again_pressed() -> void:
# ALWAYS unpause before leaving, or the next scene opens frozen solid.
get_tree().paused = false
# "reload_current_scene()" builds this level again from scratch.
get_tree().reload_current_scene()
func _on_home_pressed() -> void:
get_tree().paused = false
get_tree().change_scene_to_file("res://shared/ui/lesson_select.tscn")class_name Player
extends CharacterBody2D
## How fast the player moves. "@export" puts "speed" at the top of the
## Inspector panel, so you can change it there instead of in this file.
@export var speed: float = 200.0
@onready var sprite: Sprite2D = $Sprite2D
# "_physics_process()" runs about 60 times a second. Godot calls it for you.
func _physics_process(_delta: float) -> void:
# "Input.get_vector(...)" checks all four keys at once and hands back
# an arrow pointing where to go. The four names in quotes are ours.
# See them under Project > Project Settings > Input Map.
var direction: Vector2 = Input.get_vector(
"move_left", "move_right", "move_up", "move_down"
)
# "direction" is only WHICH WAY (its length is always 1).
# "direction * speed" stretches it, so "velocity" is also HOW FAST.
velocity = direction * speed
# Nothing moves until "move_and_slide()" runs.
# "Slide" means we slide along walls instead of sticking to them.
move_and_slide()
# The warrior picture only faces right. Walking left mirrors it.
if direction.x != 0.0:
sprite.flip_h = direction.x < 0.0