Build a 2D Platformer in Godot

Build a playable 2D platformer with physics, tilemaps, coins, and a score HUD.

Introduction

30 Second Summary

Every video game you have ever played started as an empty screen. Someone had to figure out how to make a character appear, respond to button presses, and interact with a world built from scratch.

In this project, you will build a 2D platformer called "Coin Collector" in Godot 4.7 where a character runs, jumps across platforms, and collects coins that update a live score counter.

What You'll Build

You'll run your finished game, move your character across platforms with arrow keys, jump over gaps with the spacebar, and watch your score tick up every time you grab a coin.

By the end of this project, you'll have:

  • A player character that responds to keyboard input with smooth horizontal movement and gravity-based jumping.
  • A hand-painted level made of tiles where you design the platforms, gaps, and walls your character navigates.
  • A coin collection system where coins vanish on contact and a score counter on screen updates in real time.
  • Secret Mission: Add a patrolling enemy that resets the game when it catches you.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters. Taking a moment to commit to your goal makes the hands-on work ahead more focused.

Set Up Your Project and Player Scene

Every game starts with a blank canvas. Before you can make a character run and jump, you need a project to hold all your game files and a player scene that defines what your character is made of.

In this step, you will create a new Godot project, configure its display settings, and build the Player scene with the nodes that give your character a physical body and a visible sprite.

In this step, get ready to:
  • Create a new Godot project and configure its viewport size.
  • Build a Player scene with collision and sprite nodes.
  • Set up input actions for movement and jumping.
Create the CoinCollector project

The Project Manager is the first window you see when Godot opens. You will create a new project here and then configure its window size for a landscape platformer.

  • In the Project Manager, click the Create button in the top-left corner.
  • Type CoinCollector in the project name field.
  • Enable the Create Folder option so Godot creates a subfolder named after your project automatically.
  • Leave the renderer on its default setting.
  • Click Create to generate the project folder and open it in the editor.

The editor opens with an empty scene. Now you need to set the game window size for a landscape platformer.

  • Click Project in the top menu bar.
  • Select Project Settings from the dropdown.
  • In the left column, expand Display and click Window.
  • Set Viewport Width to 1152.
  • Set Viewport Height to 648.
  • Scroll down to the Stretch section.
  • Set Mode to canvas_items.
  • Close the Project Settings window.

Why these viewport settings?

The 1152x648 resolution gives you a 16:9 landscape ratio, which is the standard for side-scrolling platformers. Setting Stretch Mode to canvas_items ensures your game scales consistently on different screen sizes without distorting your pixel art or UI.

Build the Player scene

In Godot, a scene is a tree of nodes that you can save and reuse. Your Player scene needs three nodes: a physics body that interacts with gravity and platforms, a collision shape that defines the body's physical boundaries, and a sprite that makes the character visible on screen.

  • In the Scene dock on the left, click Other Node to open the Create New Node dialog.
  • Type CharacterBody2D in the search filter at the top.
  • Select CharacterBody2D from the list and click Create.
  • Double-click the new node's name in the Scene dock and rename it to Player.

The editor switches to the 2D workspace and you can see a Player node in the Scene dock. Next, add its child nodes.

  • With the Player node selected, click the + icon (Add Child Node) at the top of the Scene dock.
  • Search for CollisionShape2D and click Create.
  • In the Inspector dock on the right, find the Shape property (it shows <empty>).
  • Click the Shape dropdown and select New RectangleShape2D.
  • Click the shape resource that appeared to expand it. Set Size x to 48 and y to 48.

You now have a 48x48 pixel collision box. Time to add the sprite so your player is visible.

  • Select the Player node again in the Scene dock (not the CollisionShape2D).
  • Click the + icon (Add Child Node) again.
  • Search for Sprite2D and click Create.
  • In the FileSystem dock at the bottom-left, find icon.svg.
  • Drag icon.svg from the FileSystem dock onto the Texture property in the Inspector.

What is a CharacterBody2D?

A CharacterBody2D is Godot's physics node designed for characters you control directly. It handles collisions with floors, walls, and ceilings while letting you write the movement logic (gravity, jumping, walking) yourself.

The CollisionShape2D tells the engine the size and shape of your character's physical body. The Sprite2D is purely visual and draws the character texture on screen.

