Your First Game¶
Build a complete 2D Gem Collector game from scratch using nodes, collision detection, audio, and Python scripting.
In this tutorial, you will build a complete mini-game: a player character navigating the screen to collect randomly spawning gems. When the player touches a gem:
- A sound effect plays.
- The score counter increments on the HUD.
- The gem relocates to a fresh random location on the screen.
You can follow every step either visually inside the Pite Editor or by directly editing project files. Use the content tabs in each section to follow your preferred approach!
Create the Project¶
Start by scaffolding a blank project from your terminal:
This creates a clean workspace with starter directories for assets/, scenes/, and scripts/.
Prepare the Assets¶
Get your sprite images and sound effects into your project:
- Launch the editor:
- In the Assets panel (bottom-left), click the button in the header (or right-click root folder and choose New Folder…), name it
sfx, and confirm. - Drag your
player.pngandgem.pngimage files from your operating system's file manager directly into theassetsfolder in the Assets panel. - Drag your
coin.wavaudio file from your OS file manager directly into thesfxfolder.
The editor automatically ingests the files and registers them in the asset manifest.
Text-First Workflow
Because Pite files are designed to be human-readable and Git-friendly (plain TOML for manifests and scenes, standard Python scripts, and standard media formats), you can comfortably build, inspect, and modify your entire game using command-line tools and your preferred text editor without needing the visual editor.
Create the audio directory from your terminal:
Copy your image and audio files into assets/ and sfx/:
assets/player.png: Sprite for the player character (e.g. 48×48 px).assets/gem.png: Sprite for the collectible gem (e.g. 32×32 px).sfx/coin.wav: Sound effect played when picking up a gem.
Tip
You can use any PNG images and WAV/OGG audio files you have on hand, or grab free public-domain assets from Kenney.
Project Manifest¶
Inspect the project settings and design resolution:
- In the editor menu bar, open Project > Project Settings….
- Confirm that Window Width is
1280and Window Height is720. - Optionally enter your name in the Author field.
- Click Save to write the configuration to
pite.toml.
Open pite.toml in your code editor to inspect the generated project metadata:
[project]
name = "gem-collector"
pite_version = "0.3.0"
main_scene = "res://scenes/main.ptscn"
python = "3.12"
author = "Your Name"
icon = ""
window_width = 1280
window_height = 720
The scaffolded defaults already configure window_width = 1280 and window_height = 720 as the target design resolution, which the engine scales uniformly to fit any monitor.
Build the Main Scene¶
Construct the scene hierarchy containing the camera, HUD label, player body, and gem trigger:
Double-click scenes/main.ptscn in the Assets panel to open it. The Scene Tree displays the root node Main.
- Add Camera:
- Select
Mainin the Scene Tree, click + (or right-clickMain> Add Child Node…), search forCamera2D, name itCamera, and click Create. - In the Inspector under
Node2Dsection, setpositiontoX: 640.0,Y: 360.0.
- Select
- Add Score Label:
- Select
Main, click +, chooseLabel, name itScoreLabel, and click Create. - In the Inspector under
Label, settexttoScore: 0,font_sizeto32.0, andcolorto#FACC15. UnderNode2D, setpositiontoX: 120.0,Y: 50.0.
- Select
-
Add Player:
- Select
Main, click +, chooseCharacterBody2D, name itPlayer. UnderNode2D, setpositiontoX: 640.0,Y: 360.0. - Select
Player, click +, add a childSprite2DnamedSprite. Dragassets/player.pngfrom the Assets panel into thetexturefield in the Inspector. -
Select
Player, click +, add a childCollisionShape2DnamedCollider. In the Inspector underCollisionShape2D, setshapetocapsuleandradiusto25.0,heightto35.0,positiontoX: 0,Y: 20.Info
This properties are based on our sample sprite for the player, you can continue configuring it to make it more similar to your player sprite.
- Select
-
Add Gem:
- Select
Main, click +, chooseArea2D, name itGem. UnderNode2D, setpositiontoX: 400.0,Y: 250.0. - Select
Gem, click +, add a childSprite2DnamedSprite. Dragassets/gem.pngfrom the Assets panel into itstexturefield. -
Select
Gem, click +, add a childCollisionShape2DnamedCollider. UnderCollisionShape2D, setshapeto"circle"andradiusto16.0.Info
This properties are based on our sample sprite for the gem, you can continue configuring it to make it more similar to your gem sprite.
- Select
Press Ctrl + S to save the scene!
When you create a blank project, scenes/main.ptscn starts with a single root Node2D named Main (lines 1–8). Open scenes/main.ptscn and expand the scene tree by adding the nodes for the camera, HUD label, player, and gem (highlighted below):
format_version = 1
root = "root"
[[node]]
id = "root"
type = "Node2D"
name = "Main"
[[node]]
id = "cam"
type = "Camera2D"
name = "Camera"
parent = "root"
[node.props]
position = [640.0, 360.0]
[[node]]
id = "score_label"
type = "Label"
name = "ScoreLabel"
parent = "root"
[node.props]
color = "#FACC15"
font_size = 32.0
position = [120.0, 50.0]
text = "Score: 0"
[[node]]
id = "player"
type = "CharacterBody2D"
name = "Player"
parent = "root"
[node.props]
position = [640.0, 360.0]
[node.script]
path = "res://scripts/player.py"
class = "Player"
[[node]]
id = "player_sprite"
type = "Sprite2D"
name = "Sprite"
parent = "player"
[node.props]
texture = "res://assets/player.png"
[[node]]
id = "player_collider"
type = "CollisionShape2D"
name = "Collider"
parent = "player"
[node.props]
shape = "circle"
radius = 24.0
[[node]]
id = "gem"
type = "Area2D"
name = "Gem"
parent = "root"
[node.props]
position = [400.0, 250.0]
[node.script]
path = "res://scripts/gem.py"
class = "Gem"
[[node]]
id = "gem_sprite"
type = "Sprite2D"
name = "Sprite"
parent = "gem"
[node.props]
texture = "res://assets/gem.png"
[[node]]
id = "gem_collider"
type = "CollisionShape2D"
name = "Collider"
parent = "gem"
[node.props]
shape = "circle"
radius = 16.0
Scene Breakdown¶
- Camera (
cam): Centers the game view at(640, 360), framing the entire 1280×720 world. - ScoreLabel (
score_label): ALabelnode positioned near the top-left to show the current score. - Player (
player): ACharacterBody2Dcontaining aSprite2Dgraphic and a circularCollisionShape2Dcollider, attached toscripts/player.py. - Gem (
gem): AnArea2Dtrigger zone containing its own sprite and collider, attached toscripts/gem.py.
Player Movement Script¶
Create the player script with smooth 8-directional movement:
import pite
class Player(pite.CharacterBody2D):
speed: float = 350.0
def _process(self, delta: float):
# Gather directional input
move_x = 0.0
move_y = 0.0
if pite.held.right or pite.held.d:
move_x += 1.0
if pite.held.left or pite.held.a:
move_x -= 1.0
if pite.held.down or pite.held.s:
move_y += 1.0
if pite.held.up or pite.held.w:
move_y -= 1.0
# Normalize diagonal movement speed
if move_x != 0.0 and move_y != 0.0:
move_x *= 0.7071
move_y *= 0.7071
# Apply movement scaled by frame delta
self.position.x += move_x * self.speed * delta
self.position.y += move_y * self.speed * delta
# Keep player within screen bounds
self.position.x = max(30.0, min(1250.0, self.position.x))
self.position.y = max(30.0, min(690.0, self.position.y))
Attaching the Script to the player¶
- In the Assets panel, select the
scriptsfolder and click + File (or right-clickscripts> New File…). - Select the Script (.py) template, name the file
player.py, and click Create. - Double-click
scripts/player.pyin the Assets panel to open it in the Code tab, and paste the code above. Save with Ctrl + S. - Switch back to the Viewport tab, select the
Playernode in the Scene Tree, and dragscripts/player.pyfrom the Assets panel onto thePlayernode to attach it.
Gem Collection Script¶
Create the gem script to handle collection events, sound playback, score tracking, and random relocation:
import random
import pite
class Gem(pite.Area2D):
score: int = 0
def _ready(self):
# Connect the built-in collision signal
self.body_entered.connect(self.on_body_entered)
def on_body_entered(self, body_id: str):
# Check if the colliding body is the player
if body_id == "player":
self.collect()
def collect(self):
# Play the pickup sound effect
pite.play("res://sfx/coin.wav")
# Increment score and update the HUD
self.score += 1
label = self.get_node("../ScoreLabel")
label.text = f"Score: {self.score}"
# Relocate the gem to a random position on screen
new_x = random.uniform(80.0, 1200.0)
new_y = random.uniform(80.0, 640.0)
self.position = (new_x, new_y)
Attaching the Script to the gem¶
- In the Assets panel under
scripts, click + File, choose Script (.py), name itgem.py, and click Create. - Double-click
scripts/gem.pyto open it in the Code tab, and paste the script above. Save with Ctrl + S. - In the Scene Tree, drag
scripts/gem.pyfrom the Assets panel onto theGemnode to attach it.
Test the Game¶
Now test your game in action:
Click the Play (▶) button in the top-right corner of the editor (or press Ctrl+R / F5).
Keep the mouse pointer inside the game viewport to send input:
- Move with WASD or Arrow keys.
- Touch the gem to collect it and hear the pickup sound effect.
- Watch your score counter increment and the gem teleport to a new spot!
Add a Countdown Timer¶
To turn the collector into a complete game, add a countdown timer that gives players 30 seconds to collect as many gems as possible.
Update the Scene¶
- Add TimeLabel:
- Select
Mainin the Scene Tree, click +, add aLabelnamedTimeLabel. - In the Inspector under
Label, settexttoTime: 30,font_sizeto28.0, andcolorto#FFFFFF. UnderNode2D, setpositiontoX: 1150.0,Y: 50.0.
- Select
- Add GameTimer:
- Select
Main, click +, add aTimernamedGameTimer. - In the Inspector under
Timer, setwait_timeto1.0.
- Select
- Press Ctrl+S to save!
Append the TimeLabel and GameTimer nodes to the end of scenes/main.ptscn (highlighted below):
# (append to the end of scenes/main.ptscn)
[[node]]
id = "time_label"
type = "Label"
name = "TimeLabel"
parent = "root"
[node.props]
color = "#FFFFFF"
font_size = 28.0
position = [1150.0, 50.0]
text = "Time: 30"
[[node]]
id = "game_timer"
type = "Timer"
name = "GameTimer"
parent = "root"
[node.props]
wait_time = 1.0
[node.script]
path = "res://scripts/game_timer.py"
class = "GameTimer"
Create the Timer Script¶
import pite
class GameTimer(pite.Timer):
seconds_left: int = 30
def _ready(self):
# Timer has a built-in timeout signal that loops every wait_time
self.timeout.connect(self.on_second_tick)
def on_second_tick(self):
if self.seconds_left > 0:
self.seconds_left -= 1
label = self.get_node("../TimeLabel")
label.text = f"Time: {self.seconds_left}"
if self.seconds_left == 0:
self.game_over()
def game_over(self):
label = self.get_node("../TimeLabel")
label.text = "Time's Up!"
label.color = "#EF4444"
# Stop player movement
player = self.get_node("../Player")
player.speed = 0.0
- In the Assets panel under
scripts, click + File, creategame_timer.py, and paste the script above. Save with Ctrl + S. - Drag
scripts/game_timer.pyfrom the Assets panel ontoGameTimerin the Scene Tree to attach it. - Press Play (
▶) to test your complete timed arcade game!
Export Your Game¶
Package your finished game for distribution:
The exported build is placed in dist/gem-collector-<platform>/ containing the standalone game binary, runtime, and all packed assets ready to share with friends.
What's Next?¶
Congratulations on finishing your first Pite game! Here are a few ways to expand it:
- Add level obstacles using
StaticBody2Dnodes or design tiled levels with Tilemaps. - Add animated coin frames using a sprite sheet atlas (see the Assets Guide).
- Use the visual Editor to arrange levels interactively.