You now have a controllable player, a working combat loop, and enemies that can fight back. The next step is to give that gameplay a home: actual levels that feel intentional rather than just a test room.
In this lesson you will:
- Design small, focused 2D levels that play to your core mechanics.
- Turn those layouts into Godot scenes using Tilemaps and reusable props.
- Build a scene management system that can:
- Load levels by name.
- Restart the current level.
- Move to the next level in a sequence.
By the end, you will have a simple but production‑ready level and scene‑flow system that we will keep reusing and extending in later lessons.
1. Define the Role of Your First Levels
Before opening Godot, decide what your first real levels should teach and test.
For this course, a sensible sequence looks like:
-
Level 1 – Movement sandbox
- A small room that encourages moving, jumping (if you have it), and basic navigation.
- Light enemy pressure or even no enemies at all.
-
Level 2 – Combat introduction
- A corridor or arena where the player can practice attacking a few enemies with plenty of space.
- Limited hazards so mistakes are recoverable.
-
Level 3 – Combined challenge
- A layout that mixes:
- Navigation (platforms, gaps, chokepoints).
- Combat (enemies placed in interesting spots).
For this lesson, focus on Level 1 and Level 2. You can add more later using the same patterns.
Pro tip:
- Keep levels shorter than you think.
- It is better to have 3 tight levels than 1 oversized one that players never finish.
2. Build a Reusable Level Scene Template
Instead of building each level from scratch, create a base level scene you can duplicate.
- In Godot, create a new
Node2Dscene calledBaseLevel. - Add children to it:
TileMap(for ground and walls).Node2DcalledProps(for barrels, decorations, etc.).Node2DcalledEnemies(spawn positions or enemy instances).Node2DcalledMarkers(for spawn points, exits).
- Save it as:
scenes/levels/base_level.tscn
This template makes it easy to keep:
- A consistent hierarchy across levels.
- Clear locations for things like enemies and exits.
Later, scripts that search for child nodes (for example a scene manager that looks for a PlayerSpawn marker) can rely on that structure.
3. Create Your First Level Scene
With BaseLevel ready, you can create your first actual level.
- Right‑click
base_level.tscnin the FileSystem dock and choose New Inherited Scene. - Name it:
scenes/levels/level_01.tscn
-
Select the
TileMapand:- Assign the same Tileset you used earlier in the course.
- Paint a simple room:
- Solid floor.
- Walls that enclose the space.
- A door or opening that visually reads as an exit.
-
Add a player spawn marker:
- Under
Markers, add aMarker2Dand name itPlayerSpawn. - Position it where you want the player to start.
- Under
-
Optionally add a level exit marker:
- Under
Markers, add anotherMarker2DnamedLevelExit. - Place it near the door or the far side of the room.
- Under
Your goal is not “perfect level design” but a clean, readable space that lets your core systems breathe.
4. Connect Levels to Your Existing Game Loop
Right now, your player and enemies probably live in a test scene.
We want to load them into level_01 instead.
One simple pattern is to use a Game or Main scene that:
- Instantiates the player.
- Loads the current level.
- Connects the two (for example spawns the player at
PlayerSpawn).
4.1. Create a Game Scene
- Create a new scene called
Gamewith a rootNode2D. - Add two children:
Node2DcalledLevelRoot.Node2DcalledPlayerRoot.
- Save it as:
scenes/game.tscn
You will use this scene as your main entry point instead of dropping directly into a level scene.
4.2. Write a Simple Scene Manager Script
Attach a new script to the Game root, for example Game.gd:
extends Node2D
@onready var level_root: Node2D = $LevelRoot
@onready var player_root: Node2D = $PlayerRoot
var current_level_scene: PackedScene
var current_level_instance: Node2D
var current_level_index := 0
var level_paths := [
"res://scenes/levels/level_01.tscn",
"res://scenes/levels/level_02.tscn"
]
func _ready() -> void:
load_level(current_level_index)
func load_level(index: int) -> void:
if index < 0 or index >= level_paths.size():
push_warning("Level index out of range")
return
# Clean up previous level
if is_instance_valid(current_level_instance):
current_level_instance.queue_free()
current_level_scene = load(level_paths[index])
current_level_instance = current_level_scene.instantiate()
level_root.add_child(current_level_instance)
spawn_player()
func spawn_player() -> void:
# Assume you have a Player scene and autoload or factory for it.
var player := get_or_create_player()
var spawn := current_level_instance.get_node_or_null("Markers/PlayerSpawn")
if spawn:
player.global_position = spawn.global_position
if player.get_parent() != player_root:
player_root.add_child(player)
func restart_level() -> void:
load_level(current_level_index)
func go_to_next_level() -> void:
current_level_index += 1
if current_level_index >= level_paths.size():
current_level_index = 0 # or show a victory screen
load_level(current_level_index)
Adapt get_or_create_player() to match how you currently instantiate your player.
The important part is:
load_level()always:- Clears the previous level.
- Instantiates the new level.
- Calls
spawn_player()to position the player atPlayerSpawn.
5. Wiring Exit Logic and Transitions
Your level exit marker should trigger a move to the next level. One straightforward integration is to let the player detect when it overlaps the exit.
5.1. Add an Exit Area
In level_01:
- Under
Markers, add anArea2DcalledLevelExitArea. - Add a
CollisionShape2Dto define the trigger zone. - Place it where the player should stand to finish the level.
5.2. Connect to the Game Scene
In your player script (or a small trigger script), you can:
func _on_body_entered(body: Node) -> void:
if body.name == "Player":
var game := get_tree().get_first_node_in_group("GameRoot")
if game:
game.go_to_next_level()
Alternatively, you can connect the Area2D’s body_entered signal directly to a method on the Game node if you add it to a known group or path.
The exact connection depends on how you structured your scenes earlier in the course; the core idea is:
- Exit → calls
go_to_next_level()on the scene manager.
6. Testing and Iterating on Level Flow
With the basics wired up, test your level structure like a player would:
- Start a new run and verify:
- The player always spawns at
PlayerSpawninlevel_01. - Restarting a level returns you to a clean state (no leftover enemies).
- Reaching the exit successfully loads the next level and respawns the player.
- The player always spawns at
If anything feels fragile:
- Add asserts and logging in
Game.gd:- For missing markers.
- For invalid level indices.
- Keep your level scenes small and focused.
- Name nodes consistently (
PlayerSpawn,LevelExitArea,Enemies,Props).
This kind of discipline prevents “mystery bugs” later in the course when levels get more complex.
7. Troubleshooting and Common Mistakes
Player spawns at (0, 0) or off-screen
- Check that:
Markers/PlayerSpawnexists in your level scene.- The path in
get_node_or_null("Markers/PlayerSpawn")matches your hierarchy exactly. - You are using
global_position, notposition, if your level is nested under another node with transforms.
Level exit does nothing
- Confirm that:
- The
Area2Dfor the exit has a collision shape. - Its collision layer/mask includes the player’s layer.
- The signal is connected to a method that actually calls
go_to_next_level().
- The
Restarting the level leaves old enemies or props behind
- Make sure
load_level()frees the entire level instance before instancing a new one. - Avoid manually freeing children in multiple places; centralize cleanup in the scene manager.
8. What’s Next
In this lesson you:
- Created a reusable level template with a clear node hierarchy.
- Built your first real level scenes with Tilemaps and markers.
- Implemented a simple scene management system to:
- Load, restart, and advance levels.
- Spawn the player correctly in each level.
In the next lesson, you will focus on UI and HUD design for your Godot 4 action game:
- Health bars and damage feedback.
- Ammo or resource displays (if applicable).
- A pause menu that plays nice with your scene manager.
If you have time before moving on, design a second level with slightly more enemy pressure and make sure it slots into your level_paths array cleanly.