Your Scene dock should now show three nodes: Player (root), CollisionShape2D (child), and Sprite2D (child). The Godot icon appears in the viewport.

Save the scene and configure input actions

Before moving on, you need to save this scene and tell Godot which keyboard keys control your player. Input actions map physical keys to named actions your code can reference.

  • Press Ctrl+S to save the scene.
  • In the Save dialog, name the file player.tscn and save it in the project root.

Now set up the input actions your movement script will use in the next step.

  • Click Project in the top menu bar.
  • Select Project Settings.
  • Click the Input Map tab at the top of the Project Settings window.
  • In the Add New Action field at the top, type move_left and press Enter to add it.
  • Click the + button next to move_left to add a key binding.
  • Press the Left arrow key on your keyboard and click OK.
  • Repeat the process: add an action called move_right and bind it to the Right arrow key.
  • Add one more action called jump and bind it to the Space key.
  • Close the Project Settings window.

Why use input actions instead of key codes?

Input actions let you reference a name like move_left in your code instead of a specific key. This makes it easy to remap controls later or support gamepads without changing your scripts.

Check your work: in the Scene dock, you should see a Player scene with a CharacterBody2D root node named Player. It has two children: a CollisionShape2D (with a RectangleShape2D assigned) and a Sprite2D displaying the Godot icon. The scene is saved as player.tscn.

Don't see the Godot icon in the viewport?

Make sure you dragged icon.svg onto the Texture property in the Inspector while the Sprite2D node is selected. If the Texture still shows empty, try clicking the dropdown and selecting Load to browse for the file manually.

If you see a yellow warning triangle on the CollisionShape2D node, it means no shape resource is assigned. Click the node, find the Shape property in the Inspector, and select New RectangleShape2D.

help me fix my Player scene setup

Your project is set up and your Player scene is ready. Next up, you will write the movement script that brings this character to life with gravity, running, and jumping.

Make the Player Move and Jump

Your Player scene has a body, a collision shape, and a sprite. Right now though, it just sits there doing nothing.

A character that sits still is not a game. In this step, you'll write a GDScript that gives your player gravity, horizontal movement, and jumping so you can see physics in action immediately.

In this step, get ready to:
  • Attach a movement script to the Player node.
  • Create a temporary test scene with a floor.
  • Playtest movement, jumping, and gravity.
Attach a movement script to the Player

Scripts in Godot attach directly to nodes. They define how that node behaves every frame. Your Player needs a script that applies gravity each physics frame, reads keyboard input for movement, and calls the built-in move_and_slide() function to handle collisions.

  • In the Scene dock, make sure your player.tscn scene is open with the Player node visible.
  • Right-click the Player node in the Scene dock and select Attach Script.
  • In the Attach Node Script window, change the Template field from Node: Default to Object: Empty.
  • Leave the Path as the default (player.gd) and click Create.

The Script workspace opens with an empty file. Select all the placeholder content and delete it, then paste the following movement code:

extends CharacterBody2D

const SPEED = 300.0
const JUMP_VELOCITY = -400.0

var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(delta):
	# Apply gravity.
	if not is_on_floor():
		velocity.y += gravity * delta

	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	# Handle horizontal movement.
	var direction = Input.get_axis("move_left", "move_right")
	if direction:
		velocity.x = direction * SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)

	move_and_slide()

What does this code do?

  • const SPEED = 300.0 and const JUMP_VELOCITY = -400.0 set how fast the player moves horizontally and how high they jump. Negative Y means upward in Godot's coordinate system.
  • var gravity pulls the project's default gravity value (980 pixels/sec²) so the player falls naturally.
  • _physics_process(delta) runs every physics frame (60 times per second by default). All movement code lives here.
  • Input.get_axis("move_left", "move_right") returns a value between -1 and 1 based on which arrow key you press.
  • move_and_slide() applies the velocity, handles collisions with walls and floors, and updates is_on_floor() for the next frame.
  • Save the script by pressing Ctrl+S.
  • Switch back to the 2D view by clicking 2D at the top of the editor. In the Scene dock, you should see a small scroll icon next to the Player node, confirming the script is attached.

Don't see the script icon?

  • Make sure you right-clicked the Player root node (not one of its children like CollisionShape2D or Sprite2D).
  • Check the FileSystem dock at the bottom-left. You should see player.gd listed there. If it is missing, repeat the attach step.
  • help me attach a script to my Player node in Godot

