Free Ebook cover Godot 4 for Beginners: Build a Small 2D Game from Scratch

Godot 4 for Beginners: Build a Small 2D Game from Scratch

New course

10 pages

Polish, Testing, and Exporting a Small 2D Godot 4 Game

Capítulo 10

Estimated reading time: 9 minutes

+ Exercise

Final Polish: Audio, Camera Feel, and Particles

“Polish” is everything that makes a small game feel responsive and satisfying: sound feedback on actions, subtle camera motion, and small visual bursts (dust, sparks, pickups). None of these change core mechanics, but they strongly improve perceived quality.

Sound Effects and Music with AudioStreamPlayer

In Godot 4, AudioStreamPlayer (2D/3D variants exist) plays an audio stream (WAV/OGG). For a 2D game, you’ll typically use:

  • AudioStreamPlayer for UI and global sounds/music
  • AudioStreamPlayer2D for positional sounds in the world (optional for small projects)

Recommended audio node layout

Create a small audio “hub” so you don’t duplicate audio logic across scenes.

  • Add an autoload singleton scene/script (e.g., Audio.gd) or a dedicated node in your main scene.
  • Inside it, add: MusicPlayer (AudioStreamPlayer) and SfxPlayer (AudioStreamPlayer). For overlapping SFX, use multiple players or a small pool.

Example: Audio singleton script (autoload as Audio):

extends Node

@onready var music: AudioStreamPlayer = $MusicPlayer
@onready var sfx: AudioStreamPlayer = $SfxPlayer

func play_music(stream: AudioStream, volume_db := -8.0) -> void:
	if music.stream == stream and music.playing:
		return
	music.stream = stream
	music.volume_db = volume_db
	music.play()

func stop_music() -> void:
	music.stop()

func play_sfx(stream: AudioStream, volume_db := -6.0, pitch := 1.0) -> void:
	sfx.stream = stream
	sfx.volume_db = volume_db
	sfx.pitch_scale = pitch
	sfx.play()

Step-by-step: wiring common sounds

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

  • Import audio files (drag into res://audio/). Prefer .ogg for music, .wav for short SFX.
  • Create two buses in Audio panel: Music and SFX. Route players accordingly (set each player’s bus property).
  • Call SFX at key moments (jump, hit, pickup, button click). Add slight pitch variation to reduce repetition.

Example: play jump sound with variation (from Player script):

func _on_jump() -> void:
	var pitch := randf_range(0.95, 1.05)
	Audio.play_sfx(preload("res://audio/sfx_jump.wav"), -8.0, pitch)

Music transitions: keep it simple—start music when entering gameplay, stop or switch on game over/victory. If you want smooth fades, animate volume_db with a Tween.

Camera2D: Follow and Simple Screen Shake

A camera that follows smoothly and reacts to impacts makes movement feel better. In Godot 4, use Camera2D as a child of the player (common) or as a separate node that tracks the player (more flexible).

Step-by-step: basic follow camera

  • Add Camera2D to your Player scene (or main gameplay scene).
  • Enable Enabled (or call make_current() in code).
  • Turn on Position Smoothing and set Smoothing Speed (start around 5–10).
  • Optional: set Limit Left/Right/Top/Bottom to keep the camera within the level bounds.

Code option (if you prefer explicit control):

func _ready() -> void:
	$Camera2D.make_current()
	$Camera2D.position_smoothing_enabled = true
	$Camera2D.position_smoothing_speed = 8.0

Step-by-step: lightweight screen shake

Screen shake is usually a brief random offset applied to the camera when something impactful happens (damage, explosion, landing). Keep it subtle.

Camera2D shake script (attach to Camera2D):

extends Camera2D

var shake_time := 0.0
var shake_strength := 0.0

func shake(duration := 0.12, strength := 6.0) -> void:
	shake_time = max(shake_time, duration)
	shake_strength = max(shake_strength, strength)

func _process(delta: float) -> void:
	if shake_time > 0.0:
		shake_time -= delta
		offset = Vector2(
			randf_range(-shake_strength, shake_strength),
			randf_range(-shake_strength, shake_strength)
		)
	else:
		offset = Vector2.ZERO

Triggering shake: call $Camera2D.shake() when the player takes damage or when a heavy enemy hits the ground.

Particle Effects: Quick Feedback with GPUParticles2D

Particles add “juice” without complex animation. In Godot 4, GPUParticles2D is efficient and flexible. Use it for dust puffs, spark hits, and pickup bursts.

Step-by-step: dust puff on landing

  • Create a new scene LandingDust.tscn with a root Node2D.
  • Add GPUParticles2D as a child.
  • In the particle node: set One Shot = true, Emitting = false, Lifetime ≈ 0.3–0.6.
  • Assign a simple texture (small circle/smoke sprite) and tune: Amount, Initial Velocity, Gravity, Scale, and Color Ramp (fade out).

Spawn particles from code (Player script example):

@export var landing_dust_scene: PackedScene

func spawn_landing_dust(global_pos: Vector2) -> void:
	if landing_dust_scene == null:
		return
	var dust := landing_dust_scene.instantiate()
	get_tree().current_scene.add_child(dust)
	dust.global_position = global_pos
	var p := dust.get_node("GPUParticles2D") as GPUParticles2D
	p.emitting = true
	# Optional: queue_free after lifetime
	dust.queue_free.call_deferred()

Practical tip: for one-shot particles, you can free them after a short timer instead of immediately. If you want a clean approach, add a Timer node in the particle scene and free on timeout.

Lightweight Testing Checklist (Before You Export)

Testing for a small game is about catching the “annoying” issues: inputs that stick, collisions that fail, UI that breaks on different resolutions, and scene transitions that leave the game in a bad state.

1) Input edge cases

  • Simultaneous inputs: hold left+right, up+down, jump+attack, and confirm behavior is deterministic.
  • Rapid tapping: spam jump/attack; ensure no unintended double triggers or stuck states.
  • Focus loss: alt-tab during gameplay; return and verify input doesn’t remain “pressed.”
  • Rebinding (if supported): verify new bindings work in gameplay and menus.

