You have a player fantasy statement. You have a design concept. You understand what games are and what game designers do. You are full of ideas and ready to build something.
In This Chapter
- 3.1 Why Godot?
- 3.2 Installing Godot 4.x
- 3.3 The Godot Interface --- A Guided Tour
- 3.4 Nodes and Scenes --- The Mental Model
- 3.5 Your First Scene --- Something on Screen
- 3.6 GDScript Fundamentals --- Learning by Building
- 3.7 Building a Moving Character
- 3.8 Input Handling in Depth
- 3.9 Signals --- Nodes Talking to Each Other
- 3.10 Paper Prototyping --- The Tool You're Underestimating
- 3.11 Design Documents --- Writing It Down Before You Build It
- 3.12 Flowcharts, Spreadsheets, and Other Thinking Tools
- 3.13 Version Control --- A Brief but Non-Negotiable Mention
- 3.14 Additional GDScript Patterns You'll Need
- 3.15 Debugging --- Your New Best Skill
- 3.16 Progressive Project --- Your First Godot Scene
- 3.17 What You Learned (and What's Next)
Chapter 3: Setting Up Your Design Laboratory --- Godot Engine, Prototyping, and the Tools of the Trade
You have a player fantasy statement. You have a design concept. You understand what games are and what game designers do. You are full of ideas and ready to build something.
You cannot build it yet.
Not because you lack talent or vision. Because you lack tools. A carpenter without a workbench is just a person thinking about furniture. A game designer without tools is just a person thinking about games. And thinking about games, while pleasant, does not produce games.
This chapter is about setting up your workbench. We are going to cover three categories of tools:
- Digital prototyping tools --- specifically Godot Engine, which you will use for the rest of this book
- Analog prototyping tools --- index cards, paper, dice, and the surprisingly powerful technology of "just writing things down"
- Design documentation tools --- concept docs, design documents, flowcharts, and spreadsheets
By the end of this chapter, you will have Godot installed on your machine, a working scene with a character that moves when you press the arrow keys, and the foundational GDScript knowledge to build on in every subsequent chapter. You will also understand why the best designers reach for a pencil before they reach for a keyboard.
This is the heaviest code chapter in Part I. After this, the code will arrive in service of design concepts. Right now, we need to build the foundation --- the vocabulary, the syntax, the mental model --- that makes all that future code possible.
Let's get to work.
3.1 Why Godot?
You have options. Unity has the largest market share. Unreal has the best graphics pipeline. GameMaker has served 2D developers for decades. So why are we using Godot?
Three reasons, and they all matter.
Reason 1: It's Free --- Actually Free
Godot is MIT-licensed. This is not "free with a revenue cap" (Unity). Not "free until you make enough money and then we change the terms retroactively" (also Unity, in that infamous 2023 debacle). Not "free but we take 5% of your gross revenue after the first million" (Unreal). Godot is free. Permanently. Unconditionally. You can sell a game made with Godot and keep every dollar. You can modify the engine's source code. You can use it commercially without ever paying anyone anything.
For a student learning game design, this matters enormously. You should not be worrying about licensing terms while you're learning what a node is.
💡 Intuition: The engine you learn on shapes how you think about game architecture. Unity teaches you to think in components and GameObjects. Unreal teaches you to think in Actors and Blueprints. Godot teaches you to think in nodes and scenes --- a model that is simpler, more composable, and closer to how games actually work conceptually. There is no objectively "best" model, but Godot's is the easiest to learn and the hardest to paint yourself into a corner with.
Reason 2: GDScript Is Python-Like
Godot's primary scripting language, GDScript, looks and feels like Python. If you have written any Python, you can read GDScript immediately. If you haven't, GDScript is one of the friendliest languages to learn from scratch.
Here is a comparison:
Python:
speed = 200.0
def move_player(direction):
if direction != Vector2.ZERO:
direction = direction.normalized()
return direction * speed
GDScript:
var speed: float = 200.0
func move_player(direction: Vector2) -> Vector2:
if direction != Vector2.ZERO:
direction = direction.normalized()
return direction * speed
The syntax is nearly identical. GDScript adds type hints (: float, : Vector2, -> Vector2), uses func instead of def, and requires var for variable declarations. But the structure --- indentation-based blocks, no semicolons, no curly braces --- is pure Python.
⚠️ Common Pitfall: GDScript is not Python. It looks like Python. It behaves like Python in most situations. But it is a purpose-built language for game development, with built-in types (Vector2, Vector3, Color, NodePath), engine-integrated functions, and a different standard library. Do not try to import numpy. It will not go well.
Reason 3: Fastest-Growing Indie Engine
Godot's community has exploded since the Unity pricing controversy of September 2023. The engine received over $1 million in donations within weeks. GitHub contributions surged. The Godot 4.x release --- a ground-up rewrite with a modern renderer, improved GDScript, and a new physics engine --- positioned it as a serious contender for indie and mid-scale development.
More practically: the games being made in Godot are good. Cassette Beasts, Brotato, Dome Keeper, Halls of Torment --- these are not tech demos. They are commercially successful, well-reviewed games that prove the engine can ship real products.
You are learning a tool with momentum. That matters for your career.
3.2 Installing Godot 4.x
Installation takes about five minutes. Godot is a single executable --- no installer, no dependencies, no runtime to configure.
Step 1: Go to godotengine.org. Click "Download Latest."
Step 2: Choose your platform (Windows, macOS, Linux). Download the Standard version. (There is also a .NET version for C# support. You don't need it. We're using GDScript.)
Step 3: Extract the downloaded archive. You'll get a single file: Godot_v4.x-stable_win64.exe (or equivalent for your OS). That's the engine. The entire engine. Put it somewhere sensible --- a Tools folder, your desktop, wherever you keep applications.
Step 4: Double-click it. Godot opens. You're done.
✅ Best Practice: Create a dedicated folder for your Godot projects --- something like
~/GameProjects/orC:\GameProjects\. Keep it separate from your regular documents. You'll accumulate a lot of project folders over the course of this book, and organization will save you hours of confusion later.
There is no account to create. No license to agree to. No telemetry to opt out of. The engine is yours.
The Godot Project Structure
Before we tour the interface, understand what a Godot project actually is. When you create a project, Godot generates a folder containing:
project.godot--- the project configuration file. Engine version, window settings, input map, autoloads. You'll rarely edit this by hand, but it's plain text and version-control friendly.icon.svg--- the default project icon (also your first test sprite)..godot/--- a cache directory. Godot generates this automatically. Never edit it. Never commit it to version control.
As you work, you'll add:
- .tscn files --- scenes. Each scene is a tree of nodes saved in a text-based format.
- .gd files --- GDScript scripts. Plain text, version-control friendly.
- .tres files --- resources (materials, themes, shapes). Also text-based.
- Asset files --- images (.png, .svg), audio (.ogg, .wav), fonts (.ttf).
Every file in your project is referenced by a res:// path --- Godot's internal resource path. res://player.gd means "the file player.gd in the project root." res://scenes/player.tscn means "the file player.tscn inside the scenes folder."
📝 Design Note: Organize your project from the start. A common structure:
text res:// ├── scenes/ (player.tscn, enemy.tscn, level_01.tscn) ├── scripts/ (player.gd, enemy.gd, game_manager.gd) ├── art/ (sprites, tilesets, UI graphics) ├── audio/ (music, sound effects) └── ui/ (menu scenes, HUD elements)You can also keep each entity self-contained:res://player/player.tscn+res://player/player.gd+res://player/player_sprite.png. Either approach works. Pick one and be consistent.
3.3 The Godot Interface --- A Guided Tour
When you open Godot for the first time, you see the Project Manager --- a list of your projects (currently empty). Click New Project, give it a name ("Chapter3" is fine), choose your project folder, and click Create & Edit.
The editor opens. It looks complex. It isn't --- once you understand the four quadrants.
The Scene Tree (Top Left)
This is the most important panel. It shows the hierarchical structure of everything in your current scene. Games in Godot are built from scenes, and scenes are built from nodes arranged in a tree.
Think of it like a family tree. A parent node can have children. Children can have their own children. The tree determines both visual hierarchy (what draws on top of what) and logical hierarchy (what belongs to what).
Right now your scene tree says "Create Root Node" with four buttons: 2D Scene, 3D Scene, User Interface, and Other Node. We'll use these shortly.
The Inspector (Right Side)
When you select a node in the scene tree, the Inspector shows all of its properties. Position, rotation, scale, color, texture, collision shapes --- everything that defines what the node is and does.
The Inspector is where you tweak. You will spend a shocking amount of time here, adjusting values by fractions to get something to feel right. This is normal. This is the job.
The Script Editor (Center, When Open)
Click the "Script" tab at the top of the viewport and you enter the code editor. It supports syntax highlighting, code completion, and inline documentation. It's not Visual Studio Code, but it's surprisingly capable for an embedded editor.
🛠️ Design Note: You can use an external editor (VS Code, Sublime Text) if you prefer. Godot supports external editors through Editor Settings > Text Editor > External. For learning, the built-in editor is fine. It keeps everything in one window, which reduces cognitive overhead when you're switching between scenes and code constantly.
The Viewport (Center)
The main canvas where you visually place and arrange nodes. In 2D mode, it's a flat canvas with a coordinate grid. You can drag nodes, zoom in and out, and see what your scene looks like before you run it.
The blue rectangle visible in the viewport represents the game window --- what the player will actually see when the game runs. Anything outside that rectangle exists in the world but won't be visible until the camera moves.
The Bottom Panel
Houses the Output console, the Debugger, the Audio bus layout, and the Animation editor. The Output console is where print() statements appear and where error messages show up. You will learn to check this panel reflexively.
The FileSystem Dock (Bottom Left)
This shows every file in your project, organized by folder. It mirrors the actual file structure on your hard drive. You can drag assets from the FileSystem dock into the Inspector (to assign textures, audio, etc.) or into the scene tree (to instance scenes).
🎓 Learning Tip: When you're stuck, use the built-in documentation. Press F1 (or click the "Search Help" button at the top of the script editor) and type any class name ---
CharacterBody2D,Sprite2D,Timer. Godot's integrated help shows every property, method, and signal for that class, with descriptions and often example code. This is faster than searching the web and always matches your engine version.
Running and Testing
The top-right corner of the editor has several play buttons:
- Play (F5) --- runs the main scene (the one set in Project Settings > Application > Run)
- Play Scene (F6) --- runs the currently open scene, regardless of main scene setting
- Play Custom Scene (F7) --- choose any scene to run
F6 is your friend. When you're working on a player scene and want to test it without loading the entire game, F6 runs just that scene in isolation. This is faster and lets you test individual pieces independently.
When the game is running, the editor's bottom panel switches to show live debugging information: the scene tree at runtime, the node inspector, and the output console. You can pause the running game, step through frames, and inspect variable values --- essential tools for debugging.
3.4 Nodes and Scenes --- The Mental Model
Here is the single most important concept in Godot: everything is a node, and nodes compose into scenes.
A node is the atomic unit of a Godot game. There are dozens of node types, each with a specific purpose:
Node2D--- a base 2D node with a position, rotation, and scaleSprite2D--- displays an imageCharacterBody2D--- a physics-enabled body for player charactersCollisionShape2D--- defines a collision boundaryCamera2D--- the viewport cameraLabel--- displays textColorRect--- a colored rectangleTimer--- fires a signal after a countdownAudioStreamPlayer--- plays a sound
A scene is a tree of nodes saved as a reusable file (.tscn). Here's the critical insight: a scene can contain other scenes as children. Your player character is a scene (with a sprite, collision shape, and script). Your enemy is a different scene. Your level is a scene that contains instances of the player scene, enemy scenes, platform scenes, and decoration scenes.
🧩 Design Insight: Godot's node-and-scene architecture mirrors how good designers think about games. A game is not one monolithic thing. It's a composition of smaller things: characters, environments, UI elements, systems. Each thing has its own behavior. Each thing can be tested in isolation. Each thing can be reused. When you build a player scene and an enemy scene and a level scene that contains both, you are thinking in composable systems --- and that's exactly how professional game architecture works.
This composability is what makes Godot's architecture powerful. In Unity, you might create a "Player" GameObject and add components to it (SpriteRenderer, Rigidbody2D, Script). In Godot, you create a player scene with child nodes. The result is similar, but Godot's version is more intuitive: instead of attaching invisible components to an object, you're building a visible tree of parts.
3.5 Your First Scene --- Something on Screen
Let's build something. Open your Chapter3 project.
Step 1: Create a ColorRect
In the Scene panel, click 2D Scene to create a root Node2D. Now right-click the Node2D in the tree and select Add Child Node. Search for ColorRect and add it.
A ColorRect is the simplest visual node --- a colored rectangle. Select it in the tree, and in the Inspector, set:
- Size: x = 1152, y = 648 (or whatever your project's window size is --- check Project > Project Settings > Display > Window)
- Color: Pick something dark. A deep navy or charcoal works.
- Position: x = 0, y = 0
You now have a background.
Step 2: Add a Sprite2D
Right-click the root Node2D again, add a child node, and this time choose Sprite2D. A Sprite2D displays an image.
You need an image. For now, Godot includes a default icon (icon.svg) in every new project. In the Inspector for the Sprite2D, find the Texture property. Click the dropdown and select Load. Navigate to res://icon.svg and select it.
The Godot icon appears in your viewport. Drag it somewhere visible --- center of the screen is fine.
Step 3: Run It
Press F5 (or the Play button in the top-right). Godot asks you to select a main scene. Choose your current scene. The game window opens.
You see a dark rectangle with a Godot icon floating on it.
This is a game. A terrible, non-interactive game with no mechanics, no feedback, and no player agency. But it runs. Something is on screen. You have crossed the first threshold.
🚪 Threshold Concept: Getting anything on screen is the hardest step. Not technically --- technically it's trivial. Psychologically. The blank project, the empty editor, the blinking cursor --- these are paralyzing. Every game that has ever been made started with someone putting one thing on screen and thinking "okay, now what?" You have done that. Now we make it move.
Experimenting with the Scene
Before we write code, spend five minutes experimenting. Select the Sprite2D and try these in the Inspector:
- Position: Drag the X and Y values. The sprite moves in the viewport.
- Rotation: Set it to 45 degrees. The sprite tilts.
- Scale: Set both X and Y to 2. The sprite doubles in size. Set them to different values --- the sprite stretches. Set one to -1 --- the sprite flips.
- Modulate: Click the color swatch. Change the tint. The sprite takes on the color you choose. This is how you'll implement damage flashes, power-up effects, and invisibility later.
Every property you see in the Inspector can also be set from code. $Sprite2D.position = Vector2(200, 300)`. `$Sprite2D.rotation_degrees = 90. $Sprite2D.modulate = Color.RED. The Inspector is the visual interface to the same data your scripts will manipulate.
This is important: the Inspector and your code are two views of the same truth. When you change a value in the Inspector, the code sees the change. When your code changes a value, the Inspector reflects it (at runtime, in the debugger). Understanding this duality --- visual editing and code editing as two interfaces to the same underlying node properties --- is fundamental to working in Godot.
3.6 GDScript Fundamentals --- Learning by Building
We're going to learn GDScript by building a moving character. Not by reading a language reference. Not by memorizing syntax tables. By writing code that makes something happen on screen.
Variables
GDScript variables are declared with var:
var health: int = 100
var player_name: String = "Hero"
var speed: float = 200.0
var position: Vector2 = Vector2(100, 200)
var is_alive: bool = true
Type hints (: int, : float, etc.) are optional but recommended. They catch errors early and make your code self-documenting.
Constants use const:
const MAX_HEALTH: int = 100
const GRAVITY: float = 980.0
Constants cannot be changed after declaration. Use them for values that should never change during gameplay --- max health, gravity, tile size.
The @export Annotation
Here's one of GDScript's best features for game development:
@export var speed: float = 200.0
@export var max_health: int = 100
@export var jump_force: float = -400.0
@export exposes the variable in the Inspector. This means you can change speed from 200 to 300 without touching the code --- just drag the slider in the editor. This is enormously useful for tuning. You'll adjust movement speed, jump height, and enemy health hundreds of times during development. Editing code each time is slow. The Inspector is fast.
💡 Intuition:
@exportis one of the most powerful design tools in Godot, and most beginners underuse it. Every time you write a number in your code, ask yourself: "Will I want to tweak this later?" If the answer is yes --- and it almost always is --- make it@export. Design is iteration, and iteration requires fast access to the values you're iterating on.
Functions
Functions use func:
func take_damage(amount: int) -> void:
health -= amount
if health <= 0:
die()
func die() -> void:
print("Game Over")
queue_free()
The -> void return type annotation says "this function returns nothing." You can also return values:
func is_dead() -> bool:
return health <= 0
func get_damage_multiplier(is_critical: bool) -> float:
if is_critical:
return 2.5
return 1.0
Built-in Types That Matter
GDScript includes several types purpose-built for game development. You'll use these constantly:
Vector2 --- a 2D vector with x and y components. Positions, velocities, directions, and sizes are all Vector2s.
var position = Vector2(100, 200)
var direction = Vector2.RIGHT # (1, 0)
var zero = Vector2.ZERO # (0, 0)
var diagonal = Vector2(1, 1).normalized() # (~0.707, ~0.707)
Color --- RGBA color values. Used for sprites, UI, particles, and tinting.
var red = Color.RED
var custom = Color(0.2, 0.5, 0.8, 1.0) # R, G, B, A
var transparent = Color(1, 1, 1, 0.5) # 50% transparent white
NodePath --- a reference to a node in the scene tree. Usually you'll use the $ shorthand instead.
Array and Dictionary --- collections. Arrays are ordered lists, dictionaries are key-value maps. We'll use these extensively starting in Chapter 10 (loot tables) and Chapter 24 (inventory).
🔄 Recurring Theme: GDScript's type system is designed for games, not for general-purpose programming. Vector2, Color, Transform2D, Rect2 --- these types exist because games need them constantly. In Python, you'd import a math library. In GDScript, the math is built in. This is what "purpose-built for game development" means in practice: the common operations are one function call, not ten.
The Big Three: _ready(), _process(), _physics_process()
Every GDScript file attached to a node can override three critical lifecycle functions:
_ready() --- called once, when the node enters the scene tree. Use it for initialization:
func _ready() -> void:
health = MAX_HEALTH
print("Player spawned with ", health, " HP")
_process(delta) --- called every frame. delta is the time in seconds since the last frame (typically ~0.016 seconds at 60 FPS). Use it for visual updates, UI, and non-physics logic:
func _process(delta: float) -> void:
# Rotate the sprite 90 degrees per second
rotation += deg_to_rad(90) * delta
_physics_process(delta) --- called at a fixed rate (default 60 times per second), regardless of frame rate. Use it for movement, physics, and anything that needs consistent timing:
func _physics_process(delta: float) -> void:
velocity = input_vector * speed
move_and_slide()
⚠️ Common Pitfall: New developers put movement code in
_process(). Don't._process()runs at variable frame rates --- 60 FPS on a fast machine, 30 FPS on a slow one. This means movement will be faster on fast machines and slower on slow ones._physics_process()runs at a fixed rate, so movement is consistent regardless of hardware. Always use_physics_process()for character movement and physics.
The Delta Mystery
Why does delta exist? Because frames are not evenly spaced. If you write position.x += 5 in _process(), your character moves 5 pixels per frame. At 60 FPS, that's 300 pixels per second. At 30 FPS, that's 150 pixels per second. The game plays differently on different hardware.
Instead: position.x += 300 * delta. At 60 FPS, delta is ~0.0167, so the character moves ~5 pixels per frame (300 * 0.0167 ≈ 5). At 30 FPS, delta is ~0.033, so the character moves ~10 pixels per frame (300 * 0.033 ≈ 10). Either way, the character moves 300 pixels per second.
delta makes your game frame-rate independent. Use it.
3.7 Building a Moving Character
Here is the goal: a character that moves in eight directions using the arrow keys or WASD. Let's build it step by step.
Step 1: Create the Player Scene
Create a new scene (Scene > New Scene). Choose Other Node and search for CharacterBody2D. This is the root node of your player.
Why CharacterBody2D? Because it is Godot's node for player-controlled characters. It handles collision detection and provides the move_and_slide() function, which moves the character and automatically slides along surfaces it bumps into. The alternative --- RigidBody2D --- is physics-driven and better suited for objects that should bounce, tumble, and obey gravity without player input (think barrels, crates, ragdolls). For a player character you control directly, CharacterBody2D is almost always the right choice.
Step 2: Add a Visual
Right-click the CharacterBody2D and add a Sprite2D child. Assign a texture (use res://icon.svg for now, or download a character sprite from itch.io or OpenGameArt).
Step 3: Add a Collision Shape
Right-click the CharacterBody2D again and add a CollisionShape2D child. In the Inspector, click the Shape property and create a new RectangleShape2D (or CircleShape2D). Resize it to roughly match your sprite.
Your scene tree should look like this:
CharacterBody2D (root)
├── Sprite2D
└── CollisionShape2D
Save this scene as player.tscn.
Step 4: Write the Movement Script
Select the CharacterBody2D root node. Click the scroll icon (Attach Script) at the top of the Scene panel. Accept the defaults and click Create.
A new file, player.gd, opens in the script editor. Replace the template code with this:
extends CharacterBody2D
@export var speed: float = 200.0
func _physics_process(delta: float) -> void:
var input_vector = Vector2.ZERO
input_vector.x = Input.get_axis("ui_left", "ui_right")
input_vector.y = Input.get_axis("ui_up", "ui_down")
if input_vector != Vector2.ZERO:
input_vector = input_vector.normalized()
velocity = input_vector * speed
move_and_slide()
Let's break this down line by line.
extends CharacterBody2D --- this script inherits from CharacterBody2D. It gets all of CharacterBody2D's built-in properties and methods, including velocity and move_and_slide().
@export var speed: float = 200.0 --- the movement speed, in pixels per second. @export means you can adjust this in the Inspector without touching code. You will adjust this many times.
var input_vector = Vector2.ZERO --- a Vector2 (x, y) that will store the player's directional input. It starts at (0, 0) every frame.
Input.get_axis("ui_left", "ui_right") --- this is elegant. get_axis returns a value between -1 and 1. If the player holds Left, it returns -1. Right returns 1. Both or neither returns 0. The same logic applies to the Y axis with Up (-1) and Down (1).
input_vector.normalized() --- this prevents diagonal movement from being faster than cardinal movement. Without normalization, moving right gives a vector of (1, 0) with a magnitude of 1. Moving diagonally gives (1, 1) with a magnitude of ~1.414. Normalized forces the magnitude back to 1 regardless of direction.
📐 Design Detail: The normalization fix is subtle but important. Without it, players who move diagonally travel ~41% faster than players who move cardinally. In a game where speed matters --- dodging projectiles, racing to an objective, kiting enemies --- this creates an unintended advantage. Speedrunners in games that don't normalize diagonal movement exploit this constantly. Fix it now and you never have to think about it again.
velocity = input_vector * speed --- sets the CharacterBody2D's built-in velocity property. A normalized vector times 200 gives a velocity of 200 pixels per second in whatever direction the player is pressing.
move_and_slide() --- the magic function. It moves the character by the velocity you set, handles collision detection, and slides the character along surfaces it contacts. You don't have to write collision response code. Godot handles it.
Step 5: Add the Player to a Level
Go back to your original scene (the one with the ColorRect). Instance the player scene: right-click the root node, select Instantiate Child Scene, and choose player.tscn. The player appears in the viewport. Drag it to the center of the screen.
Step 6: Run
Press F5. Your character moves with the arrow keys. Eight directions. Smooth. Normalized.
This is Player.gd in its first incarnation. By Chapter 40, this script will have grown to handle attacks, dashes, wall slides, inventory interaction, dialogue triggers, and more. Right now, it does one thing: it moves. And that one thing is the foundation of everything.
3.8 Input Handling in Depth
The Input.get_axis() approach is clean, but let's understand the input system more broadly.
Input Actions
Godot uses an Input Map (Project > Project Settings > Input Map) to define named actions and bind them to keys, buttons, or controller inputs. The default project includes:
ui_left--- Left Arrow, A key, D-pad Leftui_right--- Right Arrow, D key, D-pad Rightui_up--- Up Arrow, W key, D-pad Upui_down--- Down Arrow, S key, D-pad Downui_accept--- Enter, Spaceui_cancel--- Escape
You can add custom actions. For the progressive project, you'll eventually want:
attack--- bound to X, Z, or mouse clickdash--- bound to Shift or Cinteract--- bound to E or Fpause--- bound to Escape
Checking Input Directly
Besides get_axis(), you can check individual actions:
func _physics_process(delta: float) -> void:
if Input.is_action_pressed("ui_right"):
velocity.x = speed
elif Input.is_action_pressed("ui_left"):
velocity.x = -speed
else:
velocity.x = 0
is_action_pressed() returns true every frame the key is held down. is_action_just_pressed() returns true only on the first frame the key is pressed. is_action_just_released() returns true only on the frame the key is released.
For movement, use is_action_pressed() (continuous). For attacks, use is_action_just_pressed() (one-shot). For charge attacks or held actions, combine both.
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed("attack"):
perform_attack()
if Input.is_action_just_pressed("dash"):
start_dash()
🎮 Design Connection: The choice between
is_action_pressedandis_action_just_pressedis a design decision, not just a technical one. If your attack fires onpressed, the player can hold the button to attack continuously. If it fires onjust_pressed, the player must press repeatedly. The first feels automatic and effortless. The second feels deliberate and physical. Hades uses rapid-fire automatic attacks. Dark Souls requires deliberate, committed button presses. Neither is "correct." The input model serves the game feel.
3.9 Signals --- Nodes Talking to Each Other
Signals are Godot's event system. When something happens to a node --- a timer expires, a body enters a collision area, a button is clicked --- the node emits a signal. Other nodes can connect to that signal and run a function when it fires.
This is the Observer pattern, and it's fundamental to game architecture.
Built-in Signals
Every node type comes with built-in signals. A Timer node has a timeout signal. An Area2D has body_entered and body_exited. A Button has pressed.
Connecting Signals in the Editor
- Select the node that emits the signal (e.g., a Timer)
- Click the Node tab (next to the Inspector)
- Double-click the signal you want to connect (e.g.,
timeout) - Select the node that should receive the signal (e.g., your player)
- Godot auto-generates a function name (e.g.,
_on_timer_timeout) - Click Connect
Now, when the Timer's countdown hits zero, your player's _on_timer_timeout() function runs automatically.
Connecting Signals in Code
You can also connect signals programmatically:
func _ready() -> void:
$Timer.timeout.connect(_on_timer_timeout)
func _on_timer_timeout() -> void:
print("Timer finished!")
The $` operator is shorthand for `get_node()`. `$Timer gets the child node named "Timer."
Custom Signals
You can define your own signals:
signal health_changed(new_health: int)
signal player_died
func take_damage(amount: int) -> void:
health -= amount
health_changed.emit(health)
if health <= 0:
player_died.emit()
Other nodes --- a health bar, a game manager, a sound effect player --- can connect to these signals and respond independently.
🧩 Design Insight: Signals enforce loose coupling --- the player doesn't need to know that a health bar exists. It just emits "my health changed" and any node listening can respond. This is critical for maintainable game architecture. If your player script directly calls
health_bar.update(), then removing the health bar breaks the player code. With signals, you can add, remove, or change listeners without touching the emitter. This architecture scales. The alternative doesn't.
Practical Example: Damage Flash
Let's add a simple damage flash to demonstrate signals. Add a Timer node as a child of your player. Set its Wait Time to 0.15, One Shot to true. Connect its timeout signal to the player script.
extends CharacterBody2D
@export var speed: float = 200.0
var health: int = 100
signal health_changed(new_health: int)
func _physics_process(delta: float) -> void:
var input_vector = Vector2.ZERO
input_vector.x = Input.get_axis("ui_left", "ui_right")
input_vector.y = Input.get_axis("ui_up", "ui_down")
if input_vector != Vector2.ZERO:
input_vector = input_vector.normalized()
velocity = input_vector * speed
move_and_slide()
func take_damage(amount: int) -> void:
health -= amount
health_changed.emit(health)
$Sprite2D.modulate = Color.RED
$FlashTimer.start()
func _on_flash_timer_timeout() -> void:
$Sprite2D.modulate = Color.WHITE
When take_damage() is called, the sprite turns red. After 0.15 seconds, the timer fires and the sprite returns to white. Simple, effective, and built on signals.
3.10 Paper Prototyping --- The Tool You're Underestimating
Here is a truth that experienced designers know and new designers resist: your first prototype should be made of paper.
Not because paper is romantic or retro. Because paper is fast. A digital prototype takes hours. A paper prototype takes minutes. And when you're testing a core mechanic or game loop --- when you need to know whether the idea works before you invest time in the implementation --- speed is everything.
The Index Card Method
Get a stack of blank index cards. Each card represents one game element:
- Mechanic cards: Write one mechanic per card. "8-directional movement." "Melee attack with 3-hit combo." "Dash with 0.5s cooldown." "Wall jump."
- Enemy cards: One enemy type per card. Sketch it. Write its behavior in one sentence. "Patrols left-right. Charges when player is within 3 tiles."
- Item cards: One item per card. "Health potion: restores 25 HP." "Key: opens locked doors."
- Level element cards: "Moving platform." "Spike pit." "Locked door." "Treasure chest."
Now lay the cards on a table and simulate your game. Move a token (a coin, an eraser, your finger) through the arrangement. Encounter enemies. Use items. Test whether the mechanics interact in interesting ways.
💡 Intuition: If your game is fun on a table with index cards and a coin, it will almost certainly be fun as a digital game. If it's boring on the table, code won't save it. Code adds audiovisual polish and precise timing, but it cannot create fun from nothing. The fun has to be in the system, and paper lets you test the system without the overhead of implementation.
Dice as Randomness Generators
If your game involves randomness --- loot drops, critical hits, enemy spawns --- use dice to simulate it during paper prototyping.
- D6 (six-sided die): Good for simple probability. "Roll 5-6 to land a critical hit" = 33% chance.
- D20 (twenty-sided die): Finer granularity. "Roll 15+ to find a rare item" = 30% chance.
- 2D6 (two six-sided dice): Bell curve distribution. Results cluster around 7, with extremes (2, 12) being rare. Good for simulating damage ranges where average hits are common and extreme hits are rare.
When Paper Prototyping Works Best
Paper excels at testing:
- Core loops: Does the explore-fight-collect-upgrade cycle feel satisfying?
- Economy balance: Are resources too abundant? Too scarce?
- Difficulty curves: Does the game get harder at the right rate?
- Strategic depth: Are there meaningful choices, or is there one obvious best strategy?
- Progression pacing: Does the player gain new abilities at a satisfying cadence?
Paper is weaker at testing:
- Game feel: The precise timing, weight, and responsiveness of controls
- Visual design: How the game looks in motion
- Audio feedback: Sound is fundamentally un-prototypeable on paper
- Real-time interactions: Fast-paced action doesn't translate well to tabletop
The takeaway: prototype your systems on paper. Prototype your feel in the engine.
A Paper Prototyping Example
Let's make this concrete. Suppose you're designing the progressive project's core loop: explore rooms, fight enemies, collect keys, unlock doors. Here's how you'd paper-prototype it:
Setup: Lay out six index cards in a rough map. Each card is a "room." Draw a line between connected rooms. Mark two rooms with a skull (enemy encounter) and one with a star (treasure). Mark one connection with a lock symbol.
Player token: A coin starts on the first room.
Encounter resolution: When the player enters a skull room, roll a D6. On 4+, you win (remove the skull, gain a "power" token). On 1-3, you lose (lose a health token --- start with 3).
Treasure: When you enter a star room, gain a key.
Locked door: Spend a key to cross a locked connection.
Play it. Move your coin. Roll the dice. Make decisions. After three playthroughs, you'll know: Is the map too linear? Are enemies too hard or too easy? Does the key-and-lock gating feel satisfying or tedious? Does the core loop (move → fight → collect → unlock) create interesting decisions?
You just tested a game design in ten minutes without writing a line of code. The answers you got --- "enemies feel too punishing at 50/50 odds" or "the map needs more branching paths" --- are design insights that transfer directly to the digital version. This is what paper prototyping is for.
3.11 Design Documents --- Writing It Down Before You Build It
Every professional game starts with documentation. Not because documentation is fun (it isn't), but because documentation forces clarity. You cannot write down a vague idea. The act of writing reveals gaps, contradictions, and unresolved decisions.
You need three documents.
Document 1: The One-Page Concept
This is a single page (literally --- one page, no cheating) that anyone can read in two minutes and understand what your game is. It contains:
Title: The working title.
Logline: One sentence describing the game. ("A 2D action-adventure where you explore a shifting temple, fighting ancient guardians to uncover why the temple moves.")
Genre: Be specific. Not just "action" --- "2D top-down action-adventure with Metroidvania-style gating."
Platform: PC, console, mobile, web.
Target Audience: Who is this for? Age range, gaming experience, genre preferences.
Player Fantasy: Your player fantasy statement from Chapter 1's exercise.
Core Mechanic: The one thing the player does most. ("8-directional melee combat with dodge and dash.")
Core Loop: What the player repeats. ("Explore rooms → fight enemies → collect resources → unlock new areas.")
Unique Selling Point: Why should someone play this instead of the ten thousand other games in the genre? One sentence.
Reference Games: 2-3 games that yours resembles or draws from. "Dark Souls meets Celeste" is a cliche, but "the movement precision of Celeste combined with the environmental storytelling of Dark Souls" is useful.
📝 Design Note: The one-page concept is a communication tool. You will show this to playtesters, teammates, potential collaborators, and (eventually) publishers. If someone reads it and doesn't understand what your game is, the document has failed. Revise until a non-gamer could read it and say "okay, I get it."
Document 2: The Game Design Document (GDD)
The GDD is the comprehensive reference for every design decision in your game. It is a living document --- it changes as the game changes. A GDD typically includes:
- Overview: Expanded version of the concept doc
- Game mechanics: Detailed description of every mechanic, with numbers
- Level design: Map sketches, progression flow, difficulty curve
- Characters: Player, enemies, NPCs --- their abilities, behaviors, stats
- Story and narrative: Plot outline, dialogue plan, lore
- Art direction: Visual style guide, reference images, color palette
- Audio direction: Music style, sound effect categories, voice acting plan
- UI/UX: Menu flow, HUD elements, control scheme
- Economy and progression: XP tables, item lists, upgrade paths
- Technical requirements: Target platform, performance targets, engine specifics
A GDD for a small indie game might be 10-20 pages. For a AAA game, it can exceed 500 pages.
⚠️ Common Pitfall: New designers write 50-page GDDs before writing a line of code. Don't. The GDD should grow with the project. Start with 3-5 pages covering mechanics, core loop, and level design. Add sections as you build them. A beautiful 50-page document for a game that doesn't exist yet is not design --- it's procrastination.
Document 3: The Task List
Every feature in your game should be on a list, organized by priority:
- Must-Have: The game cannot ship without these. Movement, combat, one complete level, basic UI.
- Should-Have: The game is significantly better with these. Sound effects, additional levels, enemy variety.
- Nice-to-Have: Polish and extras. Particle effects, achievements, speedrun timer.
- Cut: Things you wanted but cannot afford. Every project has this column. Learning to use it is one of the hardest design skills.
We'll return to scope management in Chapter 37. For now, know that the task list exists, and that putting features into the "Cut" column is not failure --- it's discipline.
3.12 Flowcharts, Spreadsheets, and Other Thinking Tools
Flowcharts
Flowcharts map player decision paths. They're essential for:
- Menu flow: Main Menu → New Game / Continue / Settings / Quit
- Dialogue branching: NPC says X → Player responds A, B, or C → each leads to a different outcome
- Game state flow: Title Screen → Gameplay → Pause → Game Over → Retry / Quit
- Quest design: Accept quest → Complete objective A → Choice → Objective B1 or B2 → Turn in
Any free diagramming tool works --- draw.io, Figma, even hand-drawn charts on paper. The point is not the tool. The point is making the structure visible.
Spreadsheets
If you think spreadsheets aren't creative tools, you haven't met a game designer. Spreadsheets are where balancing happens:
- Damage tables: Weapon damage vs. enemy health at each level
- Economy models: Gold earned per hour vs. gold spent on upgrades
- Progression curves: XP required per level, abilities unlocked per level
- Drop tables: Item rarity, drop chances, expected loot per run
- Difficulty tuning: Enemy count, enemy health, and enemy damage at each stage
You do not need to be a spreadsheet wizard. You need to be able to put numbers in columns and see whether they make sense. We'll build specific spreadsheets in Chapters 10, 12, 24, and 32.
🔄 Recurring Theme: Design is systems, not ideas. This chapter's toolset --- GDScript, paper prototypes, design documents, flowcharts, spreadsheets --- exists to transform ideas into systems. An idea is "the player fights enemies and gets stronger." A system is "the player deals 10-15 base damage, enemies have 30-100 HP scaling with zone, each kill grants 5-15 XP, and leveling up requires 100 * current_level XP." The idea is free. The system is the design.
3.13 Version Control --- A Brief but Non-Negotiable Mention
You need version control. Specifically, you need Git.
Version control tracks every change you make to your project. If you break something at 3 AM (and you will), you can revert to the last working version. If you want to try an experimental feature without risking your stable build, you can create a branch. If you collaborate with anyone, version control is not optional.
Setting up Git is outside this chapter's scope (Appendix B covers it), but here is the minimum:
- Install Git from git-scm.com
- Create a
.gitignorefile in your project root. Godot projects should ignore the.godot/directory (it contains cached data that doesn't need version control) - Initialize a repository:
git init - Commit early, commit often. A commit message like "added player movement" is infinitely better than no commit at all
💀 Hard Lesson: You will lose work at some point. Everyone does. A corrupted file, an accidental deletion, a change that breaks everything and you can't figure out what you changed. Version control reduces "I lost everything" to "I lost the last 20 minutes." That's the difference between a setback and a catastrophe.
Services like GitHub, GitLab, and Bitbucket provide free remote repositories. Push your project to one. If your hard drive dies, your project survives.
3.14 Additional GDScript Patterns You'll Need
Before we close, here are a few more GDScript patterns that will appear in future chapters. You don't need to memorize them now. Skim them, know they exist, and come back when you need them.
Conditional Logic
if health <= 0:
die()
elif health < 25:
play_warning_effect()
else:
play_idle_animation()
Match (GDScript's Switch Statement)
match current_state:
"idle":
play_idle_animation()
"running":
play_run_animation()
"attacking":
play_attack_animation()
_:
print("Unknown state: ", current_state)
The underscore _ is the default case.
Arrays and Dictionaries
var inventory: Array = ["sword", "shield", "potion"]
var player_stats: Dictionary = {
"health": 100,
"attack": 15,
"defense": 8,
"speed": 200.0
}
# Access
print(inventory[0]) # "sword"
print(player_stats["health"]) # 100
# Modify
inventory.append("bow")
player_stats["health"] -= 20
For Loops
for item in inventory:
print(item)
for i in range(10):
print("Iteration: ", i)
for enemy in get_tree().get_nodes_in_group("enemies"):
enemy.take_damage(10)
Enums
enum State { IDLE, RUNNING, ATTACKING, DASHING }
var current_state: State = State.IDLE
func _physics_process(delta: float) -> void:
match current_state:
State.IDLE:
handle_idle()
State.RUNNING:
handle_running()
State.ATTACKING:
handle_attacking()
State.DASHING:
handle_dashing()
Enums are cleaner than strings for state management. "idle" is a string that can be misspelled. State.IDLE is a value that the editor autocompletes and the compiler checks.
⚡ Efficiency Tip: You don't need to learn all of GDScript before you start building. Learn enough to build the next feature, then learn the next pattern when you need it. GDScript's documentation is excellent (F1 in the editor searches the built-in docs), and the Godot community is one of the most welcoming in game development. Ask questions. Read other people's code. The fastest way to learn a language is to use it to build something you care about.
3.15 Debugging --- Your New Best Skill
Things will go wrong. Not occasionally --- constantly. A sprite that doesn't appear. A character that doesn't move. A signal that doesn't fire. Code that produces no errors but also produces no results. Debugging is not a failure mode --- it's the primary activity of programming.
The Output Panel
Your first debugging tool is print(). It's crude and it works.
func _physics_process(delta: float) -> void:
var input_vector = Vector2.ZERO
input_vector.x = Input.get_axis("ui_left", "ui_right")
input_vector.y = Input.get_axis("ui_up", "ui_down")
print("Input: ", input_vector) # See what the game thinks you're pressing
print("Velocity: ", velocity) # See how fast you're moving
If input_vector is always (0, 0) even when you're pressing keys, the problem is in the Input Map, not the movement code. If input_vector is correct but velocity is zero, the problem is in the multiplication. print() tells you where the chain breaks.
💀 Hard Lesson: Remove your debug
print()statements before they pile up. Aprint()inside_physics_process()runs 60 times per second. Ten debug prints at 60 FPS is 600 lines of output per second. The Output panel will become unusable. Debug, find the problem, fix it, remove the prints.
Common Beginner Errors
"Invalid call to function 'move_and_slide' in base 'Node2D'" --- you attached the script to a Node2D instead of a CharacterBody2D. move_and_slide() only exists on CharacterBody2D. Fix: change the root node type or move the script.
"Node not found: Sprite2D" --- your $Sprite2D reference doesn't match the node's actual name. Node names are case-sensitive. "Sprite2D" is not "sprite2D" is not "SPRITE2D." Check the scene tree.
Character doesn't move --- several possible causes. Is the script attached to the right node? (Check for the script icon in the scene tree.) Is speed set to 0 in the Inspector? (The @export value in the Inspector overrides the code default.) Is _physics_process spelled correctly? (GDScript won't warn you about unused functions --- it just won't call them.)
Character moves but ignores collisions --- you're using a CharacterBody2D but forgot to add a CollisionShape2D child. Without a collision shape, the physics engine doesn't know the character's boundaries. The same applies to walls and obstacles: they need both a StaticBody2D and a CollisionShape2D.
🛠️ Design Note: Debugging is a design skill, not just a programming skill. When a playtest reveals that "the jump feels wrong," you're debugging game feel. When a player reports that "I didn't know I could dash," you're debugging communication. The process is the same: observe the problem, form a hypothesis, test it, iterate. You will use this process for the rest of your career.
3.16 Progressive Project --- Your First Godot Scene
Time to formalize what you've built in this chapter. Here are the progressive project requirements for Chapter 3:
Required Deliverables
- Godot project created --- a new Godot 4.x project in your projects folder
- Player scene ---
player.tscnwith aCharacterBody2D,Sprite2D, andCollisionShape2D - Player script ---
player.gdimplementing 8-directional movement with normalized diagonal speed and@export var speed - Main scene --- a scene containing a background (ColorRect or TileMap) and an instanced player
- The game runs --- pressing F5 produces a playable scene where the character moves
Stretch Goals
- Add a custom sprite (not the default Godot icon)
- Add a sprint mechanic (hold Shift to move 50% faster)
- Add a
Camera2Dthat follows the player - Add boundary walls using
StaticBody2DandCollisionShape2Dso the player can't leave the screen
What You're Building Toward
Right now you have a character that moves on a flat background. By Chapter 8, this character will have attacks, dashes, health, screen shake on hit, and particle effects. By Chapter 17, it will navigate a full tilemap level with enemies and platforms. By Chapter 26, it will have a complete combat system with dodge rolls, hitboxes, and a boss fight.
Everything starts here. Everything starts with move_and_slide().
3.17 What You Learned (and What's Next)
This chapter covered a lot of ground --- probably more technical ground than any other chapter in the book. Here's what you now have:
Tools: - Godot 4.x installed and running - The ability to create scenes, add nodes, and run your game - A working knowledge of the scene tree, Inspector, script editor, and viewport
GDScript:
- Variables, constants, @export
- Functions, return types
- _ready(), _process(), _physics_process()
- Input handling with Input.get_axis() and is_action_pressed()
- Signals (built-in and custom)
- Delta time and frame-rate independence
Design Tools: - Paper prototyping with index cards and dice - The one-page concept document - The GDD (and why not to over-invest in it early) - Flowcharts and spreadsheets for systems thinking - Version control (Git) for project safety
A Working Game:
- A character that moves in eight directions with normalized speed
- A main scene with a background and a player
- The player.gd script that you'll extend for the next 37 chapters
In Chapter 4, we turn from tools to the most important variable in any game: the player. Who are they? What do they want? Why do they play? And how do you design for people you've never met?
Your workbench is ready. The work begins.