✔️ Awesome, I've got everything!

Great. Make sure you've saved player.gd before moving on.

ⓧ I'd like to double check the full code

Here is the complete player.gd file. Confirm yours matches exactly:

extends CharacterBody2D

const SPEED = 300.0
const JUMP_VELOCITY = -400.0

var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(delta):
	# Apply gravity.
	if not is_on_floor():
		velocity.y += gravity * delta

	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	# Handle horizontal movement.
	var direction = Input.get_axis("move_left", "move_right")
	if direction:
		velocity.x = direction * SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)

	move_and_slide()
Create a test scene and playtest

The script is attached, but you cannot press play yet. Godot needs a scene set as the main scene, and the Player needs a floor to land on. You'll create a quick temporary scene for testing.

  • Create a new scene by clicking Scene in the top menu bar, then New Scene.
  • In the Scene dock, click Other Node, search for Node2D, and click Create.
  • Instance the Player scene as a child: click the chain-link icon (Instance Child Scene) at the top of the Scene dock, select player.tscn, and click Open.
  • Position the Player instance near the top of the viewport so it has room to fall. Set its Position in the Inspector to (200, 100).

Now add a floor for the Player to land on.

  • Select the Node2D root in the Scene dock.
  • Click the + icon (Add Child Node), search for StaticBody2D, and click Create.
  • Select the new StaticBody2D node. Click the + icon again, search for CollisionShape2D, and click Create.
  • Select the CollisionShape2D you just added. In the Inspector, click the Shape dropdown and choose New WorldBoundaryShape2D. This creates an infinite horizontal floor.
  • Save the scene as test_scene.tscn by pressing Ctrl+S.

Why a WorldBoundaryShape2D?

A WorldBoundaryShape2D stretches infinitely in both horizontal directions. It is perfect for a quick test floor because you do not need to set a size or position. Your player cannot fall through it.

  • Set this scene as the main scene by clicking Project in the top menu bar, then Project Settings.
  • In the General tab, search for main_scene in the search bar at the top.
  • Click the folder icon next to the Main Scene field, select test_scene.tscn, and click Open.
  • Close the Project Settings window.

Before you press play, what do you think will happen to the Godot icon when the scene starts? Will it stay still, fall, or fly upward?

  • Press F5 to run the game.

The player falls due to gravity and lands on the invisible floor. Press the Left and Right arrow keys to move horizontally. Press Space to jump. The player only jumps when standing on the floor.

Player falls forever or doesn't move?

  • If the player falls through the floor, make sure the CollisionShape2D under StaticBody2D has a WorldBoundaryShape2D assigned (not left empty).
  • If arrow keys do nothing, open Project > Project Settings > Input Map and confirm move_left, move_right, and jump actions exist with the correct keys bound.
  • If you see a red error in the Output panel about the script, double-check that player.gd uses tabs (not spaces) for indentation. GDScript is whitespace-sensitive.
  • help me debug my Player movement script in Godot

Your player has gravity, movement, and jumping. Next up, you'll replace that invisible test floor with a real level made of painted tiles.

Build a Level with TileMapLayer

Your player can run, jump, and land on a flat surface. That proves the movement code works.

A single flat floor is not a level. In this step, you'll use TileMapLayer to paint actual platforms, walls, and gaps that turn your project into a playable game world.

In this step, get ready to:
  • Create a World scene with a TileMapLayer node and a TileSet resource.
  • Configure tile collision so the player can stand on painted tiles.
  • Paint a level with platforms and gaps, then test it with the Player.
Create the World scene and configure TileMapLayer

The World scene is the container for everything the player sees and interacts with. It needs a TileMapLayer node to hold the tile grid you'll paint.

  • In the Godot editor, click Scene in the top menu bar.
  • Click New Scene.
  • In the Create Root Node dialog, select Other Node and search for Node2D. Select it and click Create.
  • In the Scene dock (top-left), double-click the root node and rename it to World.
  • Right-click the World node and select Add Child Node.
  • Search for TileMapLayer and click Create.

