In this episode we dive into 2D game fundamentals: sprites, tilesets, tilemaps, and parallax backgrounds. Then physics bodies, collision, area detection, animation with AnimationPlayer and Tween, and Camera2D and UI overlays so the camera always follows the player's action.

In episode 5, you got familiar with GDScript: variables, functions, signals, and lifecycle callbacks like _ready(){gdscript} and _process(delta). Now it's time to use all of that to build a living 2D world. Episode 6 is the point where you stop "writing scripts" and start "making games." Every element covered here — sprites, tilemaps, physics, animation, and cameras — is raw material you'll keep assembling in every 2D project.
This episode's roadmap: we start with sprites and how to display visuals, then assemble a world from tilesets and tilemaps, add depth with parallax, bring physics to life through physics bodies and collision, detect events with Area2D, drive animation with AnimationPlayer and Tween, and wrap up with Camera2D plus a UI overlay.
The most basic node for showing 2D images is Sprite2D. You can drag it into a scene, then fill in the texture property from an image file imported by Godot. To set the position, just change the position property in the inspector — or via script:
extends Sprite2D
func _ready() -> void:
texture = preload("res://assets/player.png")
scale = Vector2(2, 2)
rotation_degrees = 15Notice preload("res://..."){gdscript} — this loads the resource at compile time, so the image is already available the moment the scene runs. For large assets or those loaded on demand, use load(){gdscript}, which is evaluated at runtime. Key point: Godot stores a node's position at its center (centered), not its top-left corner, so when you move a sprite, its pivot point is in the middle.
Placing a single sprite is easy. But what if a level consists of hundreds of dirt, grass, and stone tiles? That's the job of TileSet and TileMapLayer. TileSet is a resource that defines the "tiles" available, complete with a collision shape per tile. TileMapLayer is the node that places those tiles on a grid.
The basic workflow: import one image sheet containing all the tiles, create a new TileSet, set the tile_size (for example 16x16), pick which tiles are valid, then drag a TileMapLayer into the scene and paint the tiles directly in the editor. After that, per-tile collision is configured through the physics layer property on the tile. This is the most efficient way to build a level — not one sprite per tile, but one draw call per tilemap.
extends TileMapLayer
func _ready() -> void:
var cell := local_to_map(Vector2(32, 32))
var source := get_cell_source_id(cell)
print("cell: %s, source id: %s" % [cell, source])The local_to_map{gdscript} function converts world coordinates into grid cell coordinates, while get_cell_source_id{gdscript} tells you which tile occupies that cell — very useful for interaction systems like breaking rocks or opening doors.
A static background feels flat and boring. The solution is parallax: several layers moving at different speeds to create a sense of depth. In Godot, just use two nodes: ParallaxBackground as the root, and one or more ParallaxLayer nodes as layers.
Each ParallaxLayer has a motion_scale property. A value of (1, 1) means moving as fast as the camera; a value of (0.2, 0.2) moves very slowly so it appears far in the background. For example, a sky layer with motion_scale = (0, 0) stays completely still, while a mountain layer with (0.3, 0.3) seems to lag behind the player.
extends ParallaxLayer
@export var parallax_speed: Vector2 = Vector2(0.3, 0.3)
func _ready() -> void:
motion_scale = parallax_speedOne rule of thumb: ParallaxBackground must be a direct child of the scene root, and the camera must be under it so the movement stays in sync. Combining several ParallaxLayer nodes with Sprite2D inside them is the standard pattern for almost every 2D platformer.
Now we get to the most crucial part: physics. Godot provides several bodies, each with its own role:
CharacterBody2D — controlled directly by code (usually the player), using move_and_slide(){gdscript}.RigidBody2D — controlled by the physics engine, affected by gravity and forces.StaticBody2D — stays in place, for floors and walls.All bodies need a CollisionShape2D (or CollisionPolygon2D) as their physical shape. A basic example of a movable player that doesn't pass through floors:
extends CharacterBody2D
@export var speed := 250.0
func _physics_process(delta: float) -> void:
var direction := Input.get_axis("move_left", "move_right")
velocity.x = direction * speed
move_and_slide()move_and_slide(){gdscript} does all the heavy lifting: moves the body according to velocity, detects collisions, and slides along surfaces. This is the most common way to make a character walk on floors without sinking through — as long as the floor is a StaticBody2D with a clean collision shape.
Sometimes you don't need physical collisions, only to know "did something enter this zone?". The answer is Area2D. This node detects other bodies overlapping it, without repelling them like a wall. Area2D is used for: level triggers, item pickups, damage zones, and checkpoints.
Area2D emits the body_entered and body_exited signals you can connect:
extends Area2D
signal coin_collected
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
coin_collected.emit()
queue_free()Notice the pattern above: the Area2D checks whether the entering body is the player via is_in_group("player"), then emits the coin_collected signal and removes itself with queue_free(){gdscript}. This synergy of signals (episode 5) + Area2D is what builds most of the interaction logic in 2D games. Distinguish their roles: CharacterBody2D and StaticBody2D for physical collisions that affect movement, while Area2D is for collision-free detection — the player uses a body, danger zones use an area.
AnimationPlayer records property keyframes over time — for example a sequence of running frames from a sprite sheet, or a door opening. These animations are created in the editor via the Animation panel, then played from script:
extends AnimationPlayer
func _ready() -> void:
play("run")
func _physics_process(delta: float) -> void:
var direction := Input.get_axis("move_left", "move_right")
if direction != 0.0:
play("run")
else:
play("idle")Tween is different: it animates properties programmatically from value A to value B. It's good for one-shot effects — jumping, fading out, UI pop-ups, or camera moves.
extends Node2D
func _ready() -> void:
var tween := create_tween()
tween.tween_property(self, "position", Vector2(200, 0), 1.0)The key difference: AnimationPlayer for repeating animations prepared in the editor and previewable, Tween for ad-hoc animations from within code that are dynamic to gameplay conditions. You'll use both throughout your Godot career.
All that world is useless if you can't see it. Camera2D is your eye. When enabled = true, the screen follows this node — place it as a child of the player and the camera automatically trails their movement. A few properties you must know:
position_smoothing_enabled — the camera moves smoothly, without jitter.limit_left/right/top/bottom — limit the camera so it doesn't leave the level.zoom — zoom the view in or out.extends Camera2D
func _ready() -> void:
enabled = true
position_smoothing_enabled = true
position_smoothing_speed = 8.0
limit_left = 0
limit_right = 1024Finally, UI overlay: interface elements like score and health shouldn't scroll with the world. The solution is CanvasLayer — a separate layer whose position isn't affected by world transforms. Inside it you place a Label for the score, a TextureProgressBar for health, and so on. We'll dissect this overlay in more depth in episode 8 about UI & HUD.
Episode 6 equips you with the entire visual and physics foundation of 2D games: displaying sprites, building modular levels with TileSet and TileMapLayer, creating depth through parallax, bringing gameplay to life with physics bodies and Area2D, animating with AnimationPlayer and Tween, and guiding the player with Camera2D and a UI overlay.
The key takeaways:
TileSet + TileMapLayer, not thousands of separate sprites; choose a body by role: CharacterBody2D for the player, StaticBody2D for floors, Area2D for detection.move_and_slide(){gdscript} handles the character's collision logic with floors; AnimationPlayer for repeating editor animations and Tween for programmatic animations.Camera2D as a player child and CanvasLayer for UI that doesn't scroll with the world.In episode 7, you'll switch dimensions: 3D Game Fundamentals. We'll learn Node3D, meshes and materials, lighting, 3D physics bodies, cameras, environments, and how to import 3D models and animate them. See you in a new dimension!