Quick test script idea: add a debug overlay that prints key action strengths to verify they return to 0 when released.

2) Collision validation

  • Wall/ground edges: walk off ledges, jump into corners, slide along walls; look for snagging or jitter.
  • One-way platforms (if used): ensure you can jump up through them and land reliably.
  • Enemy/player hitboxes: confirm damage triggers once per hit (or at intended rate), not multiple times per frame.
  • Out-of-bounds: falling off the level should reset/respawn cleanly without soft-locking.

Debug tip: temporarily enable collision shape visibility in the editor/game to inspect overlaps and sizes.

3) UI scaling and readability

  • Different window sizes: test 1280×720, 1920×1080, ultrawide if possible.
  • Minimum size: shrink the window; ensure HUD doesn’t overlap or go off-screen.
  • Safe margins: verify important UI isn’t too close to edges.
  • Font sizes: ensure text remains readable and doesn’t clip.

Practical check: open Project Settings → Display and confirm your stretch settings match your intended approach (you’re not changing them here—just verifying the game behaves as expected).

4) Scene transitions and game flow

  • Restart level: verify player state resets (health, score, inventory, timers).
  • Main menu → gameplay → game over → restart: run the full loop multiple times to catch duplicated nodes, audio stacking, or lingering timers.
  • Pause/resume: confirm audio and animations behave correctly and inputs don’t “buffer” unexpectedly.
  • Memory/leaks check: repeatedly change scenes and watch for performance degradation (a sign something isn’t freed).
AreaWhat to look forCommon fix
AudioMusic overlaps after scene changeCentralize music in a singleton; stop/switch on transitions
ParticlesParticles accumulate and slow downUse one-shot and free instances after lifetime
CameraShake too strong / nauseaReduce strength and duration; avoid constant shake
UIHUD shifts off-screenAnchors/containers; test multiple resolutions

Exporting for Desktop (Windows, macOS, Linux)

Exporting turns your project into a runnable build. The key tasks are: installing export templates, creating export presets, setting icons/metadata, and producing a release build.

Step-by-step: install export templates

  • In Godot: Editor → Manage Export Templates.
  • Install the version matching your Godot editor version.

Step-by-step: create export presets

  • Project → Export.
  • Add presets for the desktop platforms you want (Windows, macOS, Linux).
  • For each preset, set an export path (e.g., builds/windows/MyGame.exe).

Icons and app identity

  • Project icon: set in Project Settings (application icon used by the project and some exports).
  • Platform icon: in each export preset, set platform-specific icons if available.
  • Name/version: set application name and version in Project Settings so builds are labeled correctly.

Practical icon guidance: prepare a square PNG (commonly 256×256 or higher). Some platforms generate multiple sizes from it; others may require specific formats—follow the preset’s fields and warnings.

Release builds vs debug builds

  • Debug: larger, includes debugging features; good for testing on another machine.
  • Release: optimized and intended for sharing.

In the Export window, choose the release option (and disable debug features as appropriate). If your game uses logs or debug overlays, ensure they’re disabled in release.

Export sanity checks (desktop)

  • Export to a clean folder (not inside your project’s res://).
  • Run the exported build on your machine.
  • If possible, run it on a second machine/user account to catch missing files and permission issues.
  • Verify audio plays, save data (if any) works, and window/fullscreen options behave.

Final Deliverable Checklist (Ready to Share)

Game completeness

  • Start-to-finish loop works: menu → play → win/lose → restart/quit.
  • Difficulty feels fair: no unavoidable hits, no soft-locks.
  • Audio: music starts/stops correctly; SFX are balanced (not too loud).
  • Camera: follow is smooth; shake is subtle and only on impactful events.
  • Particles: used sparingly; no performance drops after long play sessions.

Project organization (final pass)

  • Assets are in clear folders (audio, sprites, particles, scenes, scripts).
  • Unused test scenes/assets removed or moved to a clearly labeled dev/ folder.
  • Node names are readable (no Node2D2, Sprite3 in final scenes).
  • Exported variables have sensible defaults; no missing references in the Inspector.

Quality checks

  • Input edge cases tested (simultaneous, rapid tapping, focus loss).
  • Collision edge cases tested (corners, slopes/edges, repeated hits).
  • UI tested at multiple resolutions and aspect ratios.
  • Scene transitions tested repeatedly (no duplicated audio, no lingering timers).

Export package checklist

  • Export templates installed for your Godot version.
  • Export presets created for target desktop platforms.
  • Icons set (project + platform preset where applicable).
  • Release build exported to builds/ folder with versioned naming (e.g., MyGame_1.0.0_Windows).
  • Build tested by launching the exported executable/app.
  • Optional: include a small README.txt beside the build with controls and troubleshooting (kept outside the game window).

Now answer the exercise about the content:

When you notice background music overlapping after changing scenes in a small Godot 4 game, what is the most appropriate fix?

You are right! Congratulations, now go to the next page

You missed! Try again.

Music overlap after scene changes usually happens when multiple players keep running. A centralized audio hub can manage a single music player and stop or switch tracks during transitions.

Download the app to earn free Certification and listen to the courses in the background, even with the screen off.