You should see TileMapLayer appear as a child of World in the Scene dock.

  • Select the TileMapLayer node.
  • In the Inspector (right panel), find the Tile Set property. Click <empty> and choose New TileSet.
  • Click the new TileSet resource to expand it in the Inspector.
  • Set Tile Size x to 64 and y to 64.
  • Scroll down to Physics Layers and click Add Element.

What is a TileSet and why add a Physics Layer?

A TileSet is a library of tiles that a TileMapLayer can paint onto the grid. Each tile can carry collision, navigation, or other data.

Adding a Physics Layer tells Godot that tiles in this TileSet can have collision shapes. Without it, the player would fall straight through any tile you paint.

  • Save the scene by pressing Ctrl+S. Name it world.tscn and save it in the project root.

You now have an empty World scene with a TileMapLayer ready to receive tiles.

Set up tile collision in the TileSet editor

The TileMapLayer knows the tile size, but it has no actual tiles to paint with yet. You need to add a tile image to the TileSet and give it a collision shape so the player can stand on it.

  • Make sure the TileMapLayer node is selected in the Scene dock.
  • Look at the bottom of the editor. Click the TileSet tab to open the TileSet panel.
  • From the FileSystem dock (bottom-left), drag icon.svg onto the TileSet panel.
  • A dialog asks whether to create tiles automatically. Click Yes.

You should see icon.svg appear as an atlas in the TileSet panel with tile regions drawn on it.

Why use icon.svg for tiles?

The built-in icon.svg is a placeholder so you can learn the tilemap workflow without downloading external art. The tile will look like the Godot logo. You can swap it for a proper tileset image later.

Now you need to give this tile a collision shape so the player's CharacterBody2D treats it as solid ground.

  • In the TileSet panel, click the tile to select it.
  • Switch to the Paint properties mode by clicking the paintbrush icon in the TileSet panel toolbar.
  • In the left column, expand Physics Layer 0 and select it.
  • With the tile selected in the right column, press F on your keyboard. This creates a full rectangle collision shape that covers the entire tile.

You should see a blue rectangle overlay appear on the tile, confirming the collision shape is set.

Shortcut for full-tile collision

Pressing F while the TileSet editor is focused creates a rectangle collision shape instantly. This is the fastest way to make tiles solid.

No blue rectangle appearing?

Make sure you selected Physics Layer 0 in the left column before pressing F. If the Physics Layer does not exist, go back to the TileSet resource in the Inspector and click Add Element under Physics Layers.

Help me set up tile collision in the TileSet editor.

Paint your level and test

With a solid tile ready, you can now paint an actual level for the player to explore.

  • With the TileMapLayer still selected, click the TileMap tab at the bottom of the editor (next to the TileSet tab).
  • In the TileMap panel, click your tile to select it as the painting brush.
  • In the 2D viewport, left-click and drag to paint a row of tiles along the bottom of the screen as ground.

Paint straight lines quickly

Hold Shift before clicking and dragging to paint in a straight line. Hold Ctrl+Shift to paint a filled rectangle. Right-click erases tiles.

  • Paint several floating platforms at different heights above the ground. Leave gaps between them so the player has to jump.
  • Leave at least 2-3 gaps in the ground row so the player can fall.

Your 2D viewport should now show a ground row with gaps and platforms scattered above it at different heights.

  • Now add the Player to this scene. In the FileSystem dock, drag player.tscn into the 2D viewport (or right-click the World node, select Instantiate Child Scene, and pick player.tscn).
  • Position the Player above the ground tiles so it does not spawn inside a tile. Use the move tool in the 2D viewport to drag it up.
  • Set this scene as the main scene. Click Project in the top menu, then Project Settings.
  • Under Application > Run, set Main Scene to res://world.tscn. Close the settings.

Before you run, predict what you expect to happen. Will the player land on the painted tiles, or fall through them?

  • Press F5 to run the game.

The player falls due to gravity and lands on the ground tiles. Pressing the Left and Right arrow keys moves the character across the platforms. Pressing Space makes the character jump. The player does not fall through any painted tile.

Player falls through the tiles?

This means the tiles do not have collision. Go back to the TileSet tab at the bottom, select the tile, open Physics Layer 0, and press F to add the collision rectangle. Make sure you also added a Physics Layer under the TileSet resource in the Inspector.

Help me fix my tiles so the player doesn't fall through them.

Your level is complete. You have a painted world with solid platforms and a player that can navigate them. Next up, you'll add coins to collect so there's a reason to explore the level.

Add Collectible Coins

Your player can run and jump across platforms. That feels good for about 30 seconds. Then there is no reason to keep playing.

Coins fix that. They give the player a goal to chase across the level. Building them also teaches you how game objects talk to each other in Godot through a system called signals.

In this step, get ready to:
  • Create a Coin scene with an Area2D node that detects the player.
  • Write a script that removes the coin when touched.
  • Place coins around the level and playtest collection.
Create the Coin scene

Each coin is its own reusable scene. Instead of a CharacterBody2D (which is for things that move with physics), coins use an Area2D node. An Area2D detects when another physics body enters its space without blocking movement.

  • Create a new scene by clicking **Scene** in the top menu bar, then **New Scene**.
  • Click **Other Node** in the scene creation dialog.
  • Search for Area2D and select it to create the root node.
  • Double-click the root node name in the Scene dock and rename it to Coin.

Now add the child nodes that give the coin a collision area and a visual.

  • Right-click the Coin node and select **Add Child Node**.
  • Search for CollisionShape2D and add it.
  • Select the new CollisionShape2D node in the Scene dock.
  • In the Inspector on the right, click the **Shape** dropdown and select **New CircleShape2D**.
  • Click the CircleShape2D resource to expand it, then set **Radius** to 16.
  • Right-click the Coin node again and select **Add Child Node**.
  • Search for Sprite2D and add it.
  • Select the Sprite2D node.
  • In the FileSystem dock at the bottom-left, find icon.svg and drag it onto the **Texture** property in the Inspector.
  • In the Inspector, expand the **Transform** section and set **Scale** to 0.3 for both x and y.

This makes the Godot icon appear as a small collectible instead of full-size.

  • Save the scene by pressing **Ctrl+S** and name it coin.tscn.

Your Scene dock should show three nodes: Coin (Area2D) at the top, with CollisionShape2D and Sprite2D as children.

Seeing a warning icon on the Coin node?

If you see a yellow warning triangle on the Area2D node, it means the CollisionShape2D does not have a shape assigned yet. Click the CollisionShape2D, check the Inspector, and make sure the Shape property has a CircleShape2D (not empty).

If the warning is on the CollisionShape2D itself, it may be that the radius is set to 0. Set it to 16.

help me fix the warning on my Coin node

Write the coin collection script

The coin needs a script that reacts when the player touches it. In Godot, this reaction happens through signals. When a physics body enters the Area2D, the node fires a body_entered signal. Your script will listen for that signal and remove the coin.

  • Select the Coin root node in the Scene dock.
  • Click the **Attach Script** button (scroll icon) at the top of the Scene dock.
  • Accept the defaults and click **Create** to generate coin.gd.
  • Delete the template code that Godot generates, then paste the following script:
extends Area2D

# Define a custom signal that other nodes can listen for.
signal collected

func _on_body_entered(body):
	# Fire the custom signal (used later for scoring).
	collected.emit()
	# Remove this coin from the scene.
	queue_free()

What does this code do?

  • signal collected defines a custom signal. Think of it as a broadcast announcement that other nodes can subscribe to. You will connect this to a score counter in the next step.
  • _on_body_entered(body) is the function that runs when a physics body (like your player) enters the coin's area.
  • collected.emit() fires the custom signal so any listening node knows a coin was grabbed.
  • queue_free() removes the coin from the scene entirely. It vanishes the instant the player touches it.

Now connect the Area2D's built-in body_entered signal to the function you just wrote.

  • Select the Coin node in the Scene dock.
  • Switch to the **Node** dock (the tab next to the Inspector on the right side).
  • In the **Signals** section, find body_entered(body: Node2D) under the Area2D signals.
  • Double-click body_entered to open the connection dialog.
  • The **Receiver Method** should already show _on_body_entered. Click **Connect**.

You will see a green connection icon appear next to _on_body_entered in the script editor. This confirms the signal is wired up.

  • Save the scene by pressing **Ctrl+S**.

What are signals in Godot?

Signals are Godot's way of letting nodes communicate without tightly coupling them together. One node emits a signal ("something happened!"), and any node connected to that signal can react. This keeps your code modular.

The body_entered signal is built into Area2D. Your custom collected signal is one you invented. Both work the same way.

Don't see _on_body_entered in the Receiver Method?

If the connection dialog does not auto-fill _on_body_entered, make sure the script is attached to the Coin node (not a child node). You can type _on_body_entered manually in the Receiver Method field.

help me connect the body_entered signal to my coin script

✔️ Awesome, I've got everything!

Great. Make sure you have saved coin.tscn with the script attached and signal connected.

ⓧ I'd like to double check the full code

Here is the complete coin.gd file:

extends Area2D

signal collected

func _on_body_entered(body):
	collected.emit()
	queue_free()
Place coins in the level and playtest

The Coin scene is ready. Now you need to drop several coins into your World scene so the player has something to collect.

  • Switch back to the world.tscn scene tab at the top of the editor.
  • In the FileSystem dock, find coin.tscn.
  • Drag coin.tscn from the FileSystem dock into the 2D viewport to instance it.
  • Position the coin on top of a platform where the player can reach it.
  • Repeat this 3-4 more times, placing coins on different platforms and in gaps between them.

Spread coins across the level

Place some coins directly on platforms (easy to grab) and some floating between platforms (requires a well-timed jump). This creates a mix of easy and challenging collectibles.

Before you run this, what do you think will happen when the player character touches a coin?

  • Press **F5** to run the game.
  • Use the arrow keys to move your player toward a coin and walk into it.

The coin disappears the instant your player touches it. Each coin you run into vanishes from the level. This is queue_free() in action, removing the node from the scene tree.

Coins not disappearing?

  • Make sure the body_entered signal is connected. Select a Coin instance, open the **Node** dock, and check that body_entered shows a connection icon.
  • Confirm your Coin scene has a CollisionShape2D with a CircleShape2D assigned. Without a shape, the Area2D cannot detect anything.
  • Check that the Player's CollisionShape2D is also present and has a shape assigned. Both sides need collision shapes for detection to work.

help me figure out why my coins are not disappearing when the player touches them

Your level now has collectibles that vanish on contact. The collected signal is already firing each time. In the next step, you will connect that signal to a score counter so the player gets visible feedback for each coin they grab.

Add a Score Display (HUD)

Your coins vanish on contact, but the player has no idea how many they have collected. Without feedback, there is no sense of progress or accomplishment.

A score counter closes that loop. In this step you will add a CanvasLayer with a Label that stays fixed on screen, then wire up the coins' collected signal so the label updates every time a coin is grabbed.

In this step, get ready to:
  • Add a CanvasLayer and Label node to display the score.
  • Write a World script that tracks and displays the score.
  • Connect each coin's signal to the World script so the score updates in real time.
Add a CanvasLayer and Label to the World scene

A CanvasLayer draws its children on a separate rendering layer above the game. This means the score text stays pinned to the screen even if you add a camera later.

  • Switch back to the World scene tab in the Godot editor.
  • In the Scene dock, right-click the World root node and select Add Child Node.
  • Search for CanvasLayer and click Create.
  • Right-click the new CanvasLayer node and select Add Child Node again.
  • Search for Label and click Create.
  • Select the new Label node. In the Inspector on the right, set the Text field to Score: 0.
  • Under Control > Layout/Transform in the Inspector, set Position to x: 20, y: 20.
  • Expand Theme Overrides > Font Sizes in the Inspector, check the box next to Font Size, and set the value to 24.
  • Press Ctrl+S to save the scene, then press F5 to run the game.

You should see "Score: 0" in the top-left corner of the game window, staying fixed even as your player moves around.

Don't see the label?

Make sure the Label is a child of CanvasLayer, not of the World root directly. If it is a child of World, the text can scroll off screen.

Also check that the Text field is not empty. An empty Label is invisible.

Why can't I see my score label on screen?

Write the World script and connect coin signals

The label is showing a static number right now. You need a script on the World node that listens for coin collection events and updates the text.

  • In the Scene dock, select the World root node.
  • Click the Attach Script button (scroll icon) at the top of the Scene dock.
  • Leave the defaults and click Create to create world.gd.
  • Replace the template code with the following script:
extends Node2D

# Track the player's score
var score = 0

# Get a reference to the Label node inside CanvasLayer
@onready var score_label = $CanvasLayer/Label

# Called whenever a coin emits its collected signal
func _on_coin_collected():
	score += 1
	score_label.text = "Score: " + str(score)

What does this code do?

  • var score = 0 stores the running total of collected coins.
  • @onready var score_label = $CanvasLayer/Label grabs the Label node once the scene is ready. The $ shorthand is equivalent to get_node().
  • _on_coin_collected() adds 1 to the score and rebuilds the label string with str(score) so it displays the new total.

Now you need to connect each Coin instance's collected signal to this function. In the previous step, each coin already emits collected when touched. You just need to tell the World node to listen for it.

  • In the Scene dock, select your first Coin instance.
  • On the right side of the editor, click the tab named Node (next to the Inspector).
  • You will see the collected signal listed. Double-click it to open the connection window.
  • In the connection window, select the World root node as the receiver.
  • In the Receiver Method field at the bottom, type _on_coin_collected (replacing any auto-generated name).
  • Click Connect.
  • Repeat this process for every other Coin instance in your World scene. Select each Coin, double-click its collected signal, choose the World node, type _on_coin_collected, and click Connect.

Why connect each coin individually?

Each Coin instance is a separate node in the scene tree. The editor connects signals per-instance, so you need to wire each one to the same _on_coin_collected function on the World node. They all call the same function, but the connection must be made for each coin.

  • Save the scene with Ctrl+S.

Before you press F5, predict what will happen when you collect a coin. Will the score update to 1, or will something go wrong?

  • Press F5 to run the game.
  • Move your player into a coin.

The coin disappears and the label updates to "Score: 1". Collect another coin and it becomes "Score: 2". Each coin increments the counter by one.

Score stays at 0?

If the coins disappear but the score does not update, the signal is not connected. Select a Coin in the Scene dock and check the Node tab. You should see a green connection icon next to collected pointing to World._on_coin_collected. If nothing is listed, reconnect it.

Also check that the receiver method name is exactly _on_coin_collected (no typos, no extra numbers). The name must match the function in world.gd.

Help me debug why my coin signals are not updating the score.

✔️ Awesome, I've got everything!

Great. Make sure you have saved world.gd and the World scene.

ⓧ I'd like to double check the full code

Here is the complete world.gd file. Compare it with yours:

extends Node2D

var score = 0

@onready var score_label = $CanvasLayer/Label

func _on_coin_collected():
	score += 1
	score_label.text = "Score: " + str(score)

Confirm that coin.gd has not changed from the previous step:

extends Area2D

signal collected

func _on_body_entered(body):
	collected.emit()
	queue_free()

Secret mission

Add a Patrolling Enemy

Your platformer has coins to collect, but no danger to avoid. Can you add an enemy that patrols back and forth on a platform and resets the game when it catches you? This combines movement patterns, timers, and collision detection into one challenge.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • The CoinCollector project folder on your local machine, containing scene files (.tscn) and scripts (.gd).

Keep everything running

No action needed. Choose this if you're still actively building or want to extend the game further.

  • Your project stays on your machine exactly as you left it.
  • There are no running processes or services to worry about.
  • Open the project anytime by launching Godot and selecting CoinCollector from the Project Manager.

Pause - I'll come back to this later

Since this project is local files only, there's nothing to pause or shut down. Your project is already idle when Godot is closed.

  • Close the Godot editor if it's still open.
  • Your project folder and all files remain untouched on disk until you reopen them.

Delete - I don't want to use this again

Remove the entire project folder to free up disk space. This permanently deletes all your scene files, scripts, and assets.

  • Close the Godot editor if it's still open.
  • Open a terminal and navigate to the parent folder where you created your project.
  • Delete the project folder by running:
rm -rf CoinCollector
  • Confirm the folder is gone by running:
ls

You should no longer see CoinCollector in the output.

That's a Wrap!

That's a Wrap!

Nice work. You just built a complete 2D platformer from scratch in Godot 4.7 using GDScript. Your character runs, jumps, collects coins, and the score updates in real time.

You've learned how to:

  • Created a player character with gravity, horizontal movement, and jumping using CharacterBody2D and move_and_slide().
  • Painted a tile-based level with platforms using TileMapLayer and a TileSet with physics collision.
  • Implemented collectible coins and a live score counter using Area2D and custom signals.
  • Secret Mission: Added a patrolling enemy that resets the game on contact, combining movement logic with collision detection.

Ready to quiz yourself?