The free, open source engine for making 2D and 3D games, from your first scene to shipping.
From what a game engine is to shaders, multiplayer and editor plugins. You learn the editor, scenes and nodes, and GDScript from zero; then movement, physics, signals, animation, user interfaces, tile maps, sound and saving; then enemies with state machines and pathfinding, 3D, shaders, architecture, performance and exporting. Every module explains the ideas, shows real GDScript, and gives you exercises here and things to build in the Godot editor on your own computer.
What you’ll be able to do
Find your way around the Godot editor, and build games out of scenes and nodes.
Write clear, typed GDScript: variables, functions, classes, signals and resources.
Make a 2D game with input, physics, animation, tile maps, a camera, a user interface, sound and saving.
Give enemies behaviour with state machines and navigation, and build a 3D character and level.
Write shaders, structure a larger project with autoloads and custom resources, and profile and export it.
Extend the editor with tool scripts and plugins, and know when to reach for GDExtension or multiplayer.
Modules
What a game engine is, and meeting Godot· 10 slides
The editor, scenes and nodes· 10 slides
GDScript from zero· 10 slides
Functions, classes and types in GDScript· 10 slides
Moving in 2D: input and the game loop· 11 slides
Physics bodies and collisions· 11 slides
Signals: nodes talking to each other· 10 slides
Instancing, groups and scene composition· 11 slides
Sprites, animation and tweens· 10 slides
User interfaces with Control nodes· 11 slides
Tile maps, cameras and levels· 12 slides
Sound, particles and game feel· 11 slides
Game state, resources and saving· 11 slides
Enemies: state machines and pathfinding· 12 slides
Into 3D: nodes, meshes, lights and cameras· 12 slides
A 3D character: movement, physics and the camera· 11 slides
Shaders and visual effects· 12 slides
Architecture: autoloads, custom resources and patterns· 11 slides
Performance, debugging and shipping· 11 slides
Expert Godot· 12 slides
Interactive parts (exercises, code to run, walkthroughs, flashcards, checklists) are drawn still and marked “Interactive · disabled in the PDF”. Do them in the app; the answers are at the end.
Module 1: What a game engine is, and meeting Godot
Godot1 / 219
Module 1 · What a game engine is, and meeting Godot
A game is a loop that runs many times a second
To you, a game is a world you play in. To a computer, it is a short list of jobs repeated very fast: read what the player is pressing (input), work out what changes (update: the hero moves, a coin is collected, a timer ticks), and paint the new picture on the screen (draw). Then it starts again. One trip round the loop makes one frame. Do it 60 times a second and the pictures blur into motion, like the pages of a flip book.
Ask the keyboard, mouse and gamepad what is pressed right now.
Change the world: the player moves right a little, the enemy turns.
Paint everything in its new place.
Start again for the next frame, many times every second.
The game loop. Every game, from a puzzle to a racing game, has this shape.
Game loop
The repeating cycle of input, update and draw that every game runs.
Frame
One trip round the loop, and the single picture it draws.
Frame rate
How many frames are drawn each second, in frames per second (fps).
Godot2 / 219
Module 1 · What a game engine is, and meeting Godot
Each frame has a time budget
If a game runs at 60 frames per second, each frame has one sixtieth of a second for all of its work: about 16.7 milliseconds (ms). Take longer and the next picture is late, and the player sees a stutter. Faster screens leave even less time per frame. This budget is why games care so much about doing work efficiently.
Milliseconds available for each frame at common frame rates: 1000 divided by the frame rate.
QuizInteractive · disabled in the PDF
A game runs at 30 frames per second. Roughly how long does each frame have?
A game runs at 30 frames per second. Roughly how long does each frame have?
Godot3 / 219
Module 1 · What a game engine is, and meeting Godot
An engine does the work every game needs
You could write the loop yourself, and then also code how to draw images on the graphics card, how to read a gamepad on Windows, macOS and Linux, how to play sound, how to make things bounce off walls, and how to save it all as an app. Every game needs those, and they are hard. A game engine is software that already does them, so you can spend your time on what makes your game yours.
Your gamelevels, characters, rules, art, sound
The enginedrawing, physics, audio, input, animation, UI, export
The computeroperating system, graphics card, speakers
Your game sits on the engine, which sits on the computer.
QuizInteractive · disabled in the PDF
Which of these would you normally write yourself, rather than get from the engine?
Which of these would you normally write yourself, rather than get from the engine?
Godot4 / 219
Module 1 · What a game engine is, and meeting Godot
Godot is free, open source and yours to keep
Godot (said “GOD-oh”) was started by Juan Linietsky and Ariel Manzur, who used it in-house for years before releasing it as open source in 2014. Godot 4.0 came out in 2023 with a new renderer and a much improved language. This course teaches Godot 4, and says so when something needs a later 4.x release. Thousands of people contribute to it, and a non-profit, the Godot Foundation, supports its development.
MIT licence: you may use it for anything, including games you sell, with no fees or royalties, and you own what you make.
Small and quick: the download is tens of megabytes and opens in seconds, with no account to create.
Runs everywhere you work: Windows, macOS and Linux, and exports games to those, the web, Android and iOS.
Open source: you can read every line of the engine, and change it if you need to.
One condition. The MIT licence asks one thing: include Godot’s copyright and licence notice with your game, for example in the credits or a licence screen. The licence page says exactly what to copy.
QuizInteractive · disabled in the PDF
You sell your Godot game for 10 dollars and it sells well. What do you owe the Godot project?
You sell your Godot game for 10 dollars and it sells well. What do you owe the Godot project?
Godot5 / 219
Module 1 · What a game engine is, and meeting Godot
Godot makes both 2D and 3D games
Many engines draw 2D games as flat 3D. Godot has a real 2D engine, measured in pixels, alongside its 3D one, measured in metres. Each has its own nodes (the building blocks you will meet next module), and the names say which is which: Sprite2D and Camera2D for 2D, MeshInstance3D and Camera3D for 3D.
2D
3D
Unit
Pixels
Metres
Axes
x right, y down
x right, y up, z towards you
Typical nodes
Sprite2D, CharacterBody2D
MeshInstance3D, CharacterBody3D
Good for
Platformers, puzzles, top-down games
Shooters, racing, exploration
Tip. This course builds 2D games first, because every idea (scenes, scripts, physics, signals) is the same in 3D and easier to see in 2D. Module 15 then takes you into the third dimension.
Godot6 / 219
Module 1 · What a game engine is, and meeting Godot
You write games in GDScript, or in C#
To tell the engine your game’s rules, you write scripts. Godot’s own language, GDScript, was designed for it: it reads a lot like Python, blocks are shown by indenting, and the editor knows every node and property, so it suggests names as you type. Godot can also run C#, a language used widely in other engines and business software, in a separate “.NET” download.
A whole GDScript script. When this node starts, it prints a line. Module 3 explains every word.
GDScript
Built into the standard download
Short, made for games, quick to change
Runs on every platform Godot exports to
What most tutorials and the docs use
C#
Needs the .NET edition of Godot
Familiar if you know C# or Unity
Faster for heavy number-crunching
Some platforms, such as the web, have had limits in Godot 4: check the docs
QuizInteractive · disabled in the PDF
You are new to programming and want to make a small 2D game that runs in a web browser. Which is the better first choice?
You are new to programming and want to make a small 2D game that runs in a web browser. Which is the better first choice?
Godot7 / 219
Module 1 · What a game engine is, and meeting Godot
Installing Godot takes one download
Godot has no installer. You download one file from godotengine.org, unzip it if it is zipped, and open the program inside. That is the whole editor. You can keep several versions side by side, which is handy when an old project needs an old version. It is also on Steam and itch.io if you prefer a store that updates it for you.
Put in orderInteractive · disabled in the PDF
Put the steps for getting Godot running in order.
1.Choose the latest stable Godot 4 for your operating system (the standard edition, not .NET)
2.See the Project Manager, which lists your projects
3.Unzip the download into a folder you will find again
4.Open the download page on godotengine.org
5.Open the Godot program in that folder
Versions matter. Godot 3 and Godot 4 are different: scripts, node names and project files changed. Projects open in a newer 4.x, but not cleanly across the 3 to 4 jump. When you follow a tutorial, check it says Godot 4.
Godot8 / 219
Module 1 · What a game engine is, and meeting Godot
The Project Manager makes and opens projects
A project is a folder holding everything for one game: scenes, scripts, images, sounds. The Project Manager lists your projects and makes new ones. Click Create, give the project a name, choose an empty folder, pick a renderer (how Godot draws), and Godot opens the editor on it.
Renderer
Draws with
Choose it for
Forward+
Vulkan, Direct3D 12 or Metal
Desktop games with the best 3D lighting
Mobile
Vulkan, Direct3D 12 or Metal, with fewer features
Phones and tablets
Compatibility
OpenGL 3 / WebGL 2
The web, older computers, simple 2D games
space-rocks/
project.godot# the project's settings, as text
icon.svg# the default icon
.godot/# Godot's cache: never edit, don't commit
A new project. project.godot is what makes a folder a Godot project.
QuizInteractive · disabled in the PDF
Your first game is a 2D puzzle you want people to play in their browser. Which renderer fits?
Your first game is a 2D puzzle you want people to play in their browser. Which renderer fits?
Godot9 / 219
Module 1 · What a game engine is, and meeting Godot
Your first scene says hello
In the new project, the left of the editor offers to make a root for a scene. Choose 2D Scene: Godot makes a Node2D; double-click its name and rename it Main. Click the plus button over it to add a child, search for Label, and add it. In the Inspector on the right, type “Hello, Godot” into its Text. Save with Ctrl+S (Cmd+S on a Mac) as main.tscn. Press F5 (Cmd+B on a Mac) to run: Godot asks for a main scene, so choose the current one, and a window opens with your words in it.
What you saved, opened in a text editor. A scene is plain text: a list of nodes, their parents and the properties you changed.
QuizInteractive · disabled in the PDF
The first time you press F5, Godot asks you to choose a main scene. Why?
The first time you press F5, Godot asks you to choose a main scene. Why?
Try it in GodotInteractive · disabled in the PDF
Godot10 / 219
Module 1 · What a game engine is, and meeting Godot
Recap
A game is a loop: read input, update the world, draw a frame, many times a second.
At 60 fps each frame has about 16.7 ms for all of its work.
An engine provides what every game needs (drawing, physics, sound, input, export), so you write only your game.
Godot is free and open source under the MIT licence: no fees, and your game is yours.
It has a true 2D engine in pixels with y pointing down, and a 3D engine in metres with y up.
You script it in GDScript (built in) or C# (the .NET edition).
The Project Manager makes projects; a project is a folder with a project.godot file.
FlashcardsInteractive · disabled in the PDF
The three steps of the game loop
Input, update, draw.
Frame
One trip round the game loop, and the picture it draws.
Time per frame at 60 fps
About 16.7 ms: 1000 ms divided by 60.
Game engine
Software that does the work every game needs, so you write only your game.
Godot’s licence
MIT: free for any use, no royalties; include its notice.
Which way does y point in Godot 2D?
Down. In 3D, y points up.
project.godot
The file that makes a folder a Godot project, holding its settings.
Compatibility renderer
Draws with OpenGL 3 / WebGL 2: for the web and older machines.
Module 2: The editor, scenes and nodes
Godot11 / 219
Module 2 · The editor, scenes and nodes
The editor is a viewport surrounded by docks
The middle of the editor is the viewport, where you see and arrange your game. Around it are docks, panels with one job each. They can be dragged and rearranged, but every Godot install starts with the same layout, and tutorials assume it.
The Scene dock lists the nodes of the scene you are editing, as a tree.
Select one and the Inspector shows its properties, ready to change.
The FileSystem dock shows the project folder: scenes, scripts, images.
The viewport shows the scene; drag images from the files to add them.
The bottom panel holds the Output (printed messages and errors) and more.
The Signals and Groups docks, tabs beside the Inspector, list the node’s signals and groups. Before Godot 4.6 they were two tabs of one Node dock.
The default layout, roughly as it sits on screen.
Viewport
The big central area where you see and arrange a scene.
Dock
A panel around the viewport with one job, such as the Inspector.
Inspector
The dock that shows and edits the properties of the selected node.
Godot12 / 219
Module 2 · The editor, scenes and nodes
Workspaces switch what the viewport shows
Across the top of the editor are the workspaces: 2D, 3D, Script, Game (since Godot 4.4, the running game shown inside the editor) and AssetLib (free add-ons made by the community). Godot usually switches for you: select a 2D node and you see the 2D view, click a script and you see the code editor. The bottom panel works the same way: Output shows what your game prints and the errors it hits; Debugger shows errors in detail; Animation appears when you edit an animation.
QuizInteractive · disabled in the PDF
Your script prints “Game started”. Where do you see it?
Your script prints “Game started”. Where do you see it?
Godot13 / 219
Module 2 · The editor, scenes and nodes
A node is one building block with one job
Everything in a Godot game is made of nodes. Each kind does one thing: a Sprite2D shows an image, a Camera2D decides what part of the world is on screen, a Timer counts down, an AudioStreamPlayer plays a sound. Godot has hundreds of node types. You don’t learn them all; you learn the dozen you use most and look up the rest in the built-in help (press F1).
Node
What it does
Node2D
A point in 2D space: position, rotation, scale. The base of 2D things.
Sprite2D
Shows an image (a texture).
CollisionShape2D
Gives a body a shape to collide with.
Camera2D
Chooses what part of the 2D world the screen shows.
Label
Shows text in a user interface.
Timer
Waits a time, then says so.
AudioStreamPlayer
Plays a sound or music.
Nodename, children
CanvasItemcan be drawn in 2D
Node2Dposition, rotation
Sprite2D
Camera2D
Controluser interface
Label
Button
Timer
Node types inherit from each other: a Sprite2D is a Node2D, so it has a position too.
QuizInteractive · disabled in the PDF
Using the tree above, which of these has a position it inherits from Node2D?
Using the tree above, which of these has a position it inherits from Node2D?
Godot14 / 219
Module 2 · The editor, scenes and nodes
Nodes form a tree, and children follow their parent
Nodes are arranged in a tree: each node has one parent and any number of children. Building a player means putting a few nodes together: a body that moves, an image to show, a shape to collide with, a camera to follow. A child’s position is measured from its parent, so when the parent moves, its children move with it.
PlayerCharacterBody2D
Sprite2Dwhat you see
CollisionShape2Dwhat collides
Camera2Dfollows the player
A player made of four nodes. Move Player and the sprite, the shape and the camera all come along.
QuizInteractive · disabled in the PDF
A Sprite2D is a child of Player. The sprite’s position is (0, -20). Player moves from (100, 300) to (150, 300). Where is the sprite on screen now?
A Sprite2D is a child of Player. The sprite’s position is (0, -20). Player moves from (100, 300) to (150, 300). Where is the sprite on screen now?
Godot15 / 219
Module 2 · The editor, scenes and nodes
A scene is a tree of nodes saved to a file
When you save a tree of nodes, you get a scene: a .tscn file. A scene is Godot’s unit of reuse. Make a player scene once, then put it into a level as many times as you like; each copy is an instance. Levels are scenes, menus are scenes, a coin is a scene. A whole game is scenes inside scenes, and the one running is a single big tree.
Level1level_1.tscn
Playerinstance of player.tscn
Coininstance of coin.tscn
Coin2instance of coin.tscn
HUDinstance of hud.tscn
A level scene with instances of other scenes in it. Each instance shows as one node, with its own tree inside.
res://
player/
player.tscn
player.gd
player.png
coin/
coin.tscn
levels/
level_1.tscn
main.tscn
One way to arrange a small project: a folder per thing, its scene and script side by side.
Tip. Change coin.tscn and every coin in every level changes with it. To add an instance, drag a .tscn from the FileSystem dock into the viewport, or click the chain-link button at the top of the Scene dock.
Godot16 / 219
Module 2 · The editor, scenes and nodes
The Inspector edits a node’s properties
Select a node and the Inspector lists its properties, grouped by the class they come from: a Sprite2D shows its own Texture and Offset, then the Transform it gets from Node2D, then what it gets from CanvasItem, such as Modulate (a colour tint) and Visible. A property you changed shows a circular arrow beside it: click it to put the default back. Hover over a property name to read what it does.
Some properties hold a resource, a piece of data such as an image, a shape or a sound. A Sprite2D’s Texture holds an image; a CollisionShape2D’s Shape holds a rectangle or circle. Click a resource to open and edit it in place.
Put in orderInteractive · disabled in the PDF
Put the steps for building a simple player scene in order.
1.Save the scene as player.tscn
2.Add a Sprite2D child and drag an image into its Texture in the Inspector
3.Make a new scene (Scene, then New Scene)
4.Choose Other Node and pick CharacterBody2D as the root, renamed Player
5.Add a CollisionShape2D child and give it a new RectangleShape2D in Shape
Godot17 / 219
Module 2 · The editor, scenes and nodes
Run the whole game, or just this scene
Key
What it runs
F5
The project, from its main scene
F6
Only the scene you are editing
F8
Stops the running game
The buttons for these are at the top right of the editor (on macOS the shortcuts differ; hover over a button to see its own). The main scene is set in the project settings, or by right-clicking a scene in the FileSystem dock and choosing Set as Main Scene. Testing a scene on its own with F6 is a habit worth having: if player.tscn runs and moves by itself, it will work in every level.
QuizInteractive · disabled in the PDF
Your main scene is the title menu. You are editing level_3.tscn and want to try it without clicking through the menu. What do you press?
Your main scene is the title menu. You are editing level_3.tscn and want to try it without clicking through the menu. What do you press?
Godot18 / 219
Module 2 · The editor, scenes and nodes
Project settings are saved in project.godot
Open Project, then Project Settings for settings that apply to the whole game: its name, the main scene, the window size, the input actions (module 5), and much more. Turn on Advanced Settings at the top right to see every setting. Only what you change is saved, as text, into project.godot.
A setting’s path, such as display/window/size/viewport_width, is its section plus its key. Code can read one with ProjectSettings.get_setting().
Tip. Keep projects in version control such as Git. Scenes, scripts and project.godot are text, so changes are easy to see. Leave out the .godot/ folder: it is a cache Godot rebuilds.
Godot19 / 219
Module 2 · The editor, scenes and nodes
res:// is the project, user:// is the player’s
Godot names files with its own paths, so the same path works on every computer. res:// is the project folder: res://player/player.tscn. In an exported game these files are packed inside the game and are read-only. user:// is a folder the game may write to, somewhere in the player’s own files, for save games and settings.
Path
Points at
Writable in a shipped game
res://
The project folder (packed into the game when exported)
No
user://
A folder for this game in the player’s data folder
Yes
QuizInteractive · disabled in the PDF
Where should your game write the player’s save file?
Where should your game write the player’s save file?
Case matters once exported. On Windows and macOS, Player.png and player.png are usually the same file, so a wrong case works in the editor. Inside an exported game’s pack, paths are case-sensitive, and the file is suddenly “not found”. Use lowercase file names with underscores, as Godot’s style guide suggests, and the problem never comes up.
Godot20 / 219
Module 2 · The editor, scenes and nodes
Recap
The viewport is in the middle; the Scene, FileSystem, Inspector, Signals and Groups docks sit around it.
A node is a building block with one job; node types inherit properties from each other.
Nodes form a tree. A child’s position is relative to its parent, so it follows it.
A scene is a saved tree (.tscn); instances of scenes go into other scenes.
The Inspector edits properties, and some properties hold resources such as textures and shapes.
F5 runs from the main scene, F6 runs the current scene, F8 stops.
Project settings live in project.godot; res:// is the project, user:// is writable.
FlashcardsInteractive · disabled in the PDF
Node
A building block with one job, such as showing an image or counting down.
Scene
A tree of nodes saved as a .tscn file, reusable as instances.
Instance
One copy of a scene placed inside another scene.
Inspector
The dock that shows and edits the selected node’s properties.
Resource
A piece of data a property holds: a texture, a shape, a sound.
F5 and F6
F5 runs the project from the main scene; F6 runs the open scene.
res://
The project folder; read-only in an exported game.
user://
A writable folder for the game’s saves and settings.
Try it in GodotInteractive · disabled in the PDF
Module 3: GDScript from zero
Godot21 / 219
Module 3 · GDScript from zero
A script gives a node behaviour
Nodes do their one job by themselves. To give one your own behaviour, attach a script: select the node, click the scroll icon with a plus at the top of the Scene dock (Attach Script), keep the language as GDScript, and press Create. Godot makes a .gd file and opens the Script workspace. A script is a list of instructions the computer follows in order, top to bottom.
WalkthroughInteractive · disabled in the PDF
res://main.gd
1extendsNode2D23# Runs once, when this node is ready.4func_ready()->void:5print("Hello, Godot")6print(2+3)
Lines 1:extends Node2D: this script adds to a Node2D. It must match (or be a parent type of) the node it is attached to.
Lines 3: A comment: everything after # is a note for people. Godot ignores it.
Lines 4:func starts a function, a named block of instructions. Godot calls _ready for you when the node enters the game. -> void says it gives nothing back.
Lines 5-6: The function’s body, indented by one tab. Indenting is how GDScript knows these lines belong to _ready. print writes to the Output panel: Hello, Godot, then 5.
Indentation is the grammar. Many languages use braces to mark blocks; GDScript uses indentation, and a line indented wrongly is an error. Godot’s editor indents with tabs. Don’t mix tabs and spaces in one file: Godot will refuse to run it.
Godot22 / 219
Module 3 · GDScript from zero
Variables remember values
A variable is a name for a value the game remembers: the score, the player’s name, whether the door is open. You make one with var, a name, and a starting value after =. Later you can change it with = again. Every value has a type, the kind of thing it is; these four are the ones you use all the time.
Prints Ada has 10 points. print joins its values with nothing in between, so the spaces are in the strings.
QuizInteractive · disabled in the PDF
What does print(7 / 2) show?
What does print(7 / 2) show?
Godot23 / 219
Module 3 · GDScript from zero
Type hints let Godot catch mistakes
A plain var can hold any type, and can switch type later. That freedom hides mistakes. Add a type hint with a colon, var score: int = 0, and Godot refuses a value of the wrong type, such as a string, before the game even runs. Writing := asks Godot to infer the type from the starting value: var speed := 200.0 is a float for good. This course types everything, and so should you.
Untyped
var lives =3funclose_life()->void: lives ="none"# allowed, and wrong
Typed
var lives:int=3var speed :=200.0# inferred: floatfunclose_life()->void: lives ="none"# error, caught at once
QuizInteractive · disabled in the PDF
What type does var gold := 50 give gold?
What type does var gold := 50 give gold?
Note. A const is a value that never changes: const MAX_LIVES := 3. Names of constants are written in capitals, by convention; variables and functions use snake_case, lower case with underscores.
Godot24 / 219
Module 3 · GDScript from zero
Operators calculate and compare
Operator
Means
Example
Result
+ - * /
Add, subtract, multiply, divide
10 - 4 * 2
2
%
Remainder of a whole division
17 % 5
2
**
Power
2 ** 3
8
== !=
Equal, not equal
3 == 3
true
< > <= >=
Less, greater, or equal
5 >= 6
false
and or not
Combine true and false
true and not false
true
+= -=
Change a variable by an amount
score += 10
score is 10 more
* and / happen before + and -, as in school maths; use brackets to change the order. + also joins strings, but only strings: "Score: " + str(score) turns the number into text first. A tidier way is a format string, where %d is filled in with a whole number and %s with anything: "Score: %d" % score.
Fill the blanksInteractive · disabled in the PDF
Add 25 to the score, then print Score: 125 using a format string.
if runs its indented block only when its condition is true. elif (else if) is checked only when everything before it was false, and else catches whatever is left. Exactly one block runs, the first whose condition holds. Each line that opens a block ends with a colon.
GDScript
1funcdescribe(health:int)->void:2if health <=0:3print("Defeated")4elif health <30:5print("Badly hurt")6else:7print("Fine")
With 20, the first test fails and the second passes: Badly hurt. The order matters: check 0 before 30, or 0 would count as badly hurt.
Find the bugInteractive · disabled in the PDF
Godot won’t run this script. Which line is wrong?
GDScript
Godot26 / 219
Module 3 · GDScript from zero
match picks one branch from many values
When you compare one value against a list of possibilities, match is tidier than a chain of elifs. Each pattern is followed by a colon and its block. Several values can share one branch, separated by commas, and _ matches anything, so it goes last, as the fallback. Only the first matching branch runs.
With the function above, what does react("potion") print?
With the function above, what does react("potion") print?
Godot27 / 219
Module 3 · GDScript from zero
for and while repeat work
A loop runs a block many times. for goes through a sequence, giving each item a name in turn. range(n) is the numbers from 0 up to, but not including, n; range(a, b, step) counts from a towards b in steps. while repeats as long as its condition stays true, so something inside must eventually make it false. break leaves a loop early and continue skips to the next turn.
What does for i in range(2, 10, 3): print(i) print?
What does for i in range(2, 10, 3): print(i) print?
Godot28 / 219
Module 3 · GDScript from zero
Arrays hold lists; dictionaries look things up
An array is an ordered list in square brackets. Items are numbered from 0, so items[0] is the first and items[-1] the last. A typed array, Array[String], only accepts strings. A dictionary pairs keys with values, in braces, so you can look a value up by its key, like a word in a dictionary.
A function is a named, reusable block. It can take parameters, values passed in when it is called, each with a type, and can give back a value with return. The type after -> says what it returns; void means nothing. A parameter can have a default, used when the caller leaves it out.
GDScript
1funcdamage_after_armour(damage:int, armour:int=0)->int:2var result := damage - armour3if result <1:4return15return result67func_ready()->void:8print(damage_after_armour(10,4))# 69print(damage_after_armour(10))# 1010print(damage_after_armour(3,9))# 1
return ends the function at once. Every hit does at least 1 damage.
Fill the blanksInteractive · disabled in the PDF
Finish is_rich, which takes a number of coins and answers whether it is 100 or more.
GDScript
1is_rich(coins:)->:2 coins >=100
Tip. A comment starting with ## above a function or variable is a documentation comment: Godot shows it in the built-in help and when you hover over the name. Use it for anything someone else (or you, next month) will call.
Godot30 / 219
Module 3 · GDScript from zero
Recap
A script extends a node type and adds behaviour; _ready runs when the node starts.
Blocks are marked by indentation with tabs, and a line opening a block ends in a colon.
int, float, String and bool are the everyday types. 7 / 2 is 3; 7 / 2.0 is 3.5.
Type everything: var x: int = 0, or var x := 0 to infer.
if/elif/else and match choose; for and while repeat; range leaves out its end.
Arrays are lists from index 0; dictionaries map keys to values, and get reads them safely.
Functions take typed parameters and return a typed value.
FlashcardsInteractive · disabled in the PDF
extends
Names the node type a script adds to.
:=
Make a variable whose type is inferred from its starting value.
= and ==
= assigns a value; == compares two values.
What is 17 % 5?
2: the remainder after dividing 17 by 5.
range(4)
The numbers 0, 1, 2, 3: from 0, leaving out the end.
match with _
_ matches anything: the fallback branch, written last.
dict.get(key, default)
The value for the key, or the default if it isn’t there.
-> void
The function gives nothing back.
Try it in GodotInteractive · disabled in the PDF
Module 4: Functions, classes and types in GDScript
Godot31 / 219
Module 4 · Functions, classes and types in GDScript
Every script is a class
A class is a blueprint: it says what data a kind of thing has and what it can do. Every GDScript file is one. Its vars at the top are the class’s properties (each node with the script gets its own copy) and its funcs are methods. extends says which class it builds on. Add class_name and the class gets a global name, usable as a type anywhere in the project, and listed in the Add Node dialog.
Lines 1:class_name Player: a global name. Other scripts can now write var target: Player.
Lines 2: It builds on CharacterBody2D, so a Playeris a CharacterBody2D, with everything that has.
Lines 4-5: Properties: every player node gets its own health and coins.
Lines 7-11: Methods: what a player can do. Another script calls them as player.collect(5).
QuizInteractive · disabled in the PDF
Two player nodes in a level both use player.gd. One calls collect(5). What happens to the other one’s coins?
Two player nodes in a level both use player.gd. One calls collect(5). What happens to the other one’s coins?
Godot32 / 219
Module 4 · Functions, classes and types in GDScript
Inheritance and super reuse a parent’s code
A class that extends another inherits its properties and methods, and may override a method by writing one with the same name. Inside the override, super() calls the parent’s version, so you add to it instead of replacing it. With class_name Enemy on the parent, the child just writes extends Enemy.
GDScript
1# enemy.gd2class_nameEnemy3extendsCharacterBody2D45var health:int=3067functake_damage(amount:int)->void:8 health -= amount910# armoured_enemy.gd, a separate file11extendsEnemy1213functake_damage(amount:int)->void:14super(amount /2)# half damage, then the usual
An armoured enemy is an enemy that takes half damage. Everything else it inherits.
Predict the outputInteractive · disabled in the PDF
Enemy._ready() starts the enemy’s patrol. Boss extends Enemy and has this _ready. Does the boss patrol?
GDScript
1extendsEnemy23func_ready()->void:4 health =3005print("The boss arrives")
Enemy._ready() starts the enemy’s patrol. Boss extends Enemy and has this _ready. Does the boss patrol?
Godot33 / 219
Module 4 · Functions, classes and types in GDScript
Enums name a fixed set of choices
An enum (enumeration) is a set of named constants, for something that can be one of a few things: a state, a direction, a weapon type. A named enum is also a type, so a variable typed with it only takes its values, and the editor completes the names. Behind the names are whole numbers: IDLE is 0, RUN 1, JUMP 2.
Enums and match go together. State machines (module 14) are built on exactly this.
Fill the blanksInteractive · disabled in the PDF
Make an enum of the four directions, a variable of that type facing up, and turn it to the left.
GDScript
1Direction{ UP, DOWN, LEFT, RIGHT }23var facing:=Direction.UP45functurn_left()->void:6 facing =
Godot34 / 219
Module 4 · Functions, classes and types in GDScript
@export shows a property in the Inspector; @onready waits for the tree
Words starting with @ are annotations: notes to Godot about the line after them. @export makes a property appear in the Inspector, so you (or a designer) can set it per node without touching code: one enemy fast, one slow. @onready delays a property’s starting value until the node is ready, which is when its children exist. $Sprite2D is short for get_node("Sprite2D"), the child with that name.
@export_range adds a slider with limits. An exported PackedScene is a slot you drag a .tscn into.
Find the bugInteractive · disabled in the PDF
Godot flags an error in this script, and it can’t work when the scene starts. Which line is the cause?
GDScript
Godot35 / 219
Module 4 · Functions, classes and types in GDScript
Static typing finds bugs before you play
With types on variables, parameters and returns, Godot checks your code as you type: calling a method that doesn’t exist, or passing a String where an int goes, is underlined at once instead of crashing an hour into play. The editor’s suggestions also get much better, and typed code runs faster, because Godot can pick specialised instructions. To make a value a more specific type, use as (it gives null if the value isn’t that type) or check first with is.
GDScript
1func_on_body_entered(body:Node2D)->void:2if body isPlayer:3var player := body asPlayer4 player.collect(1)
Inside the if, Godot knows player is a Player, so collect is checked and suggested.
Make missing types an error. In Project Settings, turn on Advanced Settings and open Debug, GDScript. Set Untyped Declaration to Warn or Error, and Godot flags every variable or function you forgot to type. Many teams do this on day one.
QuizInteractive · disabled in the PDF
body is a StaticBody2D (a wall). What is body as Player?
body is a StaticBody2D (a wall). What is body as Player?
Godot36 / 219
Module 4 · Functions, classes and types in GDScript
Godot calls your functions at set moments
Functions whose names start with _ and that Godot calls for you are callbacks (also called virtual methods). You never call them yourself; you write them, and Godot runs them at the right moment of a node’s life.
Callback
When Godot calls it
_init()
When the object is created, before it is in the tree
_enter_tree()
Each time the node is added to the scene tree
_ready()
Once, the first time it and all its children are in the tree
_process(delta)
Every drawn frame; delta is the seconds since the last one
_physics_process(delta)
Every physics step, at a fixed rate (60 a second by default)
_input(event)
For each input event: a key, a click, a mouse move
_exit_tree()
When the node leaves the tree, for example when freed
Put in orderInteractive · disabled in the PDF
A node is created and added to the game. Put its callbacks in the order Godot calls them.
1.The first _process(delta)
2._init()
3._ready()
4._enter_tree()
QuizInteractive · disabled in the PDF
A Level node has a child Player. Both have _ready. Which runs first?
A Level node has a child Player. Both have _ready. Which runs first?
Godot37 / 219
Module 4 · Functions, classes and types in GDScript
Frames, physics steps and input events
_process runs once per drawn frame, so how often depends on the computer: 60, 144, or 30 when it struggles. _physics_process runs at a steady rate, independent of drawing, so physics behaves the same everywhere. Movement of physics bodies goes in _physics_process; things that only look (a spinning coin, a timer label) can go in _process.
_input sees every event first; _unhandled_input sees only those the user interface didn’t use. Gameplay keys belong in the second. (Once paused, this node stops getting input, so it can’t unpause: module 10 shows the fix.)
Note.delta and moving at the same speed on every computer are the subject of module 5. For now: multiply anything “per second” by delta.
Going deeper
The physics rate is Physics, Common, Physics Ticks Per Second in the project settings. When drawing is faster than physics, a body only moves on physics steps, which can look jerky on a 144 Hz screen; Godot 4.3 and later offer physics interpolation (in the same settings) to smooth this. Engine.get_physics_frames() and Engine.get_process_frames() count each kind of step.
Godot38 / 219
Module 4 · Functions, classes and types in GDScript
null, and nodes that are gone
null means “no value”: an object variable that points at nothing. Using null as if it were an object is the most common error in any game: “Cannot call method … on a null value”, or “Invalid access to property or key … on a base object of type ‘Nil’”, Godot says. A related trap: when a node is deleted with queue_free(), variables that pointed at it still hold the freed object, and using it stops the game (“previously freed instance”). is_instance_valid(x) is true only for an object that exists and hasn’t been freed, so it covers both cases.
Find the bugInteractive · disabled in the PDF
The turret crashes the moment its target is destroyed. Which line needs to change?
GDScript
Godot39 / 219
Module 4 · Functions, classes and types in GDScript
Functions are values: Callables and lambdas
A function can be stored in a variable and passed around, as a Callable. Writing a method’s name without brackets, jump instead of jump(), gives its Callable; .call() runs it. A lambda is a function with no name, written right where it is needed: func(n: int) -> int: return n * 2. Arrays take them to transform themselves: map changes every item, filter keeps the items for which it returns true.
Signals (module 7) are connected to Callables, so you will write button.pressed.connect(start) a lot.
QuizInteractive · disabled in the PDF
What does print([1, 2, 3].map(func(n): return n * n)) print?
What does print([1, 2, 3].map(func(n): return n * n)) print?
Going deeper
A lambda captures the local variables around it by value, at the moment it is made: if the function later sets the local to something else, the lambda still sees the old value, and assigning to it inside the lambda changes only the lambda’s copy. Arrays and dictionaries are shared by reference, so changing their contents is seen on both sides. To keep state between calls, use a property of the script, or an array.
callable.bind(x) makes a new Callable with x added as an argument, handy for connecting many buttons to one function: button.pressed.connect(choose_level.bind(3)).
Godot40 / 219
Module 4 · Functions, classes and types in GDScript
Recap
A script is a class: properties and methods, built on what it extends. class_name names it globally.
Overriding replaces the parent’s method; super() calls the parent’s version, _ready included.
Enums name fixed choices and are types; const values never change.
@export puts a property in the Inspector; @onready waits for the children, so $ works.
Types catch mistakes early; is checks a type and as converts or gives null.
Use is_instance_valid for nodes that may have been freed. Functions are values: Callables and lambdas.
FlashcardsInteractive · disabled in the PDF
class_name
Gives a script a global name, usable as a type and in the Add Node dialog.
super()
Inside an override: call the parent class’s version of this method.
@export
Show this property in the Inspector, settable per node.
@onready
Set this property just before _ready, when children exist.
$Name
Short for get_node("Name"): the child called Name.
body as Player
The value as a Player if it is one, otherwise null.
_physics_process
Called at a fixed rate, 60 a second by default: for moving bodies.
is_instance_valid(x)
True only if x is an object that exists and hasn’t been freed.
Lambda
A function with no name, written inline: func(n): return n * 2.
Try it in GodotInteractive · disabled in the PDF
Module 5: Moving in 2D: input and the game loop
Godot41 / 219
Module 5 · Moving in 2D: input and the game loop
Actions name what the player wants to do
Your code shouldn’t ask “is the A key down?”. Some players use arrow keys, some a gamepad, some a French keyboard where A is somewhere else. Instead you make actions, named intentions like move_left or jump, and bind keys and buttons to them. Code asks about the action; players (and you) can change the keys without touching code.
Open Project, Project Settings, and the Input Map tab.
Type an action name, such as move_left, and click Add.
Click the + beside it and press a key (A), then add another (the left arrow), and a gamepad stick direction.
Repeat for move_right, move_up, move_down, and anything else the game needs.
Action
A named input, like jump, with any number of keys and buttons bound to it.
Input map
The project’s list of actions and their bindings, in the project settings.
Dead zone
How far a stick must move before it counts, so a resting stick reads as zero.
Note. Godot comes with actions named ui_left, ui_accept and so on, used by menus. You can use them for a quick test, but give your game its own actions, so changing the menu keys doesn’t change the gameplay.
Godot42 / 219
Module 5 · Moving in 2D: input and the game loop
Ask Input whether an action is held, pressed or released
Call
True when
Input.is_action_pressed("fire")
The action is held down, on every frame it is held
Input.is_action_just_pressed("jump")
Only on the one frame it went down
Input.is_action_just_released("jump")
Only on the one frame it came up
GDScript
1func_process(_delta:float)->void:2ifInput.is_action_pressed("fire"):3print("Firing")# many times a second4ifInput.is_action_just_pressed("jump"):5print("Jump")# once per press
QuizInteractive · disabled in the PDF
The player holds the jump button for half a second, and the character jumps about 30 times. Which call was probably used?
The player holds the jump button for half a second, and the character jumps about 30 times. Which call was probably used?
Godot43 / 219
Module 5 · Moving in 2D: input and the game loop
get_axis and get_vector turn input into a direction
Input.get_axis("move_left", "move_right") gives a number from -1 to 1: -1 for left, 1 for right, 0 for neither (or both), and in between for a half-tilted stick. Input.get_vector does the same for two axes at once, giving a Vector2, a pair of numbers (x, y), that points where the player wants to go. Its four actions are in the order: negative x, positive x, negative y, positive y.
GDScript
1var turn :=Input.get_axis("turn_left","turn_right")2var direction :=Input.get_vector("move_left","move_right",3"move_up","move_down")
Up is the negative y direction, because y points down in 2D.
Length 1
Added length1.41
get_vector length1
Drag the inputs to (1, 1), right and down together. Added naively, the arrow is about 1.41 long, so diagonals are 41% faster. get_vector limits it to 1. (The graph draws y upwards.)
QuizInteractive · disabled in the PDF
Why use get_vector rather than reading two get_axis values into a Vector2 yourself?
Why use get_vector rather than reading two get_axis values into a Vector2 yourself?
Godot44 / 219
Module 5 · Moving in 2D: input and the game loop
Positions and directions are Vector2s
A 2D node’s position is a Vector2: x pixels to the right and y pixels down from its parent’s origin (for the root of a scene, the top-left of the window). Vectors add and scale like arrows: adding moves, multiplying by a number makes them longer or shorter. A few methods come up constantly.
Written
Gives
Vector2(3, 4)
A vector with x 3 and y 4
Vector2.ZERO, Vector2.RIGHT, Vector2.UP
(0, 0), (1, 0), (0, -1)
a + b, a * 2.0
The sum of two vectors; a vector twice as long
v.length()
Its length: 5 for (3, 4)
v.normalized()
The same direction, length 1: (0.6, 0.8) for (3, 4)
a.distance_to(b)
How far apart two points are
a.direction_to(b)
The length-1 direction from a towards b
Fill the blanksInteractive · disabled in the PDF
Make the enemy step 50 pixels towards the player, and print how far apart they are.
Multiply by delta to move per second, not per frame
_process(delta) gets delta, the seconds since the last frame: about 0.0167 at 60 fps. Move 5 pixels per frame, and a fast computer moves the player further each second than a slow one. Move speed * delta per frame, and the player covers speed pixels per second, whatever the frame rate: fast computers just take smaller steps more often.
Pixels moved in one second with position.x += 5 (per frame). With position.x += 300 * delta it is 300 at every frame rate.
QuizInteractive · disabled in the PDF
A ship moves with position.x += 200.0 * delta. The game drops from 60 to 30 fps. How far does it move in one second?
A ship moves with position.x += 200.0 * delta. The game drops from 60 to 30 fps. How far does it move in one second?
Godot46 / 219
Module 5 · Moving in 2D: input and the game loop
Moving a sprite with the keyboard
WalkthroughInteractive · disabled in the PDF
res://player/player.gd
1extendsSprite2D23@exportvar speed:float=300.045func_process(delta:float)->void:6var direction :=Input.get_vector("move_left","move_right",7"move_up","move_down")8 position += direction * speed * delta
Lines 1: A Sprite2D shows an image and has a position: enough to move around.
Lines 3: Speed in pixels per second, exported so you can tune it in the Inspector.
Lines 5: Every frame, Godot calls _process with the seconds since the last one.
Lines 6-7: Where the player wants to go, length at most 1. Brackets let a call continue onto the next line.
Lines 8: Direction times speed is a velocity in pixels per second; times delta, it is this frame’s step.
Note. Moving a Sprite2D by its position ignores walls: it passes through anything. That is fine for a space game, and module 6 adds bodies that collide.
Godot47 / 219
Module 5 · Moving in 2D: input and the game loop
Velocity and acceleration make movement feel weighty
Jumping to full speed in one frame feels stiff. Real things accelerate: their velocity (speed with a direction, pixels per second) changes a little each frame. Keep a velocity variable, nudge it towards the speed you want with move_toward, which changes it by at most a given amount, and then move by it. When there is no input, the same call with a target of zero acts as friction.
Speed
Top speed
Seconds to top speed0.5
Speed over time from a standstill to a top speed of 300 pixels a second. Drag the acceleration: higher feels snappier, lower feels heavier.
Fill the blanksInteractive · disabled in the PDF
Accelerate towards the wanted velocity, then move by the velocity, both per second.
Another common way to ease is velocity = velocity.lerp(target, 0.1): move 10% of the way each frame. It is simple, but the fraction is per frame, so it eases faster at higher frame rates. The frame-rate independent version is velocity.lerp(target, 1.0 - exp(-k * delta)), with k how quickly it settles. move_toward avoids the problem: a constant acceleration is already per second.
Godot48 / 219
Module 5 · Moving in 2D: input and the game loop
rotation is in radians, and transform.x is forward
A Node2D’s rotation is an angle in radians: a full turn is TAU (about 6.28), a quarter turn PI / 2. Use rotation_degrees if you think in degrees, and deg_to_rad() to convert. Positive rotation turns clockwise on screen, because y points down. Draw your sprites facing right: at rotation 0 they then face along transform.x, the node’s own forward direction, which is (1, 0) turned by its rotation. look_at(point) turns a node so its forward points at a position.
GDScript
1# A turret aiming at the mouse2func_process(_delta:float)->void:3look_at(get_global_mouse_position())45# A ship that turns, then pushes forward6func_process(delta:float)->void:7 rotation +=Input.get_axis("turn_left","turn_right")*3.0* delta8ifInput.is_action_pressed("thrust"):9 position += transform.x *250.0* delta
Two separate scripts. 3.0 radians a second is just under half a turn.
QuizInteractive · disabled in the PDF
A ship drawn facing right has rotation = PI / 2. Which way is transform.x pointing on screen?
A ship drawn facing right has rotation = PI / 2. Which way is transform.x pointing on screen?
Godot49 / 219
Module 5 · Moving in 2D: input and the game loop
Wrapping round the edges of the screen
In a game like Asteroids, flying off the right edge brings you back on the left. get_viewport_rect().size is the size of the visible area, and wrapf(value, min, max) wraps a number into a range: past max, it comes back from min. (clamp would stop you at the edge instead.)
Find the bugInteractive · disabled in the PDF
The ship wraps correctly left and right, but going down it reappears at the top far too early. Which line is wrong?
No friction: in space the ship keeps drifting until you thrust the other way. limit_length caps the speed.
Try it in GodotInteractive · disabled in the PDF
Godot51 / 219
Module 5 · Moving in 2D: input and the game loop
Recap
Bind keys and buttons to actions in the Input Map; code asks about actions.
is_action_pressed is held; is_action_just_pressed is once per press.
get_axis gives -1 to 1; get_vector gives a direction of length at most 1.
Positions are Vector2s, with y pointing down.
Multiply per-second amounts by delta so speed doesn’t depend on the frame rate.
Keep a velocity and change it with move_toward for acceleration and friction.
rotation is in radians, clockwise; transform.x is forward; wrapf wraps round the screen.
FlashcardsInteractive · disabled in the PDF
Action
A named input such as jump, with keys and buttons bound to it.
Input.get_vector(…)
A Vector2 from four actions, length at most 1, with a dead zone.
delta
Seconds since the last frame; multiply per-second amounts by it.
v.normalized()
The same direction as v, with length 1.
a.move_toward(b, amount)
a moved towards b by at most amount.
transform.x
A 2D node’s forward direction: (1, 0) turned by its rotation.
Positive rotation in 2D
Clockwise on screen, because y points down.
wrapf(v, min, max)
v wrapped into the range: past the end, back from the start.
Module 6: Physics bodies and collisions
Godot52 / 219
Module 6 · Physics bodies and collisions
Four kinds of physics object, for four jobs
Moving a sprite’s position walks it through walls. To collide, a thing must be a physics body, which Godot’s physics engine knows about. Which body you choose depends on who moves it: nobody, your code, or the physics engine. A fourth kind, the area, doesn’t collide at all; it notices when things are inside it. The fifth row, AnimatableBody2D, is a static body that code or an animation moves, carrying what stands on it.
Node
Moved by
Use it for
StaticBody2D
Nothing: it stays put
Floors, walls, rocks
CharacterBody2D
Your code, with move_and_slide()
The player, enemies
RigidBody2D
The physics engine: forces, gravity, bounces
Crates, balls, debris
Area2D
Anything; it doesn’t block
Coins, triggers, damage zones
AnimatableBody2D
Animation or code, pushing others
Moving platforms, doors
Physics body
A node the physics engine moves or collides with.
Collision
Two shapes touching, and the engine stopping them overlapping.
Physics step
One update of the physics world, 60 times a second by default.
Godot53 / 219
Module 6 · Physics bodies and collisions
A body collides with the shapes you give it
A body has no shape of its own. Give it a child CollisionShape2D, and set that node’s Shape to a resource: RectangleShape2D, CircleShape2D or CapsuleShape2D (good for characters: no corners to catch on ledges). For an outline you draw point by point, use CollisionPolygon2D. The shape is drawn in blue in the editor; turn on Debug, Visible Collision Shapes to see them while the game runs.
CrateRigidBody2D
Sprite2Dcrate.png
CollisionShape2DRectangleShape2D, 32 × 32
A crate. The sprite is what you see; the shape is what collides. They are separate, so they can differ.
Resize the shape, not the node. Drag the shape’s orange handles to change its size. Scaling the CollisionShape2D node, or the body, especially by different amounts in x and y, makes the physics behave oddly.
QuizInteractive · disabled in the PDF
You add a StaticBody2D floor with a Sprite2D, but the player falls straight through it. What is most likely missing?
You add a StaticBody2D floor with a Sprite2D, but the player falls straight through it. What is most likely missing?
Godot54 / 219
Module 6 · Physics bodies and collisions
CharacterBody2D moves with velocity and move_and_slide
A CharacterBody2D has a built-in velocity property, in pixels per second. Set it, then call move_and_slide(): Godot moves the body by velocity over one physics step, stops it at anything solid, and slides it along walls and floors instead of sticking. It uses the step’s time itself, so you don’t multiply by delta here. Do it in _physics_process.
Find the bugInteractive · disabled in the PDF
This top-down character crawls at a tiny fraction of its speed. Which line is wrong?
GDScript
Godot55 / 219
Module 6 · Physics bodies and collisions
Gravity pulls, and a jump is one upward kick
WalkthroughInteractive · disabled in the PDF
res://player/player.gd
1extendsCharacterBody2D23@exportvar speed:float=220.04@exportvar jump_velocity:float=-420.056var gravity:float=ProjectSettings.get_setting(7"physics/2d/default_gravity")89func_physics_process(delta:float)->void:10ifnotis_on_floor():11 velocity.y += gravity * delta1213ifInput.is_action_just_pressed("jump")andis_on_floor():14 velocity.y = jump_velocity1516var direction :=Input.get_axis("move_left","move_right")17 velocity.x = direction * speed1819move_and_slide()
Lines 3-4: A jump is negative, because up is negative y.
Lines 6-7: Gravity from the project settings: 980 pixels per second², by default. Every body uses the same value.
Lines 10-11: In the air, gravity changes the velocity: this one does need delta, because it is an acceleration, per second, applied to a speed.
Lines 13-14: On the floor and just pressed: set the vertical speed to the jump’s, once.
Lines 16-17: Left and right are direct: full speed or none. Add acceleration later for more feel.
Lines 19: Move, collide, slide, and update is_on_floor() for the next step.
Note. Since Godot 4.3, a body can call get_gravity(), which also includes areas that change gravity locally (such as an underwater zone). The new-script template uses it. Reading the project setting works in every 4.x version.
Godot56 / 219
Module 6 · Physics bodies and collisions
The jump’s height comes from its speed and gravity
Once off the ground, the body slows as it rises, stops at the top, and falls back: a parabola. With a jump speed and gravity , it reaches the top after seconds, at a height of . That formula is useful backwards: decide how high the jump should be (say, three tiles) and work out the speed.
Height
Peak height (px)90
Time in the air (s)0.86
Height above the ground during a jump. Drag the sliders and watch the peak.
QuizInteractive · disabled in the PDF
You double jump_velocity and keep gravity the same. What happens to the height of the jump?
You double jump_velocity and keep gravity the same. What happens to the height of the jump?
Going deeper
For a jump that is higher the longer the button is held, cut the upward speed when it is released early: if Input.is_action_just_released("jump") and velocity.y < 0.0: velocity.y *= 0.5. Two more tricks players never notice but always feel: coyote time (still allow a jump for about 0.1 s after walking off a ledge) and jump buffering (remember a jump pressed just before landing). Both are a small timer each.
Godot57 / 219
Module 6 · Physics bodies and collisions
After moving, ask what the body touched
move_and_slide() also records what happened. is_on_floor(), is_on_wall() and is_on_ceiling() say which way the body was blocked; which way is “up” comes from its up_direction, Vector2.UP by default. Each thing it bumped into is a collision, which you can read in a loop.
GDScript
1move_and_slide()23ifis_on_wall():4print("Pressed against a wall")56for i inget_slide_collision_count():7var collision :=get_slide_collision(i)8print("Touched ", collision.get_collider().name)
for i in n counts 0 to n − 1, like range(n).
QuizInteractive · disabled in the PDF
Where in _physics_process does is_on_floor() give the answer from this step’s movement?
Where in _physics_process does is_on_floor() give the answer from this step’s movement?
Godot58 / 219
Module 6 · Physics bodies and collisions
A RigidBody2D is moved by the physics engine
A RigidBody2D behaves like a real object: it falls, tumbles, bounces off others and is pushed by the player. You don’t set its position; you push it. An impulse is an instant kick (a hit, an explosion); a force is a steady push applied over time (a rocket, wind). Its mass, gravity_scale and a PhysicsMaterial with bounce and friction decide how it reacts.
GDScript
1extendsRigidBody2D23func_ready()->void:4apply_central_impulse(Vector2(300, -400))# a toss56funcblast_from(origin:Vector2)->void:7var away := origin.direction_to(global_position)8apply_central_impulse(away *600.0)
QuizInteractive · disabled in the PDF
You set a RigidBody2D’s position every frame to follow the mouse, and it jitters and pushes through things. Why?
You set a RigidBody2D’s position every frame to follow the mouse, and it jitters and pushes through things. Why?
Godot59 / 219
Module 6 · Physics bodies and collisions
An Area2D notices what enters it
An Area2D has shapes like a body, but nothing collides with it. Instead it tells you when a body or another area goes in or out, with signals (messages a node sends; module 7 is all about them): body_entered(body), body_exited(body), area_entered(area). That makes it the right tool for coins, checkpoints, spikes and an enemy’s field of view.
res://coin/coin.gd
1extendsArea2D23func_ready()->void:4 body_entered.connect(_on_body_entered)56func_on_body_entered(body:Node2D)->void:7if body isPlayer:8(body asPlayer).collect(1)9queue_free()
connect says: when the signal comes, call this function. queue_free() deletes the coin safely at the end of the frame.
Fill the blanksInteractive · disabled in the PDF
Spikes hurt any player who touches them. Connect the right signal and check the body’s type.
GDScript
1extendsArea2D23func_ready()->void:4 .connect(_on_body_entered)56func_on_body_entered(body:Node2D)->void:7if body Player:8(body asPlayer).take_damage(1)
Godot60 / 219
Module 6 · Physics bodies and collisions
Layers say what you are; masks say what you look for
By default everything collides with everything. To choose, each body and area has 32 collision layers and a mask of the same 32. The layer is what this object is. The mask is what it looks for: a body collides with, and an area notices, objects whose layer is in its mask. Name the layers in Project Settings, Layer Names, 2D Physics, so the Inspector shows “player” instead of “2”.
Object
Layer (is)
Mask (looks for)
Player
1 player
2 world, 3 enemies
Walls and floor
2 world
nothing
Enemy
3 enemies
2 world
Coin (area)
4 pickups
1 player
Player bullet (area)
5 player bullets
2 world, 3 enemies
QuizInteractive · disabled in the PDF
With the table above, an enemy walks over a coin. Does the coin’s body_entered fire?
With the table above, an enemy walks over a coin. Does the coin’s body_entered fire?
Godot61 / 219
Module 6 · Physics bodies and collisions
A raycast asks what lies along a line
A RayCast2D is an invisible line from the node to its target_position (measured from the ray itself). Every physics step it checks what the line hits first, among the layers in its mask. Uses: can the enemy see the player, or is a wall in the way? Is there floor ahead, or a ledge? is_colliding(), get_collider() and get_collision_point() give the answers.
Fill the blanksInteractive · disabled in the PDF
The guard can see the player if a ray from its eyes to the player hits the player first, not a wall.
GDScript
1@onreadyvar eyes:RayCast2D=$Eyes23funccan_see(player:Node2D)->bool:4 eyes.= eyes.to_local(player.global_position)5 eyes.force_raycast_update()6return eyes.()and eyes.()== player
Going deeper
For a one-off check without a node, query the physics space directly. It returns a dictionary, empty when nothing was hit:
GDScript
1var space :=get_world_2d().direct_space_state2var query :=PhysicsRayQueryParameters2D.create(global_position, target)3query.exclude =[get_rid()]# don't hit yourself4var hit := space.intersect_ray(query)5if hit:6print(hit.collider," at ", hit.position)
Godot62 / 219
Module 6 · Physics bodies and collisions
Recap
Static bodies stay put, character bodies move by your code, rigid bodies by physics; areas detect.
Bodies collide through child CollisionShape2Ds; resize shapes, never scale them.
Set velocity, then move_and_slide(), in _physics_process, without multiplying by delta.
Gravity adds to velocity.y times delta; a jump sets velocity.y negative once, on the floor.
Jump height is speed squared over twice gravity.
Push rigid bodies with impulses and forces; don’t set their position.
Layer: what I am. Mask: what I look for. Raycasts check what lies along a line.
FlashcardsInteractive · disabled in the PDF
CharacterBody2D
A body your code moves, with velocity and move_and_slide().
move_and_slide()
Move by velocity over one step, stop at solid things, slide along them.
is_on_floor()
Whether the last move_and_slide() ended on a floor.
Impulse
An instant kick to a rigid body, as from a hit or explosion.
Area2D
Detects bodies and areas entering it, without colliding.
Collision layer
What an object is, for collisions.
Collision mask
What an object looks for: the layers it collides with or notices.
RayCast2D
An invisible line that reports the first thing it hits.
Try it in GodotInteractive · disabled in the PDF
Module 7: Signals: nodes talking to each other
Godot63 / 219
Module 7 · Signals: nodes talking to each other
A signal announces that something happened
A doorbell doesn’t know who is home. It rings, and whoever is listening comes to the door. A signal works the same way: a node emits it when something happens (a button was pressed, a body entered an area, a timer ran out), and any node that connected a function to it gets that function called. The node that emits doesn’t need to know who listens, or whether anyone does. This idea is called the observer pattern.
A button emits pressed; two listeners react, each in its own way.
Signal
A named event a node can announce, such as pressed.
Emit
Announce a signal now, calling everything connected to it.
Connect
Ask for a function to be called each time a signal is emitted.
Handler
The function connected to a signal, often named _on_<node>_<signal>.
Godot64 / 219
Module 7 · Signals: nodes talking to each other
Nodes come with signals for their own events
Node
Signal
Emitted when
Button
pressed()
It is clicked, tapped or activated with a key
Area2D
body_entered(body)
A physics body enters one of its shapes
Timer
timeout()
Its countdown reaches zero
AnimationPlayer
animation_finished(anim_name)
A non-looping animation ends
VisibleOnScreenNotifier2D
screen_exited()
Its area leaves the screen
Any Node
tree_exited()
It has been removed from the scene tree
Select a node and open the Signals dock, a tab beside the Inspector (before Godot 4.6, the Signals tab of the Node dock): it lists every signal the node has, grouped by the class it comes from. The words in brackets are the signal’s arguments, values it passes to the handler: body_entered passes the body that came in.
QuizInteractive · disabled in the PDF
You want an explosion to delete itself once its one-shot animation, played by an AnimationPlayer, is over. Which signal fits?
You want an explosion to delete itself once its one-shot animation, played by an AnimationPlayer, is over. Which signal fits?
Godot65 / 219
Module 7 · Signals: nodes talking to each other
Connecting a signal in the editor
For nodes that are in the scene from the start, the editor can make the connection for you, and write the handler. A green icon then appears in the script’s margin next to the handler, showing it is connected; the connection itself is saved in the scene file.
Put in orderInteractive · disabled in the PDF
Put the steps for connecting a button’s pressed signal in the editor in order.
1.Open the Signals dock (before Godot 4.6, the Node dock’s Signals tab)
2.Double-click pressed()
3.Pick the node whose script should receive it (the menu’s root) and click Connect
4.Write the game’s logic in the new _on_start_button_pressed() function
5.Select the StartButton node in the Scene dock
Godot66 / 219
Module 7 · Signals: nodes talking to each other
Connecting in code: signal.connect(handler)
In Godot 4 a signal is a value you can reach with a dot: $StartButton.pressed. Its connect method takes a Callable, a function written without brackets. Code connections are visible right in the script, and they are the only way for nodes created while the game runs, such as an enemy you just spawned.
In the editor
Quick, and writes the handler for you
Saved in the scene; shown by an icon in the script
Only for nodes already in the scene
In code
Visible and searchable in the script
Works for nodes made while the game runs
Can add arguments with bind
Fill the blanksInteractive · disabled in the PDF
Connect the start button’s pressed signal to _on_start_pressed in code.
When a signal has arguments, its handler must accept them, in the same order. If a handler needs an extra value of its own, bind adds it after the signal’s arguments: one handler can then serve ten buttons, each telling it which one it was.
GDScript
1func_ready()->void:2for i inrange(3):3var button:Button=$LevelButtons.get_child(i)4 button.pressed.connect(_on_level_chosen.bind(i +1))56func_on_level_chosen(level:int)->void:7print("Loading level ", level)
pressed has no arguments, so the handler receives only the bound one.
Find the bugInteractive · disabled in the PDF
When the player walks into this area, Godot reports an error instead of printing. Which line is wrong?
GDScript
Godot68 / 219
Module 7 · Signals: nodes talking to each other
Your own signals: declare, then emit
Declare a signal at the top of a script with signal, a name in the past tense (it happened), and typed arguments if it carries values. Emit it with .emit(…). Here a health component announces every change and its owner’s death; it doesn’t care whether a health bar, a sound or a game-over screen is listening.
WalkthroughInteractive · disabled in the PDF
res://components/health.gd
1class_nameHealth2extendsNode34signalhealth_changed(new_health:int)5signaldied67@exportvar max_health:int=10089var health:int:10set(value):11 health =clampi(value,0, max_health)12 health_changed.emit(health)13if health ==0:14 died.emit()1516func_ready()->void:17 health = max_health1819functake_damage(amount:int)->void:20 health -= amount
Lines 4-5: Two signals. health_changed carries the new value; died carries nothing.
Lines 9-14: A setter: this block runs every time health is assigned, from anywhere. It keeps the value between 0 and the maximum, then emits.
Lines 16-17: Start full. Exported values are set by now, so max_health is the one from the Inspector.
Lines 19-20: Taking damage is just assigning, so the setter does the clamping and the announcing.
Fill the blanksInteractive · disabled in the PDF
Declare a coin_collected signal that carries the new total, and emit it when a coin is collected.
A Timer node waits wait_time seconds, then emits timeout. With one_shot off it starts again by itself, which suits an enemy spawning every 3 seconds; with one_shot on it stops, which suits a cooldown. autostart starts it when the scene starts; otherwise call start(). is_stopped() and time_left tell you where it is.
GDScript
1# A gun with a 0.25 s cooldown: a one-shot Timer named Cooldown2@onreadyvar cooldown:Timer=$Cooldown34func_process(_delta:float)->void:5ifInput.is_action_pressed("fire")and cooldown.is_stopped():6shoot()7 cooldown.start()
Hold fire: one shot, then nothing until the timer stops, then another. Four shots a second.
QuizInteractive · disabled in the PDF
A Timer has wait_time 2, one_shot off and autostart on. How many times does it emit timeout in the first 7 seconds?
A Timer has wait_time 2, one_shot off and autostart on. How many times does it emit timeout in the first 7 seconds?
Godot70 / 219
Module 7 · Signals: nodes talking to each other
await pauses a function until a signal comes
await waits for a signal, then carries on from the same line. The rest of the game keeps running meanwhile: only this function is paused. get_tree().create_timer(0.1) makes a throwaway timer for this, so a short pause needs no Timer node. A function that awaits becomes a coroutine: whoever calls it carries on at once, unless it awaits the call too.
If the node is freed while one of its functions is waiting, that function doesn’t resume (in a debug run Godot may print an error saying the instance is gone). That means code after an await shouldn’t be the only place that cleans something up. An await in _process is a trap: a new paused copy starts every frame.
Godot71 / 219
Module 7 · Signals: nodes talking to each other
Call down, signal up
A rule that keeps scenes reusable: a parent may call methods on its children, because it owns them and knows they exist. A child signals to whoever is above, without knowing who they are. The level, which owns both the player and the HUD, does the wiring. The player then works in any level, and with no HUD at all when you test it with F6.
The player emits; the level connects the signal to the HUD. The player never mentions the HUD.
The player’s script does get_node("/root/Level/HUD").show_coins(coins). What is the main problem?
The player’s script does get_node("/root/Level/HUD").show_coins(coins). What is the main problem?
Godot72 / 219
Module 7 · Signals: nodes talking to each other
Recap
A signal announces an event; connected handlers are called; the emitter doesn’t know them.
Built-in signals are listed in the Signals dock: pressed, body_entered, timeout and more.
Connect in the editor, or in code with signal.connect(handler), the handler without brackets.
Handlers take the signal’s arguments; bind adds your own.
Declare signal name(arg: Type) and send it with name.emit(value).
Timers emit timeout; await pauses one function until a signal, and its caller carries on.
Call down, signal up: parents call children; children emit; the owner wires them.
FlashcardsInteractive · disabled in the PDF
Observer pattern
An object announces events; others subscribe, without it knowing them.
Connect a signal in code
$Button.pressed.connect(_on_button_pressed)
Declare a signal
signal health_changed(new_health: int)
Emit a signal
health_changed.emit(health)
callable.bind(x)
A Callable that passes x as an extra argument.
Timer.one_shot
On: it stops after one timeout. Off: it restarts each time.
await signal
Pause this function until the signal comes; the game keeps running.
Call down, signal up
Parents call methods on children; children emit signals upwards.
Try it in GodotInteractive · disabled in the PDF
Module 8: Instancing, groups and scene composition
Godot73 / 219
Module 8 · Instancing, groups and scene composition
A PackedScene is a scene you can stamp out
You have dragged scenes into a level by hand. Games also need to make them while running: a bullet each time you fire, an enemy every few seconds. A saved scene, loaded in code, is a PackedScene: a resource that holds the recipe for a tree of nodes. instantiate() builds a new copy of that tree, and add_child() puts it into the game. Until it is added, it exists but isn’t part of the game: it isn’t drawn, doesn’t move and doesn’t collide.
The scene is a file, like any other.
Loading it gives a PackedScene. Do this once, not every shot.
Each instantiate() builds a fresh, separate copy of the tree.
add_child() puts it in the scene tree: _ready runs and it comes alive.
From a file on disk to a node in the running game.
PackedScene
A saved scene as a resource, ready to be instantiated.
Instantiate
Build a new copy of a scene’s tree of nodes.
Spawn
Game slang for creating something in the world while it runs.
Godot74 / 219
Module 8 · Instancing, groups and scene composition
preload, load, or an exported slot
Written
When it loads
Good for
preload("res://bullet.tscn")
When the script itself loads, before the game uses it
Things a script always needs; the path must be fixed text
load(path)
At the moment the line runs
Paths worked out while running, such as the next level
@export var scene: PackedScene
With the scene that holds the node
Letting the Inspector choose: the same gun, different bullets
Lines 3: Load the bullet scene once, with the script. A constant, since it never changes.
Lines 5: A Marker2D is an invisible point you place in the editor: here, at the end of the gun barrel.
Lines 8: A new bullet. as Bullet types it, using the bullet script’s class_name.
Lines 9: Add it to the level (the current scene’s root), not to the ship, so it flies on its own.
Lines 10-11: Place and aim it with global values: positions in the world, whoever its parent is.
QuizInteractive · disabled in the PDF
What goes wrong if shoot() calls add_child(bullet), making each bullet a child of the ship?
What goes wrong if shoot() calls add_child(bullet), making each bullet a child of the ship?
Godot76 / 219
Module 8 · Instancing, groups and scene composition
The bullet looks after itself
A good spawned scene needs nothing from whoever made it: it moves, hits, and deletes itself. VisibleOnScreenNotifier2D emits screen_exited when it leaves the screen, a perfect moment to go. has_method checks whether the thing it hit can take damage, so the bullet works on enemies, crates and anything else that writes a take_damage.
queue_free is a method, so it can be connected directly as the handler.
Free what you spawn. A bullet that is never freed keeps flying forever off screen, still moving and checking collisions. A few minutes of play is thousands of them, and the game slows down. Every spawned thing needs a way to go: leaving the screen, hitting something, or a lifetime timer.
Godot77 / 219
Module 8 · Instancing, groups and scene composition
queue_free deletes a node safely
queue_free() marks a node for deletion at the end of the current frame, when nothing is using it, along with all of its children. free() deletes it immediately, which can crash if something is in the middle of using it, such as the physics engine during a collision; you rarely need it. remove_child() takes a node out of the tree without deleting it: it lives on, invisible, until you add it back or free it.
QuizInteractive · disabled in the PDF
An enemy has a Sprite2D, a CollisionShape2D and a Health child. You call queue_free() on the enemy. What happens to the children?
An enemy has a Sprite2D, a CollisionShape2D and a Health child. You call queue_free() on the enemy. What happens to the children?
Godot78 / 219
Module 8 · Instancing, groups and scene composition
Inherited scenes share a base and change the details
Several enemies often share most of their tree: a body, a sprite, a shape, a health bar. Make enemy.tscn once, then Scene, New Inherited Scene to make slime.tscn from it. The inherited scene shows the base’s nodes (you can’t delete or rename them) and lets you change their properties and add new nodes. Change the base, and every inherited scene picks it up.
enemy.tscnbody, sprite, shape, health
slime.tscngreen sprite, slow
bat.tscnflies; adds a Wings sound
Two inherited scenes. Each changes the sprite and stats, and the bat adds a node of its own.
QuizInteractive · disabled in the PDF
In bat.tscn, inherited from enemy.tscn, you want to remove the Shadow node the base has. What happens?
In bat.tscn, inherited from enemy.tscn, you want to remove the Shadow node the base has. What happens?
Godot79 / 219
Module 8 · Instancing, groups and scene composition
Prefer composing small scenes over deep inheritance
Inheritance is a chain: a flying enemy that also shoots would need a FlyingEnemy that is also a ShootingEnemy, and a class can only extend one. Composition builds a thing out of parts instead: small scenes or nodes, each with one ability, added as children. Anything with a Health child can be hurt: the player, an enemy, a door, a barrel.
BatCharacterBody2D
Sprite2D
CollisionShape2D
Healthhealth.tscn
Hurtboxhurtbox.tscn, an Area2D
Shootershooter.tscn
A bat composed from parts. A turret could reuse Health, Hurtbox and Shooter without being an enemy at all.
Deep inheritance
Enemy, then FlyingEnemy, then FlyingShootingEnemy…
Every mix of abilities needs a new class
A change high up can break everything below
Composition
Health, Hurtbox, Shooter, Flyer: one job each
Mix them freely, by adding children
Each part can be tested on its own
Godot80 / 219
Module 8 · Instancing, groups and scene composition
Groups tag nodes so you can find them all
A group is a named tag on a node; a node can be in many. Add one in the Groups dock beside the Inspector (before Godot 4.6, the Node dock’s Groups tab), or with add_to_group("enemies"). Then get_tree().get_nodes_in_group("enemies") returns every tagged node in the game, is_in_group asks about one, and get_tree().call_group("enemies", "freeze") calls a method on all of them.
GDScript
1# In enemy.gd2func_ready()->void:3add_to_group("enemies")45# In bomb.gd6funcexplode()->void:7for enemy inget_tree().get_nodes_in_group("enemies"):8var target := enemy asNode2D9if global_position.distance_to(target.global_position)<200.0:10 enemy.take_damage(50)
Find the bugInteractive · disabled in the PDF
The “freeze all” power-up does nothing, with no error. Enemies add themselves to "enemies". Which line is wrong?
GDScript
Godot81 / 219
Module 8 · Instancing, groups and scene composition
Finding nodes: paths and unique names
Written
Finds
$Sprite2D
The child called Sprite2D (the same as get_node("Sprite2D"))
$Body/Sprite2D
A grandchild, through the child Body
$"../HUD"
A sibling: up to the parent, then down to HUD
%ScoreLabel
The node marked with a unique name, anywhere in this scene
get_node_or_null("Shield")
The child, or null if there isn’t one, without an error
@export var target: Node2D
Whatever node you pick in the Inspector
Paths break when nodes are renamed or moved. A scene unique name survives moving: right-click a node and choose Access as Unique Name, and %ScoreLabel finds it wherever it sits in that scene, even after you wrap it in three containers. An exported node property is sturdier still, since the editor keeps it pointing at the right node.
QuizInteractive · disabled in the PDF
A script uses $Panel/ScoreLabel. You put ScoreLabel inside a new MarginContainer in Panel. What happens, and what avoids it?
A script uses $Panel/ScoreLabel. You put ScoreLabel inside a new MarginContainer in Panel. What happens, and what avoids it?
Godot82 / 219
Module 8 · Instancing, groups and scene composition
SpawnPoints holds a few Marker2Ds placed in the editor; pick_random chooses one. The spawner never moves, so its children can stay put.
Put in orderInteractive · disabled in the PDF
Put the life of a spawned enemy in order.
1.It is placed at a spawn point with global_position
2.instantiate() builds a new enemy
3.The scene is loaded once as a PackedScene
4.add_child() puts it in the tree, and its _ready runs
5.It is defeated and calls queue_free()
Try it in GodotInteractive · disabled in the PDF
Godot83 / 219
Module 8 · Instancing, groups and scene composition
Recap
A PackedScene is a loaded scene; instantiate() builds a copy; add_child() brings it to life.
preload for fixed paths, load for paths built at run time, @export to choose in the Inspector.
Add projectiles to the level, not their shooter, and place them with global values.
queue_free() deletes a node and its children at the end of the frame. Free everything you spawn.
Inherited scenes share a base; composition mixes small single-purpose parts, and is usually better.
Groups tag nodes: get_nodes_in_group, call_group. Their names are strings: watch for typos.
Paths break when nodes move; unique names (%Name) and exported nodes don’t.
FlashcardsInteractive · disabled in the PDF
instantiate()
Builds a new copy of a PackedScene’s tree of nodes.
preload vs load
preload: when the script loads, fixed path. load: when the line runs.
queue_free()
Delete this node and its children safely at the end of the frame.
remove_child()
Take a node out of the tree without deleting it.
Inherited scene
A scene built on another: it can change and add, not remove.
Composition
Building a thing from small parts with one job each, as children.
call_group(group, method)
Call a method on every node in a group.
%Name
The node with that scene unique name, wherever it is in the scene.
Module 9: Sprites, animation and tweens
Godot84 / 219
Module 9 · Sprites, animation and tweens
A Sprite2D shows a texture
Drop a .png into the project folder and Godot imports it: it converts the image into a texture, a form the graphics card can draw, and remembers the settings in a .import file beside it. A Sprite2D shows a texture, centred on its position unless you turn off centered. offset shifts the picture without moving the node, handy for putting a character’s feet on its origin.
Texture
An image ready for the graphics card to draw.
Import
Godot converting a source file (png, wav, glb) into something the engine uses.
Sprite
A 2D image drawn in the game world.
Sprite sheet
One image holding many frames of animation in a grid.
Tip. Select an image in the FileSystem dock and open the Import dock (a tab beside the Scene dock) to see how it was imported. Change a setting and click Reimport. Never edit or delete .import files by hand; commit them with your images.
Godot85 / 219
Module 9 · Sprites, animation and tweens
Pixel art needs the nearest filter
When a texture is drawn bigger than its real size, the graphics card fills in the in-between pixels. The default, linear filtering, blends neighbours, which looks smooth for painted art and blurry for pixel art. Nearest filtering takes the closest pixel, keeping hard edges. Set it for the whole project in Project Settings, Rendering, Textures, Canvas Textures, Default Texture Filter, or per node with its texture_filter property.
QuizInteractive · disabled in the PDF
Your 16 × 16 pixel-art hero, scaled up 4 times, looks soft and blurry. What fixes it?
Your 16 × 16 pixel-art hero, scaled up 4 times, looks soft and blurry. What fixes it?
Godot86 / 219
Module 9 · Sprites, animation and tweens
A sprite sheet holds frames in a grid
Animating with a separate image per frame is slow to load and fiddly to manage, so frames are packed into one sprite sheet. Tell a Sprite2D how many columns (hframes) and rows (vframes) it has, and frame picks one, counting from 0 along the top row, then the next row. Changing frame over time is animation in its simplest form.
A sheet has hframes = 8. Which frame is in row 2, column 3, both counted from 0?
A sheet has hframes = 8. Which frame is in row 2, column 3, both counted from 0?
Godot87 / 219
Module 9 · Sprites, animation and tweens
AnimatedSprite2D plays named animations
An AnimatedSprite2D holds a SpriteFrames resource: a set of named animations (idle, run, jump), each a list of frames with a speed in frames per second and a loop setting. Create one in its Sprite Frames property, then edit it in the SpriteFrames panel at the bottom: add an animation, and use the grid button to cut frames from a sheet. In code, play("run") starts one; calling it again for the animation already playing doesn’t restart it.
Draw characters facing right and flip them for left: flip_h mirrors a sprite. Only change it while moving, so the character keeps facing the way it last went. When there is more than one sprite (a weapon, a hat), put them all under one Node2D called Visuals and set its scale.x to -1 or 1 to flip them together; don’t flip the physics body itself, as negative scale on bodies and shapes confuses the physics.
Find the bugInteractive · disabled in the PDF
The character faces left while running left, but snaps back to face right the moment it stops. Which line causes it?
GDScript
Godot89 / 219
Module 9 · Sprites, animation and tweens
AnimationPlayer animates any property over time
An AnimationPlayer can animate any property of any node in its scene: a position, a colour, a sprite’s frame, a light’s energy, a collision shape’s disabled. Each animation is a timeline with tracks, one per property, and keyframes: values at given times, with Godot filling in between. Select a node, click the key icon beside a property in the Inspector, and a keyframe is added at the current time. Tracks can also call methods and play sounds.
Track
Animates
Property
Any property: position, modulate, frame, visible…
Call method
Calls a function at a moment: spawn_dust() when a foot lands
Audio playback
Plays a sound at a moment
Animation
Plays another AnimationPlayer’s animation
GDScript
1@onreadyvar anim:AnimationPlayer=$AnimationPlayer23funcattack()->void:4 anim.play("attack")# moves the sword, turns its hitbox on and off5await anim.animation_finished6 anim.play("idle")
QuizInteractive · disabled in the PDF
A sword’s hitbox should only hurt during the 0.1 seconds the blade swings. What is the neatest way?
A sword’s hitbox should only hurt during the 0.1 seconds the blade swings. What is the neatest way?
Going deeper
The RESET animation holds every animated property’s resting value. With it, Godot can put a scene back the way it was after you preview an animation in the editor, so saving a scene doesn’t store some half-played pose. Godot offers to create it when you add tracks; keep it.
Godot90 / 219
Module 9 · Sprites, animation and tweens
AnimationTree manages animations as a state machine
With many animations, deciding which to play gets messy. An AnimationTree node, pointed at an AnimationPlayer, can hold a state machine: a graph of animations (states) with arrows between them (transitions), drawn in the editor. Code says which state it wants with travel(), and the tree finds the path there through the transitions, blending as it goes. A BlendSpace1D state blends between, say, walk and run by a number such as speed.
A small state machine. travel("jump") from idle follows the arrow; you can only reach land from jump.
The tree’s settings are “parameters” you reach by path. A blend space is set the same way: tree.set("parameters/move/blend_position", speed).
QuizInteractive · disabled in the PDF
With the state machine above, the character is in idle and the code calls playback.travel("land"). What happens?
With the state machine above, the character is in idle and the code calls playback.travel("land"). What happens?
Godot91 / 219
Module 9 · Sprites, animation and tweens
A tween animates a value from code
For small one-off motions, a coin popping, a door sliding, a menu fading, an animation in the editor is overkill. A tween (from “in-between”) changes a property from its current value to a target over a time. create_tween() makes one, owned by the node: it runs at once and stops if the node is freed. Its steps run one after another; parallel() makes the next step run alongside the previous one.
Lines 2: Stop the area noticing bodies, so the coin can’t be collected twice while it animates. set_deferred waits until it is safe to change physics state.
Lines 3: A new tween, owned by this coin.
Lines 4: How values move between start and end: overshoot a little, then settle.
Lines 5: Grow to 1.6 times its size over 0.2 seconds.
Lines 6: At the same time, fade out. modulate:a is the alpha (opacity) inside the modulate colour: the colon reaches into a property.
Lines 7: Then, once both are done, free the coin.
Find the bugInteractive · disabled in the PDF
Godot reports that the tweened property doesn’t exist, and the sprite never fades. Which line is wrong?
GDScript
Going deeper
Starting a second tween on the same property while the first still runs makes them fight. Keep the tween in a property and kill the old one first: if tween: tween.kill(), then tween = create_tween(). tween_interval(0.5) adds a pause, set_loops() repeats, and await tween.finished waits for the end.
Godot92 / 219
Module 9 · Sprites, animation and tweens
Easing decides how a motion speeds up and slows down
A tween’s transition is the shape of the curve: TRANS_LINEAR, TRANS_SINE, TRANS_QUAD, TRANS_CUBIC, and livelier ones like TRANS_BACK (overshoot) and TRANS_BOUNCE. Its ease says which end the curve bends at: EASE_IN starts slow and ends fast, EASE_OUT starts fast and ends gently, EASE_IN_OUT is slow at both ends. In the graph, time runs from 0 to 1 along the bottom and progress from start to target up the side.
Linear
EASE_IN
EASE_OUT
EASE_IN_OUT
EASE_OUT progress at the halfway time0.75
Power curves: 2 is TRANS_QUAD, 3 TRANS_CUBIC, 4 TRANS_QUART, 5 TRANS_QUINT. Drag the power and compare the eases.
QuizInteractive · disabled in the PDF
A pause menu slides in from the side. Which ease makes it feel responsive, arriving quickly and settling gently?
A pause menu slides in from the side. Which ease makes it feel responsive, arriving quickly and settling gently?
Godot93 / 219
Module 9 · Sprites, animation and tweens
Recap
Images are imported as textures; Sprite2D draws one. Use the Nearest filter for pixel art.
Sprite sheets hold frames in a grid: frame = row × hframes + column.
AnimatedSprite2D plays named animations from a SpriteFrames resource.
Flip with flip_h only while moving, or flip a Visuals node, never the body.
AnimationPlayer keys any property, and can call methods and play sounds on its timeline.
AnimationTree state machines move between animations along transitions with travel().
create_tween() animates values from code; transitions and eases shape the motion.
FlashcardsInteractive · disabled in the PDF
Nearest filter
Draw each pixel as a hard square when scaled: crisp pixel art.
hframes and vframes
A Sprite2D’s columns and rows of frames in its sheet.
SpriteFrames
The resource of named animations an AnimatedSprite2D plays.
Keyframe
A property’s value at one moment of an animation.
playback.travel("run")
Move the state machine to run along its transitions.
create_tween()
Make a tween owned by this node, which starts at once.
"modulate:a"
A property path into part of a property: the alpha of modulate.
EASE_OUT
Fast at the start, gentle at the end: the usual choice for UI.
Try it in GodotInteractive · disabled in the PDF
Module 10: User interfaces with Control nodes
Godot94 / 219
Module 10 · User interfaces with Control nodes
Control nodes are rectangles that lay themselves out
The game world is made of Node2Ds: points with a position. A user interface (UI), menus, health bars, score counters, dialogue boxes, is made of Control nodes: rectangles with a size, which know how to place themselves relative to their parent, react to clicks and keys, take keyboard focus, and get their look from a theme. Make one with User Interface as a scene’s root, or by adding any green node.
Node2D
Control
Is
A point: position, rotation, scale
A rectangle: position and size
Placed by
Its position
Anchors and offsets, or a container
Input
Through scripts
Built in: clicks, focus, gamepad navigation
For
The game world
Menus and on-screen information
QuizInteractive · disabled in the PDF
Which of these should be built from Control nodes?
Which of these should be built from Control nodes?
Godot95 / 219
Module 10 · User interfaces with Control nodes
Anchors pin edges to the parent; offsets add pixels
Screens come in every size, so a UI can’t be placed in fixed pixels. Each edge of a Control has an anchor, a fraction of the parent’s size from 0 (left or top) to 1 (right or bottom), and an offset, pixels from that anchor. A label with its left and right anchors at 1 and offsets of -216 and -16 is always 200 pixels wide and 16 pixels from the right edge, however wide the window. The Anchor preset menu in the toolbar (Top Right, Center, Full Rect…) sets common ones in one click.
GDScript
1# The same in code: pin to the top-right corner, and move there2$ScoreLabel.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)34# Fill the whole parent: anchors 0, 0, 1, 1 and offsets 05$Overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
set_anchors_preset alone moves only the anchors and keeps the control where it is on screen; set_anchors_and_offsets_preset also moves and sizes it, as the toolbar’s menu does.
QuizInteractive · disabled in the PDF
A button has both left and right anchors at 0.5 and offsets of -100 and 100. The window gets wider. What happens to the button?
A button has both left and right anchors at 0.5 and offsets of -100 and 100. The window gets wider. What happens to the button?
Godot96 / 219
Module 10 · User interfaces with Control nodes
Containers arrange their children for you
Placing every button by hand is slow and breaks when text changes. A container is a Control that positions and sizes its children itself: put buttons in a VBoxContainer and they stack, evenly spaced, and stay stacked when you add another. Containers nest, so most screens are a few containers inside each other.
Container
Arranges its children
VBoxContainer, HBoxContainer
In a column, or a row
GridContainer
In a grid with a set number of columns
MarginContainer
With a margin of space around them
CenterContainer
In the middle, at their smallest size
PanelContainer
On a background panel, from the theme
MainMenuControl, Full Rect
CenterContainerFull Rect
VBoxContainer
TitleLabel
PlayButton
OptionsButton
QuitButton
A title menu. Only the root needs anchors (Full Rect); the containers place the rest.
QuizInteractive · disabled in the PDF
You try to drag a button inside a VBoxContainer to a new spot, and it snaps back. Why?
You try to drag a button inside a VBoxContainer to a new spot, and it snaps back. Why?
Godot97 / 219
Module 10 · User interfaces with Control nodes
Size flags and minimum sizes guide the container
Each child tells its container how it would like to be sized with size flags, separately for horizontal and vertical. Fill stretches it across the space it is given; Expand asks for a share of any spare space; Shrink Center keeps it at its smallest size, centred. custom_minimum_size sets the smallest it may be. A label and a button in an HBoxContainer, with the label set to Expand and Fill, push the button to the far right.
Fill the blanksInteractive · disabled in the PDF
In a row, make the name label take all the spare width, and the button at least 120 pixels wide.
A bar drawn from your own images: under, progress, over
TextureRect
Shows an image in the UI, with a stretch mode
HSlider
value, step; signal value_changed(value)
LineEdit
One line of typed text; signal text_submitted(text)
CheckBox
button_pressed; signal toggled(on)
GDScript
1@onreadyvar volume:HSlider=%VolumeSlider2@onreadyvar volume_label:Label=%VolumeLabel34func_ready()->void:5 volume.value_changed.connect(_on_volume_changed)67func_on_volume_changed(value:float)->void:8 volume_label.text ="Volume: %d%%"% value
In a format string, %% prints a percent sign itself.
Godot99 / 219
Module 10 · User interfaces with Control nodes
A HUD lives on a CanvasLayer
A HUD (heads-up display) shows health, score and the like over the game. Put it inside a level as an ordinary node and, as soon as a Camera2D follows the player, the HUD scrolls away with the world. A CanvasLayer draws its children on a separate layer that ignores the camera, so they stay put on screen. Its layer number orders layers: higher is drawn on top.
Level
Playerwith Camera2D, Health
Enemies
HUDCanvasLayer, hud.tscn
HealthBarProgressBar
CoinsLabelLabel
The HUD as its own scene, instanced in the level. The level wires the player’s signals to it.
WalkthroughInteractive · disabled in the PDF
res://ui/hud.gd
1extendsCanvasLayer23@onreadyvar health_bar:ProgressBar=%HealthBar4@onreadyvar coins_label:Label=%CoinsLabel56funcshow_health(value:int)->void:7 health_bar.value = value89funcshow_coins(total:int)->void:10 coins_label.text ="Coins: %d"% total
Lines 1: The HUD’s root is the CanvasLayer itself, so the whole scene ignores the camera.
Lines 3-4: Unique names, so the layout can be rearranged in containers without breaking the script.
Lines 6-10: Methods for the level to connect signals to: $Player/Health.health_changed.connect($HUD.show_health). The HUD knows nothing about the player.
QuizInteractive · disabled in the PDF
Your score label drifts off screen as the camera follows the player. What fixes it?
Your score label drifts off screen as the camera follows the player. What fixes it?
Godot100 / 219
Module 10 · User interfaces with Control nodes
Menus change scenes with change_scene_to_file
A title menu is a scene, and so is each level. get_tree().change_scene_to_file(path) replaces the current scene with a new one: the old scene is freed and the new one becomes get_tree().current_scene. change_scene_to_packed does the same with a PackedScene you already loaded. get_tree().quit() closes the game (on desktop; a web page can’t close itself).
The old scene leaves the tree at once, and the new one is added at the end of the frame, so code after the call shouldn’t rely on either being there.
Note. Changing scene frees everything in the old one, including the score. Anything that must outlive a scene, such as the player’s coins across levels, belongs in an autoload, a node that stays for the whole game: module 13.
Godot101 / 219
Module 10 · User interfaces with Control nodes
Focus makes menus work with keys and gamepads
With a mouse you click a button; with a keyboard or gamepad, one Control has focus, shown by a highlight, and the ui_up, ui_down, ui_left and ui_right actions move focus to its neighbour, while ui_accept presses it. Godot works out the neighbours from the layout, and you can set them yourself with the focus_neighbor_* properties. Nothing has focus to begin with, so a menu must give it to a button with grab_focus().
Find the bugInteractive · disabled in the PDF
The menu works with the mouse, but a gamepad can’t select anything. There is no error, only a warning. Which line is wrong?
GDScript
Tip. When a menu is shown later (a pause menu becoming visible), call grab_focus() when you show it, not only in _ready. Test every menu once with only the keyboard: if you can’t reach a button, neither can a gamepad player.
Godot102 / 219
Module 10 · User interfaces with Control nodes
Pausing the tree, and what keeps running
get_tree().paused = true pauses the game: nodes stop getting _process, _physics_process and input, and physics stops. What happens to each node depends on its process_mode. The pause menu itself must keep running, or nothing could unpause the game.
process_mode
While the game is paused
Inherit (the default)
Does what its parent does; the root is pausable
Pausable
Stops
When Paused
Runs only while paused: good for a pause menu
Always
Runs whether paused or not: music, a debug overlay
Disabled
Never runs
Find the bugInteractive · disabled in the PDF
Pressing pause freezes the game and shows the menu, but pressing it again does nothing. Which line is wrong?
GDScript
Godot103 / 219
Module 10 · User interfaces with Control nodes
Themes style every control at once
A Theme resource holds the look of each kind of control: fonts, font sizes, colours, and StyleBoxes (the backgrounds and borders of buttons and panels, in each state: normal, hover, pressed, focus, disabled). Set a theme on a Control and all of its descendants use it; set one in Project Settings, GUI, Theme, Custom and the whole game does. For one-off changes, every control has Theme Overrides in the Inspector, also reachable in code.
Overrides are fine for a title. If you find yourself overriding every button, edit the theme instead.
QuizInteractive · disabled in the PDF
You add a full-screen ColorRect over the menu for a fade effect. Now no button can be clicked. What is the cause?
You add a full-screen ColorRect over the menu for a fade effect. Now no button can be clicked. What is the cause?
Godot104 / 219
Module 10 · User interfaces with Control nodes
Recap
Control nodes are rectangles for UI; Node2Ds are for the game world.
Anchors pin each edge to a fraction of the parent; offsets add pixels; presets set both.
Containers place their children; guide them with size flags and custom_minimum_size.
Put a HUD on a CanvasLayer so the camera doesn’t move it.
change_scene_to_file swaps scenes; what must survive belongs in an autoload.
Give a button focus with grab_focus() so keyboards and gamepads work.
Pause with get_tree().paused; a pause menu needs process_mode Always.
Themes style all controls; overrides change one; mouse_filter decides who gets clicks.
FlashcardsInteractive · disabled in the PDF
Control
A UI node: a rectangle with layout, input, focus and a theme.
Anchor
Where an edge is pinned, as a fraction of the parent: 0 to 1.
Offset
Pixels between a Control’s edge and its anchor.
Container
A Control that positions and sizes its children for you.
Expand and Fill
Size flags: ask for spare space, and stretch into it.
CanvasLayer
Draws its children on their own layer, unaffected by the camera.
grab_focus()
Give this control keyboard and gamepad focus.
PROCESS_MODE_ALWAYS
Keep processing whether the game is paused or not.
mouse_filter Ignore
Let clicks pass through this control to what is below.
Try it in GodotInteractive · disabled in the PDF
Module 11: Tile maps, cameras and levels
Godot105 / 219
Module 11 · Tile maps, cameras and levels
A level is a grid of small pictures
Drawing a whole level as one huge picture is slow to make and hard to change. Most 2D games instead cut the art into small squares, tiles, usually 16 or 32 pixels wide, and build the level by placing them on a grid, like a mosaic. One grass tile, painted a hundred times, makes a field. Change the tile and the whole field changes.
Godot splits this into two things. A TileSet is the palette: which tiles exist, how big they are, and what each one does (collides, slows you down, hurts). A TileMapLayer is a node that holds one grid of cells, each cell pointing at a tile in a TileSet. A level usually has a few layers stacked: the ground, the walls, the decorations on top.
Tile
One small square of art that can be placed on a grid cell.
Tile sheet (atlas)
One image holding many tiles side by side, cut into a grid.
TileSet
A resource listing the tiles, their size, and their collisions and other data.
TileMapLayer
A node with one grid of cells, painted with tiles from a TileSet.
Cell
One square of the grid, named by whole-number coordinates, a Vector2i.
TileMap is the old way. Before Godot 4.3, one TileMap node held several layers inside it. Since 4.3, each layer is its own TileMapLayer node and TileMap is deprecated. Older tutorials use TileMap and set_cell(layer, …); with TileMapLayer there is no layer argument. The editor can convert an old TileMap for you.
Godot106 / 219
Module 11 · Tile maps, cameras and levels
Making a TileSet from a tile sheet
Add a TileMapLayer node to your level. In the Inspector, its Tile Set property is empty: choose New TileSet, then click it to set Tile Size to the size of one tile in your sheet, say 16 × 16. The TileSet panel opens at the bottom of the editor. Drag your tile sheet image from the FileSystem dock into its list, and Godot offers to cut the image into tiles automatically.
levels/
level_1.tscn
level_2.tscn
tiles/
forest.png# the tile sheet: 16 × 16 tiles
forest.tres# the TileSet, shared by every level
Save the TileSet as its own .tres file, so every level uses the same one.
Crisp pixel art. Pixel art looks blurry when it is scaled with smoothing. Set Project Settings → Rendering → Textures → Canvas Textures → Default Texture Filter to Nearest, and the pixels stay sharp at any zoom.
Put in orderInteractive · disabled in the PDF
Put the steps for setting up tiles in order.
1.Drag the tile sheet into the TileSet panel and let Godot cut it
2.Add a TileMapLayer node to the level scene
3.Set the TileSet’s tile size to match the sheet
4.Switch to the TileMap panel and paint cells in the viewport
5.Create a new TileSet in its Tile Set property
Godot107 / 219
Module 11 · Tile maps, cameras and levels
Painting cells, in layers
Select the TileMapLayer, open the TileMap panel at the bottom, pick a tile and paint in the viewport. The toolbar has a pencil, a line, a rectangle and a bucket fill; right-click erases. Each cell holds exactly one tile, so to put a flower on grass you need a second layer: add another TileMapLayer that shares the same TileSet.
Level (Node2D)
Ground (TileMapLayer)grass, paths
Walls (TileMapLayer)collides
PlayerCharacterBody2D
Decor (TileMapLayer)tree tops, over the player
Layers are sibling nodes. Lower in the tree draws on top.
Code can read and change cells too. A cell is named by Vector2i coordinates in the grid, not pixels. local_to_map() turns a position into a cell, and map_to_local() turns a cell back into the position of its centre.
level.gd
1extendsNode2D23@onreadyvar ground:TileMapLayer=$Ground45funcdig(at:Vector2)->void:6var cell := ground.local_to_map(ground.to_local(at))7 ground.erase_cell(cell)89funcplace_stone(cell:Vector2i)->void:10# source 0, the tile at column 3, row 1 of the sheet11 ground.set_cell(cell,0,Vector2i(3, 1))
set_cell takes the cell, the source id (which image in the TileSet) and the tile’s coordinates in that image.
QuizInteractive · disabled in the PDF
Tiles are 16 × 16 pixels and the layer sits at the origin. What does ground.local_to_map(Vector2(40, 20)) return?
Tiles are 16 × 16 pixels and the layer sits at the origin. What does ground.local_to_map(Vector2(40, 20)) return?
Godot108 / 219
Module 11 · Tile maps, cameras and levels
Terrains pick the right edge tile for you
A patch of grass needs different tiles at its edges and corners: a top edge, a left edge, an inside corner. Picking each by hand is slow. Terrains (often called autotiling) do it for you: you tell Godot which side of each tile is grass, and as you paint, it chooses the tile that fits its neighbours.
In the TileSet’s Inspector, add a Terrain Set and choose its mode: Match Corners and Sides, Match Corners or Match Sides.
Add a Terrain to it, say “Grass”, with a colour.
In the TileSet panel, choose Paint → Terrains, pick Grass, and click the parts of each tile that are grass. These are its peering bits.
In the TileMap panel’s Terrains tab, pick Grass and paint: edges and corners appear on their own.
Missing tiles show as gaps. If a shape needs a tile your sheet doesn’t have (an inside corner, say), Godot picks the closest match and the edge looks wrong. A full “Match Corners and Sides” set needs 47 tiles; “Match Sides” needs 16. Choose the mode your art was drawn for.
QuizInteractive · disabled in the PDF
What do you mark on each tile so that terrain painting can choose tiles?
What do you mark on each tile so that terrain painting can choose tiles?
Going deeper
Code can paint terrains too: set_cells_terrain_connect(cells, terrain_set, terrain) paints a list of cells and updates their neighbours, which is how a procedurally generated cave gets proper edges. set_cells_terrain_path() does the same for a line of cells, such as a road.
Godot109 / 219
Module 11 · Tile maps, cameras and levels
Tiles that collide, and tiles that carry data
Walls are only walls if bodies bump into them. In the TileSet’s Inspector add a Physics Layer; then in the TileSet panel select a tile and draw its collision polygon (a full square for a wall, a slope for a ramp). Every cell painted with that tile collides, with no extra nodes. The physics layer has its own collision layer and mask, like any body.
Tiles can also carry your own data. Add a Custom Data Layer, say slippery of type bool, tick it on the ice tiles, and read it from code under the player’s feet.
player.gd
1@exportvar ground:TileMapLayer23funcis_on_ice()->bool:4var feet := ground.to_local(global_position)5var cell := ground.local_to_map(feet)6var data := ground.get_cell_tile_data(cell)7return data !=nulland data.get_custom_data("slippery")
get_cell_tile_data is null for an empty cell, so check before reading it.
Fill the blanksInteractive · disabled in the PDF
Read the damage custom data of the cell at pos (a global position).
GDScript
1funcdamage_at(pos:Vector2)->int:2var cell := lava.(lava.to_local(pos))3var data := lava.(cell)4if data == :5return06return data.("damage")
Platforms you can jump through. Each collision polygon in the TileSet panel has a One Way option: bodies pass through from below and land on top, the classic platformer ledge.
Godot110 / 219
Module 11 · Tile maps, cameras and levels
A camera that follows the player
A Camera2D decides which part of the world is on screen. Make it a child of the player and it follows for free, because children move with their parent. Only one camera is active per viewport: the first one entering the tree with Enabled on, or the one you call make_current() on.
Property
What it does
zoom
How much to magnify: Vector2(2, 2) shows everything twice as big.
position_smoothing_enabled
Glide towards the target instead of snapping to it.
position_smoothing_speed
How fast it catches up, higher is snappier (default 5).
limit_left, _top, _right, _bottom
Edges, in pixels, the view never goes past.
drag_horizontal_enabled
Let the player move inside a margin before the camera moves.
offset
Shift the view without moving the camera (used for screen shake later).
QuizInteractive · disabled in the PDF
You want the camera closer, so things look bigger. Which zoom do you set in Godot 4?
You want the camera closer, so things look bigger. Which zoom do you set in Godot 4?
Godot111 / 219
Module 11 · Tile maps, cameras and levels
Keep the camera inside the level
Without limits the camera happily shows the empty grey beyond the level’s edge. You can type the limits in the Inspector, but they go stale every time you extend the level. Better: work them out from the tiles that are painted.
Lines 1: The script goes on the Camera2D, so limit_left and the others are its own properties.
Lines 3: The level’s main layer, set in the Inspector. @export keeps the camera from guessing a node path.
Lines 6:get_used_rect() is the smallest rectangle of cells holding every painted tile, a Rect2i in cells.
Lines 7: The size of one tile in pixels, a Vector2i.
Lines 8-11: Cells times tile size gives pixels. position is the top-left cell, end the cell just past the bottom-right.
Lines 12: Smoothing on. Set the limits before it, or the camera visibly glides in from outside the level at the start.
Find the bugInteractive · disabled in the PDF
The camera is meant to follow the player, but the screen stays still while the player walks off it. Which line is wrong?
Text
Going deeper
This assumes the layer sits at the origin with no scale. If it is moved, add its position to each limit, or convert with map.to_global(map.map_to_local(cell)), remembering that map_to_local gives the centre of a cell, half a tile in from its corner.
Godot112 / 219
Module 11 · Tile maps, cameras and levels
Parallax: far things move slower
Look out of a moving train: the fence rushes past, the hills crawl, the moon seems not to move at all. Copying that, background layers that scroll slower than the camera, is called parallax, and it makes a flat 2D level feel deep. Since Godot 4.3 the node for it is Parallax2D: put a sprite in one, and set its Scroll Scale.
Layer
scroll_scale
Feels
Sky
Vector2(0, 0)
Infinitely far: never moves
Mountains
Vector2(0.2, 0.2)
Far away
Trees
Vector2(0.6, 0.6)
Nearer
The level
(no parallax)
Where you stand: moves with the world
Foreground leaves
Vector2(1.3, 1.3)
Closer than you
Set Repeat Size to the width of the background image and it tiles forever sideways, so a 512-pixel strip of hills can cover a level of any length. Autoscroll moves it on its own, for drifting clouds.
QuizInteractive · disabled in the PDF
The camera moves 100 pixels to the right. How far does a Parallax2D with scroll_scale = Vector2(0.25, 1) scroll on screen?
The camera moves 100 pixels to the right. How far does a Parallax2D with scroll_scale = Vector2(0.25, 1) scroll on screen?
Going deeper
ParallaxBackground with ParallaxLayer children is the older way, from Godot 3 and 4.0–4.2. It still works but is deprecated: it lives on its own canvas layer, which made it hard to mix with the level. Parallax2D is an ordinary node you can put anywhere in the tree.
Godot113 / 219
Module 11 · Tile maps, cameras and levels
Y-sort: lower on screen draws in front
In a top-down game you walk behind a tree when you are above it on screen, and in front when you are below it. Godot normally draws nodes in tree order, which can’t know that. Turn on Y Sort Enabled on the parent (a Node2D or a TileMapLayer) and its children are drawn by their y position instead: the higher the y, the later they draw, so the nearer they look.
Turn on Y Sort Enabled on the node holding the player, the enemies and the tall objects.
Put each character’s origin at its feet, not its centre: the sprite is offset up so (0, 0) is where it touches the ground.
For tiles, turn on Y Sort on the TileMapLayer, and set each tall tile’s Y Sort Origin in the TileSet to where its trunk meets the ground.
QuizInteractive · disabled in the PDF
With Y-sort on, the player’s head draws over the trunk of a tree while their feet are clearly behind it. What’s the likely cause?
With Y-sort on, the player’s head draws over the trunk of a tree while their feet are clearly behind it. What’s the likely cause?
Godot114 / 219
Module 11 · Tile maps, cameras and levels
Moving from one level to the next
Each level is its own scene. A door is an Area2D with a collision shape and a script that changes scene when the player walks in. The scene to load is exported as a file path, so the same door scene works for every level: you pick its destination in the Inspector.
call_deferred waits until the physics step is over: removing bodies in the middle of one causes errors.
change_scene_to_file frees the whole current scene, player included, and loads the new one. Anything that must survive the trip (the score, the player’s health) has to live outside the level scene; module 13 keeps it in an autoload. For a fade, put a black ColorRect on a CanvasLayer, tween its alpha up, change scene, and tween it down again.
Fill the blanksInteractive · disabled in the PDF
Finish the door: export a scene path and change to it when the player enters.
Why a path and not a PackedScene. You could export var next_level: PackedScene and call change_scene_to_packed(). But level 1’s door then loads level 2 the moment level 1 loads, and if level 2 has a door back to level 1, the two scenes reference each other, a cycle Godot can fail to load. A path loads the next level only when it’s needed.
Godot115 / 219
Module 11 · Tile maps, cameras and levels
Try it: build two connected levels
Open the Godot editor and build a small level from tiles, with a camera and a door to a second level. Free tile sheets are easy to find under a CC0 licence, which lets you use them for anything.
In the editorInteractive · disabled in the PDF
Godot116 / 219
Module 11 · Tile maps, cameras and levels
Recap
A TileSet is the palette of tiles; a TileMapLayer is one painted grid. Stack layers for ground, walls and decor.
Cells are Vector2i: local_to_map turns a position into a cell, map_to_local a cell into its centre.
Terrains choose edge and corner tiles from each tile’s peering bits.
Collision and custom data are set per tile in the TileSet; get_cell_tile_data reads them, and is null for an empty cell.
A Camera2D under the player follows it. Zoom 2 is closer in Godot 4. Limits keep it inside the level.
Parallax2D with a scroll scale below 1 makes backgrounds feel far away.
Y-sort draws lower nodes in front; put origins at the feet.
Change level with change_scene_to_file, deferred, from an exported path.
FlashcardsInteractive · disabled in the PDF
TileSet
A resource listing tiles, their size, collisions and custom data.
TileMapLayer
A node holding one grid of cells painted with a TileSet’s tiles.
Peering bits
The sides and corners of a tile marked as belonging to a terrain.
get_used_rect()
The rectangle of cells, as a Rect2i, that holds every painted tile.
zoom = Vector2(2, 2) in Godot 4
Everything twice as big: the camera is closer.
scroll_scale of 0.5
The layer scrolls at half the camera’s speed, so it looks far away.
Y-sort
Children drawn in order of their y position, lower on screen in front.
Why change_scene_to_file.call_deferred(path)?
So the scene isn’t freed in the middle of a physics callback.
Module 12: Sound, particles and game feel
Godot117 / 219
Module 12 · Sound, particles and game feel
Game feel is many small reactions
Two games can have the same rules, levels and art, and one feels dull while the other feels great. The difference is game feel, sometimes called juice: the game answering every action at once and generously. A jump makes a sound and a puff of dust, a hit flashes the enemy white and nudges the screen, a coin pops and chimes. None of it changes the rules; all of it tells the player “that worked”.
Action
Without juice
With juice
Jump
The sprite moves up
A whoosh, dust at the feet, a quick stretch
Land
The sprite stops
A thud, a squash, a little dust
Hit an enemy
Its health goes down
A white flash, a short freeze, a small shake
Pick up a coin
The counter goes up
A chime, sparkles, the counter bounces
Add it late, and add it with a switch. Make the game work first, then polish what the player does most. And give every strong effect a setting (shake, flashes): some players need them off, and the last slide in this module explains why.
Godot118 / 219
Module 12 · Sound, particles and game feel
Playing a sound
A sound file is an AudioStream resource. To hear it you need a player node, with the stream in its Stream property, and a call to play(). Godot has three players: which one you use decides whether the sound has a place in the world.
A slightly different pitch each time stops a repeated sound from getting tiring. Import short effects as WAV and music as Ogg Vorbis.
Find the bugInteractive · disabled in the PDF
The coin disappears when picked up, but its chime is never heard. Which line is wrong?
GDScript
Going deeper
An AudioStreamRandomizer is a stream made of other streams: give it three footstep recordings and a random pitch and volume range, and each play() picks one. A player can also overlap its own sound: raise Max Polyphony so rapid shots don’t cut each other off.
Godot119 / 219
Module 12 · Sound, particles and game feel
Audio buses: a mixing desk for your game
Players don’t go straight to the speakers. They go to a bus, a channel on a mixing desk, and every bus flows into Master. Open the Audio panel at the bottom of the editor and add a Music bus and an SFX bus; then set each player’s Bus property. Now a “music volume” slider is one line of code, and effects such as reverb can go on a whole bus.
Bus volume is in decibels (dB), a logarithmic scale that matches how ears hear loudness: 0 dB is unchanged, every −6 dB roughly halves the signal’s amplitude, and around −80 dB is silence. A slider from 0 to 1 is linear, so convert it with linear_to_db(), which computes .
linear_to_db
Volume (dB)−6
Drag the slider value: half of it is about −6 dB, and the curve plunges towards silence near 0.
Fill the blanksInteractive · disabled in the PDF
Set the Music bus from a slider value between 0 and 1.
GDScript
1func_on_music_slider_value_changed(value:float)->void:2var bus :=AudioServer.("Music")3AudioServer.(bus,(value))
QuizInteractive · disabled in the PDF
A bus is at 0 dB. You set it to −12 dB. Roughly how big is the signal’s amplitude now?
A bus is at 0 dB. You set it to −12 dB. Roughly how big is the signal’s amplitude now?
Godot120 / 219
Module 12 · Sound, particles and game feel
Music that loops and fades
Music should loop without a gap. Select the Ogg file in the FileSystem dock, open the Import dock, tick Loop and click Reimport; if the track has an intro, Loop Offset sets where each repeat starts. Put the music player on its own Music bus, and set Autoplay to start it with the scene.
Fade out, swap the track, fade in. A tween can animate any property, volume included.
QuizInteractive · disabled in the PDF
The music player is in each level scene. Every time the player goes through a door, the music starts again from the beginning. Why?
The music player is in each level scene. Every time the player goes through a door, the music starts again from the beginning. Why?
Godot121 / 219
Module 12 · Sound, particles and game feel
Particles: many tiny sprites at once
Dust, sparks, smoke, rain and confetti are all particles: lots of small images, each born, moving and fading on its own, set up as one node. You describe the rules (how many, how long each lives, which way they fly, how gravity pulls them, how their colour and size change) and the node does the rest.
GPUParticles2D
CPUParticles2D
Runs on
The graphics card
The processor
Rules in
A ParticleProcessMaterial
The node’s own properties
Good at
Thousands of particles, cheaply
Older or weaker devices, and the web
Switch
Menu: Convert to CPUParticles2D
Menu: Convert to GPUParticles2D
Amount: how many particles are alive at once. Lifetime: how long each lives, in seconds.
One Shot: emit once and stop, for bursts such as an explosion. Off, it emits forever, like a torch.
Explosiveness: 0 spreads the births over the lifetime, 1 releases them all at once.
Emitting: whether it’s producing particles now. Set it to true to fire a one-shot burst.
A one-shot burst as its own scene, added to the level (not the enemy, which may die first), and freed when it’s done.
QuizInteractive · disabled in the PDF
You want a puff of dust when the player lands: 12 particles, all at once, then nothing. Which settings?
You want a puff of dust when the player lands: 12 particles, all at once, then nothing. Which settings?
Godot122 / 219
Module 12 · Sound, particles and game feel
Screen shake, done gently
A shake sells a big impact. The trick that keeps it pleasant is trauma: a number from 0 to 1 that hits push up and time drains away. The shake’s strength is trauma squared, so small hits barely move the screen while big ones really do, and the shake fades out smoothly instead of stopping dead.
Trauma
Shake = trauma²
Calm after (s)0.67
After a full hit, trauma drains in a straight line; the shake, trauma squared, dies away faster and softer.
Lines 3-4: How fast the shake calms down, and the biggest offset in pixels.
Lines 7-8: A hit adds trauma, never past 1, so ten hits at once can’t throw the camera off the level.
Lines 11: Trauma drains by decay per second, using delta so it’s the same at any frame rate.
Lines 12: Squaring makes small trauma tiny: 0.3 of trauma is only 0.09 of shake.
Lines 13-14:offset moves the view without moving the camera itself, so following and limits keep working.
Fill the blanksInteractive · disabled in the PDF
An enemy hit should add 0.4 trauma to the camera.
GDScript
1func_on_hurt()->void:2var camera :=get_viewport().()3 camera.()
Going deeper
Pure random offsets jitter every frame. For a rolling, more natural shake, sample a FastNoiseLite with get_noise_1d(time * speed) instead of randf_range: neighbouring samples are close, so the motion is smooth. A small rotation shake, a degree or two, adds a lot too.
Godot123 / 219
Module 12 · Sound, particles and game feel
Hit stop: freeze for a few frames
Fighting games pause everything for a split second when a big blow lands. This hit stop makes a hit feel heavy, because the eye gets a moment to register it. In Godot, slow time almost to a stop with Engine.time_scale, wait a few hundredths of a second of real time, and put it back.
create_timer(time, process_always, process_in_physics, ignore_time_scale): the last true counts real seconds.
Find the bugInteractive · disabled in the PDF
hit_stop(0.08) should freeze the game for 0.08 seconds, but it freezes for more than a second. Which line is wrong?
GDScript
Godot124 / 219
Module 12 · Sound, particles and game feel
Easing, squash and stretch
Things in the real world speed up and slow down; they don’t move at a constant speed. An easing curve says how far along a movement is at each moment. Ease out starts fast and settles, which feels responsive; back overshoots a little and springs back, which feels bouncy and alive.
Linear
Quad, ease out
Back, ease out
Progress over time for three tweens of the same length. Back out goes past 1, then returns.
On landing, squash wide and short, then spring back. set_trans and set_ease apply to the steps added after them. A modulate above 1 brightens towards white.
QuizInteractive · disabled in the PDF
A menu panel should slide in quickly and settle softly into place. Which easing fits best?
A menu panel should slide in quickly and settle softly into place. Which easing fits best?
Godot125 / 219
Module 12 · Sound, particles and game feel
Let players turn the juice down
Effects that delight one player can hurt another. Screen shake can cause motion sickness; bright flashes can trigger seizures in people with photosensitive epilepsy; some players can’t hear, can’t tell red from green, or can’t press two buttons at once. A few settings let far more people play.
Screen shake strength, from 0 to 100 %, multiplied into add_trauma.
Flashes reduced or off; avoid flashing more than three times a second.
Remappable controls: InputMap can change an action’s keys at run time.
Subtitles for speech, and captions for important sounds (“footsteps behind you”).
Colour: never show information by colour alone; add a shape or an icon.
Hold or toggle for actions such as sprint or aim, and adjustable text size.
Settings is an autoload holding the player’s options; the next module saves it to disk.
QuizInteractive · disabled in the PDF
Enemies glow red when they are about to attack. What makes that warning accessible to more players?
Enemies glow red when they are about to attack. What makes that warning accessible to more players?
Godot126 / 219
Module 12 · Sound, particles and game feel
Try it: juice up your game
Take the 2D game from the earlier modules and add each reaction one at a time. Play after every change: you’ll feel the difference more than see it.
In the editorInteractive · disabled in the PDF
Godot127 / 219
Module 12 · Sound, particles and game feel
Recap
Game feel is the game answering every action with sound, motion and light.
AudioStreamPlayer for music and UI, AudioStreamPlayer2D for sounds in the world.
Route players to buses; set bus volume in dB with linear_to_db (−6 dB is about half).
A sound stops when its node is freed: wait for finished, or play it from something that stays.
Particles: One Shot and Explosiveness 1 for a burst; free it on finished.
Screen shake from trauma squared, applied to the camera’s offset.
Hit stop: lower Engine.time_scale and wait with a timer that ignores it.
Ease out for responsive motion, back for bounce; squash on landing, flash on hits.
Give strong effects a setting, and never rely on colour alone.
FlashcardsInteractive · disabled in the PDF
Game feel (juice)
Small, immediate reactions to actions: sound, particles, shake, squash.
Audio bus
A mixer channel players send sound to; every bus flows into Master.
linear_to_db(0.5)
About −6 dB: half the amplitude.
One Shot (particles)
Emit one cycle and stop, for bursts.
Explosiveness
0 spreads births over the lifetime; 1 releases all at once.
Trauma
A 0–1 value hits raise and time drains; shake strength is its square.
Hit stop
A freeze of a few frames when a hit lands, so it feels heavy.
Ease out
Starts fast, ends slow: feels responsive and settles softly.
Module 13: Game state, resources and saving
Godot128 / 219
Module 13 · Game state, resources and saving
Changing scene forgets everything in it
When you change level, Godot frees the whole old scene: the player, the HUD, and every variable they held. The score drops back to 0 and the music restarts. Anything that must outlive a level needs a home outside the levels.
That home is an autoload: a script or scene that Godot creates once when the game starts, puts under the root of the tree next to the current scene, and never frees. Add one in Project Settings, on the Autoload tab (under Globals in recent versions): pick the script, give it a name, and that name works from every script, like a global variable.
root (Window)
GameStateautoload: score, lives
Musicautoload: keeps playing
Level2the current scene, swapped by doors
Autoloads are siblings of the current scene, so swapping the scene leaves them alone.
Registered as GameState. Any script can now call GameState.add_coin().
Godot129 / 219
Module 13 · Game state, resources and saving
Using an autoload, and not overusing it
The HUD doesn’t ask for the score every frame. It connects to the autoload’s signal once, and updates its label only when the number changes. The coin calls a method; the HUD listens. Neither knows about the other.
Line 7 shows the current value straight away, so a new level’s HUD doesn’t start at 0.
Globals are easy to reach for. If every script reads and writes an autoload, any of them can break any other, and you can’t test a scene on its own. Keep autoloads for what truly is global: the saved progress, settings, music, scene changes. Pass everything else down the tree, or through signals.
QuizInteractive · disabled in the PDF
What happens to an autoload node when the game calls change_scene_to_file()?
What happens to an autoload node when the game calls change_scene_to_file()?
Godot130 / 219
Module 13 · Game state, resources and saving
Resources are data you can save and share
You have used resources all along: a texture, a sound, a TileSet, a material. A Resource is a bundle of data that isn’t in the scene tree, saved as its own file (.tres as text, .res as binary) and loaded once even if many nodes use it. You can make your own: a script that extends Resource with exported properties becomes a new kind of data file you edit in the Inspector.
Lines 1-2:class_name makes ItemData a type the editor knows; it extends Resource, not Node.
Lines 4-8: Exported properties are the fields you fill in. @export_range gives a slider; @export_multiline a big text box.
Lines 10-11: Resources can have methods too, so data can answer questions about itself.
Now right-click in the FileSystem dock, choose New Resource…, pick ItemData, and save sword.tres. Fill in its fields in the Inspector. Each item is a small file: a designer can add a hundred without touching code.
Godot131 / 219
Module 13 · Game state, resources and saving
Exporting resources, and the shared-copy trap
A node can export a resource property, and you drop a .tres on it in the Inspector. Typed arrays work too, for a shop’s stock or an enemy’s loot.
Loading the same resource file twice gives the same object, not two copies. That saves memory and is right for things that never change, like a texture. But if your code changes a resource, every node using that file sees the change.
Find the bugInteractive · disabled in the PDF
Five slimes share slime_stats.tres. Hitting one slime lowers the health of all five. Which line causes it?
GDScript
Going deeper
duplicate() is shallow: a resource inside the copied one is still shared. duplicate(true) copies sub-resources too, though arrays and dictionaries of resources have had edge cases across 4.x versions; check what your version copies before relying on it.
Godot132 / 219
Module 13 · Game state, resources and saving
Where a game may write: user://
res:// is your project folder. Once the game is exported it is packed into the game’s .pck file and is read-only. Saves and settings go in user://, a folder Godot gives each game in the player’s own data directory.
Find it quickly. In the editor, Project → Open User Data Folder opens it. Turn on Application → Config → Use Custom User Dir in Project Settings to give it your game’s name instead of Godot’s.
QuizInteractive · disabled in the PDF
An exported game saves to res://save.json. What happens?
An exported game saves to res://save.json. What happens?
Godot133 / 219
Module 13 · Game state, resources and saving
Settings in a ConfigFile
Settings (volume, shake strength, key bindings) are a few named values. A ConfigFile stores them in sections, in a readable text format like an .ini file, and reads back Godot types such as Vector2 and Color as they were.
A save game is the progress turned into data: which level, how many coins, which doors are open. Gather it in a Dictionary, turn it into JSON text, and write it with FileAccess. JSON is readable, easy to debug, and can’t contain code.
WalkthroughInteractive · disabled in the PDF
autoload/game_state.gd
1const SAVE_PATH :="user://save.json"23funcsave_game()->void:4var data :={5"version": 1,6"level": current_level,7"coins": coins,8}9var file :=FileAccess.open(SAVE_PATH,FileAccess.WRITE)10 file.store_string(JSON.stringify(data,"\t"))1112funcload_game()->void:13ifnotFileAccess.file_exists(SAVE_PATH):14return15var text :=FileAccess.get_file_as_string(SAVE_PATH)16var data =JSON.parse_string(text)17iftypeof(data)!= TYPE_DICTIONARY:18return# damaged file19 current_level = data.get("level","res://levels/level_1.tscn")20 coins =int(data.get("coins",0))
Lines 4-8: Only plain data: numbers, strings, booleans, arrays, dictionaries. A version number, for later.
Lines 9-10:FileAccess.WRITE creates or replaces the file. JSON.stringify with a tab indent makes it readable. The file closes when file goes out of scope.
Lines 13-15: No save yet is normal on a first run. get_file_as_string reads the whole file.
Lines 16-18:parse_string returns null for broken JSON, so check what came back before using it.
Lines 19-20:get with a default copes with a key missing from an older save. int() is needed: see the quiz.
QuizInteractive · disabled in the PDF
You save {"coins": 3} as JSON and load it back. What type is data["coins"]?
You save {"coins": 3} as JSON and load it back. What type is data["coins"]?
Godot135 / 219
Module 13 · Game state, resources and saving
Saving resources, and why not to load untrusted ones
You can also save a whole custom resource: make a SaveData resource class with exported properties, fill it in, and call ResourceSaver.save(data, "user://save.tres"). Loading it back with load() gives typed properties, and Vector2, Color and nested resources all survive, with no conversion code.
JSON
Only basic types; convert the rest
Readable in any editor and by other tools
Pure data: safe to load from anywhere
Resource (.tres)
Every Godot type, typed, with no conversion
Readable too, but tied to your class names
Can contain a script, which runs when loaded
A resource file can run code. A .tres file can embed a GDScript, and loading the file runs it. That’s fine for files you made, but a save file is a file players share online. Someone could hand out a “100 % save” that installs malware. Use JSON or ConfigFile for anything a player might download from someone else.
QuizInteractive · disabled in the PDF
Your game lets players share level files they built with an in-game editor. Which format is the safe choice?
Your game lets players share level files they built with an in-game editor. Which format is the safe choice?
Godot136 / 219
Module 13 · Game state, resources and saving
Designing saves that last
Save what the player earned, not the scene. The level’s walls are already in its .tscn; the save only needs which level, which coins are taken, which doors are open. Give each pickup a stable id (its node path in the level works) and save the list of ids taken. Then version the file: when an update changes the format, the game can still read old saves and upgrade them.
GDScript
1const VERSION :=223funcmigrate(data:Dictionary)->Dictionary:4var version:int=int(data.get("version",1))5if version <2:6# version 2 split "coins" into "gold" and "gems"7 data["gold"]= data.get("coins",0)8 data["gems"]=09 data.erase("coins")10 data["version"]= VERSION11return data
Each step upgrades by one version, so a save from version 1 runs every step in turn.
Put in orderInteractive · disabled in the PDF
Put loading a save in a safe order.
1.Read its text and parse the JSON
2.Check the result is a dictionary
3.Copy its values into the game state
4.Check the save file exists
5.Migrate it to the current version
6.Change to the saved level
Going deeper
If the game crashes or the power fails in the middle of writing, a half-written save is worse than an old one. Write to user://save.json.tmp first, then replace the real file with DirAccess.rename_absolute(): a rename is all or nothing, so the save on disk is always a whole one. Keeping the previous save as a backup gives players a way back too.
Godot137 / 219
Module 13 · Game state, resources and saving
Try it: a game that remembers
Give the game from the earlier modules a memory. Test it the way players will: quit, start again, and check what came back.
In the editorInteractive · disabled in the PDF
Godot138 / 219
Module 13 · Game state, resources and saving
Recap
Changing scene frees it; state that must last lives in an autoload, a child of the root.
Keep autoloads few: progress, settings, music. Talk to them through methods and signals.
A custom Resource class is data you edit in the Inspector and save as .tres.
A loaded resource is shared: duplicate() it before changing it per node.
res:// is read-only in an exported game; save to user://.
ConfigFile for settings, JSON for saves. JSON numbers come back as float.
Never load() a resource file someone else made: it can run code.
Save what was earned, with a version number, and migrate old saves step by step.
FlashcardsInteractive · disabled in the PDF
Autoload
A node created at start-up under the root, kept across scene changes, reachable by name.
Resource
Shared, saveable data outside the scene tree: a texture, a TileSet, your ItemData.
stats.duplicate()
A copy of a resource, so changes don’t reach other nodes using the file.
user://
The game’s writable folder in the player’s data directory.
ConfigFile
Sections of named values, saved as text; ideal for settings.
JSON.parse_string on bad text
Returns null, so check the result before using it.
Why not load a shared .tres?
It can embed a script that runs when loaded.
Save versioning
A version number in the save, and code that upgrades old formats.
Module 14: Enemies: state machines and pathfinding
Godot139 / 219
Module 14 · Enemies: state machines and pathfinding
An enemy is always in one state
A guard stands still, notices you, runs at you, swings, and gives up if you escape. Written as a pile of ifs, that soon becomes a tangle where it chases and attacks at the same time. A finite state machine untangles it: the enemy is in exactly one state at a time, each state has its own small behaviour, and transitions say when it moves to another.
The guard starts in Idle, doing nothing.
Seeing the player is a transition: now it only chases.
Close enough, it switches to Attack; too far again, back to Chase.
If it loses the player, it walks back to its post and idles.
Each box is a state; each arrow a transition, labelled with what triggers it.
State
One mode of behaviour, such as Idle or Chase. Exactly one is active.
Transition
A rule for leaving one state for another: “if I see the player, chase”.
Finite state machine
A fixed set of states, the current one, and the transitions between them.
QuizInteractive · disabled in the PDF
What is the main thing a state machine gives an enemy that a heap of if statements doesn’t?
What is the main thing a state machine gives an enemy that a heap of if statements doesn’t?
Godot140 / 219
Module 14 · Enemies: state machines and pathfinding
A state machine with an enum and match
For a small enemy, an enum names the states, a variable holds the current one, and a match in _physics_process runs the current state’s code. Each branch also checks its own transitions.
Module 14 · Enemies: state machines and pathfinding
Doing something once on entering a state
Some things should happen once, when a state starts or ends, not every frame: play the run animation on entering Chase, switch the hitbox on when an attack starts and off when it ends. Send every change of state through one function, and it has a place for that.
GDScript
1funcchange_state(new_state:State)->void:2if new_state == state:3return4# leaving the old state5if state ==State.ATTACK:6 hitbox.set_deferred("monitoring",false)7 state = new_state8# entering the new one9match state:10State.IDLE:11 sprite.play("idle")12State.CHASE:13 sprite.play("run")14State.ATTACK:15 sprite.play("attack")16 hitbox.set_deferred("monitoring",true)
Now the branches call change_state(State.CHASE) instead of assigning state directly.
QuizInteractive · disabled in the PDF
Inside the Chase branch, which runs every physics frame, the code calls sprite.play("run"). What does the player see?
Inside the Chase branch, which runs every physics frame, the code calls sprite.play("run"). What does the player see?
Godot142 / 219
Module 14 · Enemies: state machines and pathfinding
Bigger enemies: each state as a node
When states grow to dozens of lines each, one match becomes one huge file. The common Godot answer: each state is a child node with its own script, and a StateMachine node runs whichever is current. Each state has enter(), exit() and physics_update(), and emits finished with the name of the next state.
Guard (CharacterBody2D)
Sprite (AnimatedSprite2D)
StateMachineruns the current state
Idle
Chase
Attack
state_machine.gd
1class_nameStateMachine2extendsNode34@exportvar initial:State5var current:State67func_ready()->void:8for child inget_children():9if child isState:10 child.body = owner11 child.finished.connect(_on_finished)12 current = initial13 current.enter()1415func_physics_process(delta:float)->void:16 current.physics_update(delta)1718func_on_finished(next_state:String)->void:19 current.exit()20 current =get_node(next_state)21 current.enter()
owner is the root of the scene the node was saved in: here, the Guard.
Enum and match
One file, easy to read
Best for 2–5 short states
Grows messy as states grow
State nodes
One script per state, each small
States can have their own timers and exported settings
The base class. Chase extends State and overrides physics_update, calling finished.emit("Attack") when in reach.
Godot143 / 219
Module 14 · Enemies: state machines and pathfinding
Seeing the player: an area, then a ray
Detection usually has two steps. A big Area2D around the enemy, its detection zone, notices when the player is near (body_entered, body_exited). Being near isn’t seeing: there may be a wall between them. A raycast, an invisible line from the enemy to the player, checks for walls in the way: if it hits nothing on the walls’ layer, the enemy has line of sight.
GDScript
1const WALLS :=1# the walls' collision layer, as a mask23funccan_see(player:Node2D)->bool:4var space :=get_world_2d().direct_space_state5var query :=PhysicsRayQueryParameters2D.create(6 global_position, player.global_position, WALLS)7var hit := space.intersect_ray(query)8return hit.is_empty()
intersect_ray returns a Dictionary: empty if the ray hit nothing, or with collider, position and normal if it did.
Find the bugInteractive · disabled in the PDF
The enemies are children of an Enemies node placed at (400, 0). Their rays point at the wrong spot and line of sight is nonsense. Which line is wrong?
GDScript
Godot144 / 219
Module 14 · Enemies: state machines and pathfinding
Steering: seek, flee and arrive
Setting the velocity straight at the target makes an enemy turn instantly, like a robot. Steering behaviours move smoothly: work out the velocity you want (the desired velocity) and turn the current velocity towards it a little each frame. Seek wants to go towards the target, flee away from it, and arrive seeks but slows down within a radius so it stops without overshooting.
Module 14 · Enemies: state machines and pathfinding
Navigation: finding a way around walls
Seek runs straight into walls. Navigation finds a path around them. A NavigationRegion2D holds a navigation mesh, the walkable area, which you draw as an outline and bake in the editor (or give the TileSet a navigation layer, so floor tiles are walkable). Each enemy gets a NavigationAgent2D child: you give it a target, and it hands you the next point on the path, one corner at a time.
Lines 5: The agent is a helper node; it doesn’t move anything on its own.
Lines 7-11: A Timer (0.25 s, autostart) asks for a new path a few times a second. Setting target_position every frame would compute a path 60 times a second for nothing.
Lines 14-15: At the target (within the agent’s Target Desired Distance), stop.
Lines 17-18: Otherwise head for the next corner of the path. The agent moves on to the following corner as the body gets near each one.
No path on the first frame. The navigation map is built after the scene loads, on the first physics frame. A path asked for in _ready() comes back empty. If you must set a target at the start, await get_tree().physics_frame first.
Put in orderInteractive · disabled in the PDF
Put setting up a navigating enemy in order.
1.Add a NavigationAgent2D as a child of the enemy
2.Draw the walkable outline and bake the navigation mesh
3.Each physics frame, move towards get_next_path_position()
4.Add a NavigationRegion2D to the level
5.Set the agent’s target_position to where the enemy should go
Godot146 / 219
Module 14 · Enemies: state machines and pathfinding
Avoidance: crowds that don’t pile up
Ten enemies on the same path shove into each other. Turn on Avoidance Enabled on each agent and set its Radius: you give the agent the velocity you want, the navigation server adjusts it so agents steer around each other, and hands back a safe velocity through the velocity_computed signal. You move with that one.
Ask with agent.velocity, move in the callback. Also set the agent’s Max Speed to your speed.
QuizInteractive · disabled in the PDF
Avoidance is on, _on_velocity_computed is connected and calls move_and_slide(), but the enemy never moves. The physics code sets velocity (the body’s) towards the next point. What’s wrong?
Avoidance is on, _on_velocity_computed is connected and calls move_and_slide(), but the enemy never moves. The physics code sets velocity (the body’s) towards the next point. What’s wrong?
Godot147 / 219
Module 14 · Enemies: state machines and pathfinding
Cooldowns with timers
The slime from earlier attacks every frame, 60 hits a second. An attack needs a cooldown: after a hit, wait before the next one. A Timer node with One Shot on and a Wait Time of, say, 1 second does it: start() it after each hit, and only attack while is_stopped() is true.
GDScript
1@onreadyvar cooldown:Timer=$Cooldown# one shot, 1.0 s23funcattack()->void:4ifnot cooldown.is_stopped():5return6hit_player()7 cooldown.start()
Find the bugInteractive · disabled in the PDF
This enemy attacks once and never again. Which line is wrong?
GDScript
Godot148 / 219
Module 14 · Enemies: state machines and pathfinding
Grid pathfinding with AStarGrid2D
On a tile grid, a turn-based tactics game, a dungeon crawler, you often want paths in whole cells. AStarGrid2D runs A\*, the classic shortest-path algorithm, on a grid you describe: its size, which cells are blocked, and whether diagonal moves are allowed. It needs no navigation mesh.
get_id_path returns cells; get_point_path returns positions. An empty array means no path.
QuizInteractive · disabled in the PDF
A programmer marks the wall cells solid, then sets region and calls update(). Paths now go straight through walls. Why?
A programmer marks the wall cells solid, then sets region and calls update(). Paths now go straight through walls. Why?
Going deeper
A\* explores the cell with the smallest , where is the cost of the path so far and a heuristic, a guess of the remaining cost. With a heuristic that never overestimates, the path found is the shortest. AStarGrid2D lets you choose it: Manhattan distance () fits 4-way movement, Octile or Euclidean fits diagonals. Setting jumping_enabled turns on jump point search, which skips across open areas much faster.
Mesh navigation or a grid?. Use NavigationAgent2D for free movement in real time, with avoidance and any shape of walkable area. Use AStarGrid2D when movement is cell by cell, or when you need the path’s exact cost (tactics games show it before you commit). Both can live in one game.
Godot149 / 219
Module 14 · Enemies: state machines and pathfinding
Try it: a guard that hunts you
Build one enemy step by step, testing each state before adding the next. Turn on Debug → Visible Navigation in the editor’s menu to see the navigation mesh and paths while the game runs.
In the editorInteractive · disabled in the PDF
Godot150 / 219
Module 14 · Enemies: state machines and pathfinding
Recap
A finite state machine: one active state, explicit transitions between states.
Small enemies: an enum and a match. Big ones: one node per state with enter, exit, physics_update.
Put “once on entering” work in a change_state() function, not in per-frame code.
Detect with an Area2D, confirm with a raycast in global coordinates.
Steering turns the velocity towards a desired velocity: seek, flee, arrive.
NavigationAgent2D follows a baked navigation mesh; repath a few times a second.
Avoidance: set agent.velocity, move with the safe velocity from velocity_computed.
A timer started every frame never fires. Start cooldowns once.
AStarGrid2D: set region, update(), then mark solids; get_id_path gives cells.
FlashcardsInteractive · disabled in the PDF
Finite state machine
A set of states, the current one, and the transitions between them.
Transition
A rule that moves the machine from one state to another.
Line of sight
A ray from enemy to player that hits no wall on the way.
Desired velocity
The velocity a steering behaviour wants; the real one turns towards it.
Arrive
Seek, but slowing down inside a radius so it stops at the target.
get_next_path_position()
The next corner of the agent’s path to head for.
velocity_computed
The agent’s signal with a safe velocity that avoids other agents.
A\* heuristic
A guess of the remaining cost; never overestimating keeps paths shortest.
Module 15: Into 3D: nodes, meshes, lights and cameras
Godot151 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
3D is the same tree with one more axis
Everything you learned in 2D still holds: a game is a tree of nodes, scenes are saved trees, scripts extend nodes, signals connect them. What changes is the family of nodes. In 2D they inherit from Node2D and have a Vector2 position; in 3D they inherit from Node3D and have a Vector3 position, with a third axis, z, for depth.
2D
3D
What it is
Node2D
Node3D
Something with a place in the world
Vector2
Vector3
A point or a direction
Transform2D
Transform3D
Position, rotation and scale together
Sprite2D
MeshInstance3D
Something you can see
Camera2D
Camera3D
Where the screen looks from
CharacterBody2D
CharacterBody3D
A body you move in code
Node3D
The base of every 3D node: it has a position, rotation and scale in 3D space.
Vector3
Three numbers, x, y and z: a point in space, or a direction and length.
Mesh
A 3D shape made of triangles, the thing the graphics card draws.
Material
How a surface looks: its colour, how shiny or rough it is, whether it glows.
QuizInteractive · disabled in the PDF
In a 3D scene, which node shows a box you can see?
In a 3D scene, which node shows a box you can see?
Godot152 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
Y is up, and forward is minus Z
Godot’s 3D space is right-handed with Y up: x points right, y points up, and z points toward the viewer, out of the screen. So forward is minus Z: a camera, a light or a character with no rotation looks along -z. One unit is meant to be one metre; physics, gravity (9.8 per second squared) and the default sizes all assume it.
Constant
Value
Meaning
Vector3.RIGHT
(1, 0, 0)
Right
Vector3.UP
(0, 1, 0)
Up
Vector3.BACK
(0, 0, 1)
Toward the viewer
Vector3.FORWARD
(0, 0, -1)
Forward, away from the viewer
Vector3.ZERO
(0, 0, 0)
The origin
Models from other tools. Blender uses Z up. Its glTF exporter (which Godot also uses to import .blend files) converts that to Y up for you, but a model made facing Blender’s front arrives looking along +z in Godot, backwards. Check which way an imported character faces before writing movement code.
QuizInteractive · disabled in the PDF
A Camera3D has no rotation. Which way does it look?
A Camera3D has no rotation. Which way does it look?
Godot153 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
A transform is position, rotation and scale
Every Node3D has a transform: where it is, how it is turned and how big it is, relative to its parent. You usually change its parts: position (a Vector3), rotation (angles in radians around x, y and z) or rotation_degrees, and scale. global_position and global_transform are the same things measured from the world’s origin instead of the parent.
A Transform3D is a basis (a 3×3 matrix holding rotation and scale) plus an origin (the position). Children multiply their transform by their parent’s: global_transform = parent.global_transform * transform. That is why moving a parent moves all its children, and why global_position costs a little more to read: Godot may have to work the chain out.
Godot154 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
The basis tells you which way a node faces
The basis has three vectors, basis.x, basis.y and basis.z: the node’s own right, up and back directions, in its parent’s space. Forward is therefore -basis.z. To move a node the way it faces, add some of -basis.z to its position. To turn it toward a point, use look_at(target), which turns its -z to face the target.
Lines 3-4: How fast the drone flies, and what it chases, both set in the inspector.
Lines 7-8: With nothing to chase, do nothing. An @export node can be left empty.
Lines 9:look_at turns the drone so its -z points at the target. The second argument says which way is up, so it does not roll.
Lines 10: Forward in world space is minus the global basis’ z. Each basis vector has length 1 while the scale is 1.
Lines 11: Move along it, speed metres per second, the same at any frame rate thanks to delta.
QuizInteractive · disabled in the PDF
A turret calls look_at(target.global_position, Vector3.UP), and the target stands exactly above it. What goes wrong?
A turret calls look_at(target.global_position, Vector3.UP), and the target stands exactly above it. What goes wrong?
Godot155 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
Meshes: primitive shapes to start with
A MeshInstance3D draws the Mesh in its mesh property. For blocking out a level or a character, Godot builds simple ones for you, the primitive meshes: BoxMesh, SphereMesh, CapsuleMesh, CylinderMesh, PlaneMesh, PrismMesh and TorusMesh. Pick one in the inspector and set its size there, or build it in code.
Five crates in a row. y = 0.5 lifts each one so its bottom sits on the floor, since a box’s origin is its centre.
Find the bugInteractive · disabled in the PDF
No ball appears, and Godot complains about a type. Which line is wrong?
GDScript
Godot156 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
Importing models made in other tools
Real games use models made in a 3D tool such as Blender. The format to use is glTF 2.0, as a .glb file (one binary file) or .gltf (text plus separate files). Drop it in the project folder: Godot imports it as a scene, with MeshInstance3D nodes for its parts, its materials, and an AnimationPlayer if it has animations. Double-click it to open the import settings, where you can generate collision, set scale, and save animations or materials to their own files.
.glb or .gltf: the recommended format. It keeps meshes, materials, skeletons and animations.
.blend: Godot can import Blender files directly if you set Blender’s path in the editor settings.
.fbx: supported too, converted on import.
.obj: only a mesh, no animations or scene; fine for a static prop.
Don’t edit the imported scene. Changes made to the imported scene itself are lost when the model is imported again. Instead, right-click the file and choose New Inherited Scene, or put the model inside a scene of your own (a CharacterBody3D with the model as a child) and add scripts, collision and extra nodes there.
Put in orderInteractive · disabled in the PDF
Put the steps for using a character model from Blender in order.
1.Drag the imported model in as a child, and add a collision shape
2.Let Godot import it, and check the import settings
3.Copy the file into the Godot project folder
4.Export it from Blender as a .glb file
5.Make a new scene with a CharacterBody3D root
Godot157 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
Materials: what a surface is made of
A mesh with no material is plain grey. A StandardMaterial3D describes a surface with a few physical ideas: albedo (its base colour, or a texture), metallic (0 for plastic, wood or skin, 1 for metal), roughness (0 is a mirror, 1 is chalk), and emission (light it gives off). Give one to a primitive mesh, to a MeshInstance3D’s surface, or to its material_override, which wins over the others.
A glossy blue gem that glows a little. With glow on in the environment, it blooms.
Fill the blanksInteractive · disabled in the PDF
Make a dull, rusty metal: fully metallic, quite rough.
GDScript
1var mat := .new()2mat.albedo_color =Color(0.5, 0.3, 0.2)3mat.=1.04mat.=0.85$Pipe.= mat
Godot158 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
Three kinds of light
Without a light, a 3D scene is lit only by its environment’s ambient light, flat and dull. Godot has three light nodes. Each has light_color, light_energy (its brightness) and shadow_enabled, which is off by default because shadows cost time to draw.
Node
Like
Where it matters
Main settings
DirectionalLight3D
The sun
Only its rotation: its rays are parallel
shadow_enabled
OmniLight3D
A light bulb
Its position: light spreads in every direction
omni_range
SpotLight3D
A torch
Position and rotation: a cone along its -z
spot_range, spot_angle
QuizInteractive · disabled in the PDF
You move a DirectionalLight3D 100 metres to the left without turning it. What changes in the scene?
You move a DirectionalLight3D 100 metres to the left without turning it. What changes in the scene?
Lights cost time. In the Forward+ renderer, many lights are fine, but each shadow-casting omni or spot light redraws part of the scene into a shadow map. Keep shadows for the few lights that need them, keep omni_range small, and for scenery that never moves, bake light with LightmapGI, or use VoxelGI or SDFGI for bounced light.
Godot159 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
The environment: sky, ambient light and tone
A WorldEnvironment node holds an Environment resource: what is drawn behind everything (a colour or a sky), the ambient light that fills the shadows, tone mapping (how bright values are squeezed into what a screen can show), fog, and effects like glow. Have one per scene. A new 3D scene in the editor shows a preview sky and sun; click Add Environment to Scene and Add Sun to Scene in the toolbar to make them real nodes.
Usually you set all this in the inspector; in code you see how the pieces fit: a material in a sky in an environment.
QuizInteractive · disabled in the PDF
Your level has a sun and a sky, but the sides of buildings facing away from the sun are pitch black. What is the first thing to check?
Your level has a sun and a sky, but the sides of buildings facing away from the sun are pitch black. What is the first thing to check?
Godot160 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
The camera decides what you see
A Camera3D draws the scene from its position, looking along its -z. Only one camera is used at a time per viewport: the one with current set, or the first one added. fov is its vertical field of view in degrees (75 by default); near and far are the closest and farthest distances it draws. Set projection to orthogonal for a view without perspective, for strategy or puzzle games.
res://level/overview_camera.gd
1extendsCamera3D23func_ready()->void:4 position =Vector3(0.0, 10.0, 10.0)5look_at(Vector3.ZERO,Vector3.UP)6 fov =60.07make_current()89func_unhandled_input(event:InputEvent)->void:10if event isInputEventMouseButtonand event.pressed:11var from :=project_ray_origin(event.position)12var dir :=project_ray_normal(event.position)13print("The click points from ", from," along ", dir)
project_ray_origin and project_ray_normal turn a point on the screen into a ray in the world: the start of picking things with the mouse.
Find the bugInteractive · disabled in the PDF
Players say the mountains in the distance pop in and out as they walk. Which line causes it?
GDScript
Godot161 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
Try it: a small lit room
Build a little 3D scene by hand, with no models yet. Everything here is a primitive mesh, a light, an environment and a camera.
In the Godot editorInteractive · disabled in the PDF
Godot162 / 219
Module 15 · Into 3D: nodes, meshes, lights and cameras
Recap
3D nodes extend Node3D and use Vector3; the scene tree works just as in 2D.
Y is up, forward is -z, and one unit is one metre.
position, rotation (radians) and scale make the transform; global versions are measured from the world.
The basis holds a node’s right, up and back; forward is -basis.z, and look_at turns -z toward a point.
MeshInstance3D draws a mesh; primitive meshes are for blocking out, glTF models for the real thing.
Directional, omni and spot lights; shadows are off by default and cost time.
WorldEnvironment sets sky, ambient light and tone mapping; Camera3D sets what is seen.
FlashcardsInteractive · disabled in the PDF
Which way does a node with no rotation look?
Along its -z: Vector3.FORWARD, (0, 0, -1).
What units is rotation in?
Radians. Use deg_to_rad or rotation_degrees for degrees.
Transform3D
A basis (rotation and scale) and an origin (position).
Forward from a basis
-global_transform.basis.z.
The best format for models
glTF 2.0, as .glb or .gltf.
metallic and roughness
0 to 1: metal or not, and mirror-smooth to chalky.
What matters for a DirectionalLight3D?
Only its rotation; its rays are parallel like the sun’s.
What lights the shadowed side of things?
The ambient light in the Environment.
Camera3D.far too small
Distant things are not drawn and pop in and out.
Module 16: A 3D character: movement, physics and the camera
Godot163 / 219
Module 16 · A 3D character: movement, physics and the camera
A character is a CharacterBody3D with a shape and a look
In 2D you moved a CharacterBody2D with velocity and move_and_slide(). 3D is the same with a CharacterBody3D: you set velocity, a Vector3 in metres per second, and move_and_slide() moves the body, slides it along walls and floors, and updates is_on_floor(). It needs a CollisionShape3D child (a CapsuleShape3D is the usual choice: it slides over small bumps) and something to see, the model.
PlayerCharacterBody3D, player.gd
CollisionShape3DCapsuleShape3D
Modelthe imported .glb, or a capsule mesh
CameraPivotNode3D, turned by the mouse
SpringArm3D
Camera3D
The player scene. The camera rig is a child, so it follows the body for free.
QuizInteractive · disabled in the PDF
Why is a capsule the usual collision shape for a walking character?
Why is a capsule the usual collision shape for a walking character?
Godot164 / 219
Module 16 · A 3D character: movement, physics and the camera
Gravity pulls velocity down, a jump sets it up
Gravity is acceleration: every second in the air, the downward speed grows by about 9.8 metres per second. So each physics frame, subtract gravity * delta from velocity.y while the body is off the floor. A jump is the opposite in one go: set velocity.y to a positive speed while on the floor, and gravity brings it back down.
Reading gravity from the project settings means one change there changes every body that uses it.
Height
Highest point (m)1.03
Time in the air (s)0.92
Height above the floor during a jump, with gravity 9.8. Doubling the jump speed makes the jump four times as high.
QuizInteractive · disabled in the PDF
Players want the jump twice as high. What should jump_velocity be, starting from 4.5?
Players want the jump twice as high. What should jump_velocity be, starting from 4.5?
Going deeper
Since Godot 4.3, physics bodies have get_gravity(), which returns the gravity vector at the body, including any Area3D that overrides gravity (a low-gravity room, a planet). velocity += get_gravity() * delta then handles both. Many games also fall faster than they rise, multiplying gravity by 1.5 or 2 once velocity.y is negative: it feels snappier.
Godot165 / 219
Module 16 · A 3D character: movement, physics and the camera
Move relative to where the camera looks
In a third-person game, “forward” means away from the camera, not along the world’s -z. Read the stick or keys with Input.get_vector, which gives a Vector2 with up as negative y. Turn it into a flat Vector3 (x stays x, the stick’s y becomes z), then rotate it around the up axis by the camera pivot’s yaw, its rotation around Y.
Lines 1-2: The running speed, and the camera pivot, the node the mouse turns.
Lines 5-6: Gravity, as before. It only touches velocity.y.
Lines 7-8: Four actions from the input map, as one vector. Its length is at most 1, so diagonals are not faster, and a gamepad stick gives values in between.
Lines 9: Forward on the stick is y = -1, which becomes z = -1: forward in Godot. The y of the move is 0: the player walks, it does not fly.
Lines 10: Rotate that around up by the pivot’s yaw, so pushing forward goes the way the camera looks.
Lines 11-13: Set only x and z, so the falling speed in y is kept, then move.
QuizInteractive · disabled in the PDF
A learner writes var dir := Vector3(input.x, 0.0, -input.y). What happens when they push forward?
A learner writes var dir := Vector3(input.x, 0.0, -input.y). What happens when they push forward?
Godot166 / 219
Module 16 · A 3D character: movement, physics and the camera
Mouse look: capture the mouse, read how far it moved
To turn the camera with the mouse, first capture it: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED hides the pointer and keeps it in the window, so it never stops at the edge. Then, in _unhandled_input, each InputEventMouseMotion has relative, how far the mouse moved since the last event. Turn the pivot around Y by the sideways movement (yaw), and around X by the up and down movement (pitch), clamped so the camera cannot flip over.
The sensitivity is radians per pixel. Esc gives the pointer back, which players expect, and which you need to reach the editor while testing.
Find the bugInteractive · disabled in the PDF
With the mouse captured, the camera turns once and then never moves again. Which line is wrong?
GDScript
Going deeper
Mouse motion should not be multiplied by delta: the mouse already reports a distance, and scaling it by the frame time makes the turn speed change with the frame rate. A gamepad’s right stick is different: it reports a position, so turning from it is a rate, and that one is multiplied by delta in _process.
Godot167 / 219
Module 16 · A 3D character: movement, physics and the camera
A SpringArm3D keeps the camera out of walls
Put the camera behind the player and it will end up inside a wall the moment the player backs against one. A SpringArm3D fixes it: it casts a ray (or a shape, if you give it one) along its own +z for spring_length metres, and places its children where it hits, minus a small margin. Put the Camera3D under it, and the camera slides in front of walls instead of through them.
add_excluded_object(get_rid()) stops the arm hitting the player’s own capsule, which would pull the camera into its head. Setting the arm’s collision_mask to skip the player’s layer does the same.
Put in orderInteractive · disabled in the PDF
Build the third-person camera rig, in order.
1.Keep the arm from hitting the player, with its mask or add_excluded_object
2.Add a SpringArm3D under the pivot, and set its spring_length
3.Add a Node3D called CameraPivot as a child of the player, at head height
4.Add a Camera3D under the spring arm
5.Turn the pivot from the mouse in _unhandled_input
Godot168 / 219
Module 16 · A 3D character: movement, physics and the camera
Turn the model to face where it runs
The body itself does not turn (the camera pivot is its child, and would turn too); the model does. The angle around Y that makes a node’s -z face a flat direction dir is atan2(-dir.x, -dir.z). Snapping to it looks robotic, so ease toward it each frame with lerp_angle, which takes the short way round.
Call face(dir, delta) after working out dir. With no input, the model keeps facing the last way.
QuizInteractive · disabled in the PDF
The model faces an angle of 3.1 radians and should turn to −3.1. Why use lerp_angle and not lerp?
The model faces an angle of 3.1 radians and should turn to −3.1. Why use lerp_angle and not lerp?
Godot169 / 219
Module 16 · A 3D character: movement, physics and the camera
Slopes, steps and sticking to the floor
move_and_slide() decides what is a floor and what is a wall by the angle between the surface and up_direction. Anything flatter than floor_max_angle (45 degrees by default, in radians in code) is floor: you stand on it, and is_on_floor() is true. Steeper is wall: you slide off it. A few properties tune how the body behaves on the ground.
Property
Default
What it does
floor_max_angle
45°
The steepest slope that still counts as floor
floor_snap_length
0.1 m
How far down to look for floor to stick to, going over a crest or down steps
floor_stop_on_slope
on
Standing still on a slope does not slide you down it
floor_constant_speed
off
On: the same speed up and down a slope, not slower uphill
up_direction
Vector3.UP
Which way is up, for floor and ceiling tests
QuizInteractive · disabled in the PDF
A ramp is 50 degrees steep and floor_max_angle is left at the default. What happens when the player runs at it?
A ramp is 50 degrees steep and floor_max_angle is left at the default. What happens when the player runs at it?
Going deeper
Floor snapping is skipped while the body moves upward, which is why a jump still leaves the ground with snapping on. If a moving platform or a launch pad sets an upward speed, it works the same. apply_floor_snap() snaps right away, for after a teleport. Real stairs are best done with an invisible ramp collision over them: the capsule walks the ramp while the stairs are only what you see.
Godot170 / 219
Module 16 · A 3D character: movement, physics and the camera
RigidBody3D: things physics moves for you
A RigidBody3D is moved by the physics engine: it has mass, falls, bounces and tumbles. You do not set its position each frame; you push it with forces and impulses, like apply_central_impulse(v), a one-off kick. A CharacterBody3D running into a crate does not push it on its own, because it has no mass. After move_and_slide(), look at what it hit, and kick each rigid body away.
Find the bugInteractive · disabled in the PDF
Crates fly toward the player when touched, instead of being pushed away. Which line is wrong?
GDScript
Kicks add up. This runs every physics frame while the player keeps touching the crate, so the impulses add up into a steady push. A heavier crate (mass) moves less from the same kick. Keep push_force small, and consider multiplying it by how fast the player moves into the crate, so leaning on one does not launch it.
Godot171 / 219
Module 16 · A 3D character: movement, physics and the camera
Area3D and raycasts in 3D
The 2D tools have 3D twins. An Area3D with a shape detects bodies entering it, through body_entered and body_exited: checkpoints, pickups, a kill zone under the level. A RayCast3D node checks one line every physics frame, toward its target_position. For a one-off check from code, ask the physics space directly with intersect_ray.
res://player/interact.gd
1funclook_target(camera:Camera3D)->Object:2var from := camera.global_position3var to := from - camera.global_transform.basis.z *3.04var query :=PhysicsRayQueryParameters3D.create(from, to)5 query.exclude =[get_rid()]6var space :=get_world_3d().direct_space_state7var result := space.intersect_ray(query)8if result.is_empty():9returnnull10return result["collider"]
What is the player looking at, within 3 metres? Call it from _physics_process: the space can only be queried safely there.
Fill the blanksInteractive · disabled in the PDF
A RayCast3D under the player points down 1.5 metres. Finish the ground check.
Module 16 · A 3D character: movement, physics and the camera
Prototype levels with CSG shapes
Before modelling a level, block it out to test how it plays. CSG (constructive solid geometry) nodes are shapes you combine: CSGBox3D, CSGCylinder3D, CSGSphere3D, CSGPolygon3D and more, each with an operation of union (add), subtraction (cut away) or intersection (keep the overlap). Children are combined into their CSG parent. Turn on use_collision on the top CSG node and the player can walk on it at once.
QuizInteractive · disabled in the PDF
You have a CSGBox3D wall. How do you cut a doorway through it?
You have a CSGBox3D wall. How do you cut a doorway through it?
In the Godot editorInteractive · disabled in the PDF
Godot173 / 219
Module 16 · A 3D character: movement, physics and the camera
Recap
CharacterBody3D moves by velocity and move_and_slide(), with a capsule collision shape.
Gravity subtracts from velocity.y every frame in the air; a jump sets it once. Height grows with speed squared.
Read input with get_vector, make it a flat Vector3, rotate it by the camera’s yaw.
Capture the mouse and turn by relative, clamping the pitch.
A SpringArm3D keeps the camera out of walls; exclude the player from it.
Turn the model, not the body, with atan2 and lerp_angle.
floor_max_angle splits floors from walls; snapping keeps you on the ground.
Push RigidBody3Ds with impulses from the slide collisions; check with Area3D, RayCast3D and intersect_ray.
Block out levels with CSG, then replace them with real meshes.
FlashcardsInteractive · disabled in the PDF
Where does gravity come from in a script?
ProjectSettings.get_setting("physics/3d/default_gravity"), or get_gravity() since 4.3.
A child shape that cuts its volume out of its parent.
Module 17: Shaders and visual effects
Godot174 / 219
Module 17 · Shaders and visual effects
A shader is a tiny program the graphics card runs for every pixel
Your GDScript runs on the processor, one line after another. A shader runs on the GPU, the graphics card, which runs the same small program thousands of times at once. Drawing a thing has two programmable stages: the vertex shader runs once per corner (vertex) of the mesh and can move it; the GPU then fills in the triangles; the fragment shader runs once per pixel covered and decides its colour. Every material you used so far is a shader Godot wrote for you.
The mesh’s corners go to the GPU: four for a sprite, thousands for a character.
vertex() runs for each corner, and may move it: swaying grass, waves.
The GPU works out which pixels each triangle covers. You do not write this part.
fragment() runs for each of those pixels, all in parallel, and sets its colour.
The colours are blended into the image on screen.
One draw, step by step. Your code goes in the two accented stages.
QuizInteractive · disabled in the PDF
A sprite covers 100 by 100 pixels on screen. How many times does its fragment shader run each frame?
A sprite covers 100 by 100 pixels on screen. How many times does its fragment shader run each frame?
Godot175 / 219
Module 17 · Shaders and visual effects
Your first shader: a canvas_item that changes colours
Select a Sprite2D, set its Material to a new ShaderMaterial, and give that a new Shader. The first line says what kind it is: shader_type canvas_item; for 2D and UI, spatial for 3D. In fragment(), COLOR comes in as the sprite’s colour at this pixel (its texture times its modulate), and whatever you leave in it is drawn. The language looks like C: every statement ends with ;, and types are written first.
Colours are 0 to 1 per channel, so 1 minus each one gives the negative. COLOR.a, the transparency, is kept.
Floats need a dot. The shading language does not convert between int and float for you. COLOR.rgb * 2 is an error; write 2.0. vec3(1.0) is vec3(1.0, 1.0, 1.0): one value fills every component.
Fill the blanksInteractive · disabled in the PDF
Turn the sprite grey: work out its brightness and use it for all three channels.
UV is a vec2 saying where the current pixel is on the sprite’s texture: (0, 0) at the top-left, (1, 1) at the bottom-right in a canvas_item. texture(TEXTURE, uv) reads the texture’s colour at any UV you like, which is how shaders move, stretch and mirror pictures: read from somewhere other than where you are.
TIME is the number of seconds since the game started, the same for every pixel. Feed it into sin and you get something that swings smoothly between −1 and 1, forever. A pulse, a shimmer, swaying grass: they are all sin(TIME * speed), shifted and scaled into the range you want.
Brightness
Pulses per second0.48
Brightness of a pulsing pickup: 0.75 + 0.25 × sin(speed × TIME). Drag the speed.
Grass that sways. VERTEX is the corner’s position in pixels; the top corners (UV.y = 0) move, the bottom ones stay put.
QuizInteractive · disabled in the PDF
Every tuft of grass using this shader sways exactly in step, which looks fake. What fixes it?
Every tuft of grass using this shader sways exactly in step, which looks fake. What fixes it?
Godot178 / 219
Module 17 · Shaders and visual effects
Uniforms: knobs your scripts can turn
A uniform is a value set from outside the shader, the same for every pixel in a draw: it shows in the inspector under Shader Parameters, and scripts set it with set_shader_parameter. Hints shape how it is edited: source_color shows a colour picker, hint_range(0.0, 1.0) a slider. Here is the classic flash when an enemy is hit: blend toward white by an amount the script controls.
@onreadyvar sprite:Sprite2D=$Sprite2Dfunctake_hit()->void:var mat := sprite.material asShaderMaterial mat.set_shader_parameter("flash",1.0)var tw :=create_tween() tw.tween_property(mat,"shader_parameter/flash",0.0,0.15)
Find the bugInteractive · disabled in the PDF
The sprite turns white on a hit and stays white; Godot reports a missing property. Which line is wrong?
GDScript
Godot179 / 219
Module 17 · Shaders and visual effects
A material is shared until you make it unique
A ShaderMaterial is a resource, and resources are shared: every enemy made from enemy.tscn uses the same material. Set flash on one, and they all flash. Give each instance its own copy: tick Local to Scene (resource_local_to_scene) on the material in the inspector, or duplicate it in _ready().
Each enemy now has its own copy. The shader inside is still shared, which is what you want: it is compiled once.
QuizInteractive · disabled in the PDF
You hit one slime and all twelve slimes on screen flash white. What is the cause?
You hit one slime and all twelve slimes on screen flash white. What is the cause?
Going deeper
A shader can also declare an instance uniform: a value stored per node rather than per material, set with set_instance_shader_parameter, so nothing is duplicated. In 3D it is stored on each GeometryInstance3D; since Godot 4.4, 2D CanvasItems have it too. That keeps one material for many objects, each with its own tint or flash.
Godot180 / 219
Module 17 · Shaders and visual effects
Dissolve: noise decides which pixels vanish first
To make an enemy burn away, read a noise texture: a NoiseTexture2D with a FastNoiseLite, a cloudy picture of greys from 0 to 1. Each pixel compares its noise value with a progress uniform; below it, discard throws the pixel away so nothing is drawn. Pixels just above the threshold get a glowing edge. Tween progress from 0 to 1 and the sprite dissolves in a ragged pattern.
smoothstep(a, b, x) rises smoothly from 0 to 1 as x goes from a to b. The step line turns the edge off while progress is 0.
Kept
Edge glow
Share of pixels gone (even noise)0.4
For each noise value: is the pixel kept, and how much it glows. Drag progress: the cut moves right, and the glow rides just above it.
Fill the blanksInteractive · disabled in the PDF
Finish the core of the dissolve.
Godot shader
1voidfragment() {2float n =(noise,UV).r;3if(n <) {4;5 }6}
Godot181 / 219
Module 17 · Shaders and visual effects
Spatial shaders paint 3D surfaces
A shader_type spatial; shader colours 3D meshes, and Godot still does the lighting for you: in fragment() you set the surface’s properties, ALBEDO, ROUGHNESS, METALLIC, EMISSION, and Godot lights them. NORMAL is the way the surface faces and VIEW the way toward the camera, both in view space. Their dot is 1 where the surface faces you and 0 at its edges: a rim or fresnel glow uses that.
res://effects/shield.gdshader
1shader_typespatial;23uniformvec4 base :source_color=vec4(0.1,0.2,0.4,1.0);4uniformvec4 rim :source_color=vec4(0.3,0.8,1.0,1.0);5uniformfloat rim_power :hint_range(0.5,8.0)=3.0;67voidfragment() {8ALBEDO= base.rgb;9ROUGHNESS=0.6;10float facing =clamp(dot(NORMAL,VIEW),0.0,1.0);11EMISSION= rim.rgb *pow(1.0- facing, rim_power);12}
A sphere with this looks like an energy shield: dark in the middle, glowing at the edges.
Glow
Rim glow against how directly the surface faces the camera. A higher power keeps the glow to a thinner edge.
QuizInteractive · disabled in the PDF
You want a ghost that ignores every light in the scene and is always drawn in its own flat colour. What do you add?
You want a ghost that ignores every light in the scene and is always drawn in its own flat colour. What do you add?
Godot182 / 219
Module 17 · Shaders and visual effects
Visual shaders: the same thing as a graph
A VisualShader is a shader built from boxes and wires instead of text: nodes for textures, maths, TIME, UV and the outputs, joined in a graph. Godot turns the graph into the same shading language you have been writing, so it is no slower or faster. It suits artists and quick experiments; text is easier to review, diff and reuse in larger projects. You can also start from a StandardMaterial3D and turn it into a ShaderMaterial (right-click it, Convert to ShaderMaterial) to see and edit the code Godot uses.
QuizInteractive · disabled in the PDF
Which statements about visual shaders are true?
Which statements about visual shaders are true?
Godot183 / 219
Module 17 · Shaders and visual effects
Post-processing: a shader over the whole screen
Effects over the whole picture (a vignette, a damage tint, a retro look) read what has already been drawn. In a canvas_item, declare a sampler with hint_screen_texture, and read it at SCREEN_UV, this pixel’s place on the screen. Put the shader on a ColorRect that fills a CanvasLayer above the game, with mouse_filter set to ignore so it does not block clicks. Try the built-in effects first: the Environment already has glow, fog, and brightness, contrast and saturation adjustments.
res://effects/vignette.gdshader
1shader_typecanvas_item;23uniformsampler2D screen : hint_screen_texture,filter_linear_mipmap;4uniformfloat strength :hint_range(0.0,1.0)=0.5;56voidfragment() {7vec3 c =texture(screen,SCREEN_UV).rgb;8float d =distance(UV,vec2(0.5));9 c *=1.0-smoothstep(0.3,0.8, d)* strength;10COLOR=vec4(c,1.0);11}
A vignette: pixels far from the centre are darkened. UV is the place on the rectangle, SCREEN_UV the place on the screen.
Find the bugInteractive · disabled in the PDF
This shader comes from a Godot 3 tutorial and does not compile in Godot 4. Which line is wrong?
Godot shader
Godot184 / 219
Module 17 · Shaders and visual effects
What makes shaders slow
A fragment shader’s cost is roughly its work per pixel times the pixels it covers. A full-screen effect at 1920 by 1080 runs over 2 million times a frame, 124 million times a second at 60 frames per second, and at 4K four times that. Transparent things drawn over each other (overdraw) run the fragment shader again for the same pixel.
Millions of pixels a full-screen shader runs on, per frame, by resolution.
Cost
Why
What to do
Texture reads
Each texture() call fetches memory
Read fewer, smaller textures; pack greys into one texture’s channels
Overdraw
Overlapping transparent pixels each run
Keep big transparent quads few; prefer opaque
discard in 3D
Stops early depth tests
Use only where needed, such as foliage and dissolves
Compiling
A new shader compiles the first time it is drawn: a stutter
Show effects once during a loading screen; reuse shaders
Put in orderInteractive · disabled in the PDF
Order these from cheapest to most expensive, all at 1920×1080, the same shader on each.
1.One full-screen effect
2.Three full-screen effects stacked on top of each other
3.A 512×512 sprite
4.A 64×64 sprite
In the Godot editorInteractive · disabled in the PDF
Godot185 / 219
Module 17 · Shaders and visual effects
Recap
Shaders run on the GPU: vertex() once per corner, fragment() once per pixel, all in parallel.
shader_type canvas_item for 2D and UI, spatial for 3D; floats need a dot.
COLOR, UV, TEXTURE and TIME are the 2D basics; texture() reads a sampler.
Uniforms are knobs: hints for the inspector, set_shader_parameter and shader_parameter/ in tweens.
Materials are shared resources: make them local to the scene to change one instance.
discard and noise make dissolves; smoothstep makes soft edges.
Spatial shaders set ALBEDO, ROUGHNESS, EMISSION; Godot lights them unless unshaded.
Screen effects read a hint_screen_texture at SCREEN_UV; Godot 4 has no SCREEN_TEXTURE.
Cost is work per pixel times pixels: watch full-screen passes, overdraw and texture reads.
FlashcardsInteractive · disabled in the PDF
Vertex shader
Runs once per vertex, and can move it.
Fragment shader
Runs once per covered pixel, and sets its colour.
UV in a canvas_item
(0, 0) top-left to (1, 1) bottom-right of the texture.
TIME
Seconds since the game started, the same for every pixel.
They share a material: tick Local to Scene, or duplicate() it.
discard
Throws the pixel away: nothing is drawn there.
Fresnel or rim
pow(1.0 - dot(NORMAL, VIEW), power): bright at the edges.
Reading the screen in Godot 4
uniform sampler2D screen : hint_screen_texture; and SCREEN_UV.
Module 18: Architecture: autoloads, custom resources and patterns
Godot186 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
A game grows, so plan for change
A jam game can live in one folder and three scripts. A game you work on for a year cannot: you add enemies, items, levels and menus, and every change starts to break something far away. Architecture is the set of decisions that keep a project easy to change: where files go, which scripts know about which, and where data lives. None of it is required by Godot; all of it pays off once a project passes a few thousand lines.
res://
autoload/# the few global scripts
events.gd
game_state.gd
common/
components/# reusable child nodes
health_component.gd
player/# one folder per feature
player.tscn
player.gd
player_sprites.png
enemies/
slime/
slime.tscn
slime.gd
items/
item_data.gd
coin.tres
levels/
level_01.tscn
ui/
hud.tscn
addons/# plugins, such as a test framework
test/# automated tests
Grouped by feature: everything the player needs sits together. Godot names files in snake_case.
QuizInteractive · disabled in the PDF
Why group files by feature (player/, enemies/slime/) rather than by type (scripts/, scenes/, sprites/)?
Why group files by feature (player/, enemies/slime/) rather than by type (scripts/, scenes/, sprites/)?
Godot187 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
Each feature is a scene that can run alone
A good scene is a small, self-contained machine: the player scene holds its sprite, collision shape, camera and script, and works when you open it and press Run Current Scene (F6). If a scene only works when some other scene is around it, it secretly depends on that scene, and every such hidden dependency is a future bug.
A scene owns its children: it may reach down into them freely.
It does not reach up to its parent or sideways to siblings: it doesn’t know where it will be placed.
What it needs from outside comes in through @export variables or a setup function the parent calls.
What it wants to tell the outside goes out through signals.
Hidden dependency
extendsArea2Dfunc_ready()->void:var player =get_node("/root/Main/Player") player.coins +=1
Given from outside
extendsArea2Dsignalcollected(value:int)@exportvar value :=1
A quick test. Open any scene and press F6. If it crashes with “Node not found”, it depends on something outside itself. Fix that before it spreads.
Godot188 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
Call down, signal up
The rule that keeps the scene tree untangled: a parent calls methods on its children, because it owns them and knows they are there. A child emits signals, because it doesn’t know who, if anyone, is listening. Dependencies then point one way, downwards, so you can reuse a child anywhere.
The player touches the coin. It emits collected, not knowing who listens.
The level connected to it, so it hears the signal and adds to the score.
The level calls down into the HUD, which it owns, to show the new total.
Calls go down the tree, signals come up it.
Find the bugInteractive · disabled in the PDF
This coin only works in one exact tree. Which line breaks the rule?
GDScript
Godot189 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
An event bus for news the whole game hears
Signals up the tree work for neighbours. But the achievement system, the music and the HUD may all care that the player died, and they live in unrelated branches. Wiring signals through every parent in between is tedious. An event bus is an autoload (a node Godot adds at the root, available everywhere by its name) that only declares signals. Anyone emits on it; anyone connects to it; nobody needs to know anybody else.
res://autoload/events.gd
1# Added in Project Settings > Globals > Autoload as "Events"2extendsNode34@warning_ignore("unused_signal")5signalcoin_collected(value:int)6@warning_ignore("unused_signal")7signalplayer_died8@warning_ignore("unused_signal")9signallevel_finished(level_id:StringName)
The bus holds no state and no logic: only the list of things that can happen in the game.
Fill the blanksInteractive · disabled in the PDF
Emit on the bus from the player, and listen to it from the music player.
Don’t make everything global. An event bus is easy to overuse. When every signal goes through it, you can no longer tell from a scene who talks to whom, and any script anywhere can fire any event. Use it for game-wide news (died, level finished, settings changed), and plain signals between a parent and its children. The @warning_ignore("unused_signal") lines silence the warning Godot gives for a signal a script declares but never emits itself, which is the whole point of a bus.
Godot190 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
Components: behaviour as child nodes
The player, a slime and a breakable crate can all take damage. Inheritance would put health in a shared base class, but a crate is a StaticBody2D and the slime a CharacterBody2D, so they can’t share one. Composition solves it: health becomes its own small node, a component, that you add as a child of anything that has health.
WalkthroughInteractive · disabled in the PDF
res://common/components/health_component.gd
1class_nameHealthComponent2extendsNode34signalhealth_changed(current:int, maximum:int)5signaldied67@exportvar max_health :=1089@onreadyvar health := max_health1011funcdamage(amount:int)->void:12if health <=0:13return14 health =maxi(health - amount,0)15 health_changed.emit(health, max_health)16if health ==0:17 died.emit()
Lines 1-2:class_name makes it a type: it appears in the Create Node dialog and other scripts can write HealthComponent.
Lines 4-5: It says what happens through signals; it never decides what dying means.
Lines 7-9: The maximum is set per instance in the Inspector. @onready copies it into health when the node is ready, after the Inspector’s value is applied.
Lines 11-13: Damage after death is ignored, so died can only fire once.
Lines 14-17:maxi is the integer max, so health never goes below 0. Then it reports the change, and the death.
The slime decides what death means for a slime. A crate connects died to “spawn some coins”.
QuizInteractive · disabled in the PDF
Which is the best sign that a piece of behaviour should become a component?
Which is the best sign that a piece of behaviour should become a component?
Godot191 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
Custom resources hold the data
A sword and a potion behave the same in the inventory; they differ in data: name, icon, price. Put the data in a custom Resource, a class that extends Resource, and each item becomes a .tres file a designer edits in the Inspector, with no code. The scripts read the data and stay the same for every item.
In the FileSystem dock: right-click a folder > Create New > Resource…, pick ItemData, save as sword.tres, and fill it in.
QuizInteractive · disabled in the PDF
Ten slimes each have @export var stats: EnemyStats set to the same slime_stats.tres. One slime runs stats.health -= 5. What happens?
Ten slimes each have @export var stats: EnemyStats set to the same slime_stats.tres. One slime runs stats.health -= 5. What happens?
Godot192 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
The command pattern: actions as objects
In a puzzle game you want undo. If every move is a function call, it is gone once it has run. The command pattern turns each action into an object with execute and undo. Keep a list of the commands that ran, and undo is popping the last one and calling its undo. The same objects can be recorded for replays, or sent over a network.
Module 18 · Architecture: autoloads, custom resources and patterns
The state pattern: one object per state
You met state machines for enemies earlier, written with an enum and match. That works until each state has a lot of code. The state pattern gives each state its own object (often a child node) with enter, exit and an update function, and a small machine that holds the current one and switches between them. Each state’s code is then in one file, and adding a state touches nothing else.
res://common/state_machine.gd
1class_nameStateMachine2extendsNode34@exportvar initial:State56var current:State78func_ready()->void:9 current = initial10 current.enter()1112func_physics_process(delta:float)->void:13var next := current.physics_update(delta)14if next !=nulland next != current:15 current.exit()16 current = next17 current.enter()
State is a base class with enter(), exit() and physics_update(delta) -> State, which returns the state to switch to, or null to stay.
Put in orderInteractive · disabled in the PDF
Put the steps of a state change in order.
1.The current state’s update returns a different state
2.The machine calls exit() on the old state
3.The machine calls enter() on the new state
4.Next frame, the new state’s update runs
5.The machine makes the new state the current one
Godot194 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
Testing GDScript with GUT or gdUnit4
Godot has no test runner built in, but two free add-ons are widely used: GUT (Godot Unit Test) and gdUnit4. Both install from the Asset Library into addons/, give you a panel in the editor to run tests, and can run from the command line in continuous integration. A test is a function that sets something up, does something, and asserts what should be true. Logic in components and resources, like HealthComponent, is the easiest to test.
res://test/test_health_component.gd
1extendsGutTest23functest_damage_lowers_health()->void:4var health :=HealthComponent.new()5 health.max_health =106add_child_autofree(health)7 health.damage(3)8assert_eq(health.health,7)910functest_dies_once_at_zero()->void:11var health :=HealthComponent.new()12add_child_autofree(health)13watch_signals(health)14 health.damage(100)15 health.damage(1)16assert_signal_emit_count(health,"died",1)
GUT runs every function whose name starts with test_. add_child_autofree adds the node to the tree (so _ready runs) and frees it after the test.
Fill the blanksInteractive · disabled in the PDF
Finish a GUT test that checks health never goes below zero.
GDScript
1extends23funchealth_stops_at_zero()->void:4var health :=HealthComponent.new()5add_child_autofree(health)6 health.damage(50)7(health.health,0)
Going deeper
Test the parts that decide things: damage, inventories, save-file upgrades, state transitions. Feel (does the jump feel good?) is tested by playing. Code that is hard to test is often code that reaches into the scene tree for what it needs; passing those things in, or using components, makes it testable and reusable at once.
Godot195 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
Try it in the editor
Restructure a small gameInteractive · disabled in the PDF
Godot196 / 219
Module 18 · Architecture: autoloads, custom resources and patterns
Recap
Group files by feature; move them in the FileSystem dock so paths update.
A scene should run alone (F6). What it needs comes in through exports; what it says goes out through signals.
Call down, signal up: parents call children, children emit signals.
An event bus autoload carries game-wide news. Use it for that, not for everything.
Components are child nodes with one job, shared by unrelated objects.
Custom resources hold data; they are shared, so keep changing values on the node.
The command pattern makes actions objects, for undo and replays; the state pattern gives each state its own object.
GUT and gdUnit4 add automated tests; test the logic, play-test the feel.
FlashcardsInteractive · disabled in the PDF
Call down, signal up
Parents call methods on children; children emit signals to whoever listens.
Event bus
An autoload that only declares signals, so distant parts of the game can talk.
Autoload
A script or scene Godot adds at the root on start, reachable everywhere by its name.
Component
A child node with one reusable behaviour, such as health or a hitbox.
Why can’t two enemies keep current health in one shared resource?
A loaded resource is one object shared by every user, so a change affects all of them.
Command pattern
Each action is an object with execute and undo, kept in a history.
State pattern
Each state is an object with enter, exit and update; a machine switches them.
How does GUT find tests?
Functions starting with test_ in scripts that extend GutTest.
Module 19: Performance, debugging and shipping
Godot197 / 219
Module 19 · Performance, debugging and shipping
The debugger stops the game so you can look inside
When a script fails, Godot pauses the running game and opens the Debugger panel at the bottom of the editor. Its Stack Trace shows which function was running, which function called it, and so on back to the start; click a line to jump to the code. Beside it you see every local variable and member at that moment. The Errors tab lists errors and warnings with the line they came from.
You can also stop the game on purpose. A breakpoint is a line where the game pauses when it gets there: click left of the line number in the script editor (a red dot appears), or write the keyword breakpoint in the code. Once paused, Step Into runs one line, going into any function it calls; Step Over runs one line without going into calls; Continue runs on until the next breakpoint.
GDScript
1functake_damage(amount:int)->void:2if amount >50:3breakpoint# pause here, only for suspicious hits4 health -= amount
A breakpoint inside an if pauses only in the case you are hunting. Remove it before you ship.
QuizInteractive · disabled in the PDF
The game is paused on the line var path := find_path(start, goal). You only care whether path comes out empty. Which button do you press?
The game is paused on the line var path := find_path(start, goal). You only care whether path comes out empty. Which button do you press?
Godot198 / 219
Module 19 · Performance, debugging and shipping
The remote scene tree shows the running game
While the game runs, the Scene dock gets two tabs: Local is the scene saved on disk, and Remote is the tree of the running game as it is right now. Click a node in Remote and the Inspector shows its live values: position, health, which animation is playing. You can even change them and watch the game react.
An enemy has vanished? Look for it in Remote: is it there but invisible, off screen, or gone?
Bullets piling up? Count them in Remote: if they never go away, something forgot queue_free().
A wrong value? Edit it in the Inspector to see if that fixes it, then fix the code.
Changes made in Remote are lost when the game stops: they never touch your saved scenes.
QuizInteractive · disabled in the PDF
You spawn coins from code. During play, the Local tab shows no coins under the level. What does that tell you?
You spawn coins from code. During play, the Local tab shows no coins under the level. What does that tell you?
Godot199 / 219
Module 19 · Performance, debugging and shipping
Printing, warnings and asserts
Function
What it does
print(a, b)
Writes to the Output panel. Fast to add, easy to drown in.
print_debug(a)
Prints, plus the file, line and function it was called from.
push_warning("…")
A warning in the Debugger’s Errors tab, with a stack trace. The game goes on.
push_error("…")
The same, as an error. Still doesn’t stop the game.
assert(cond, "…")
Pauses with the message if cond is false. Only in debug builds.
GDScript
1funcload_level(index:int)->void:2assert(index >=0, "level index can't be negative")3if index >= levels.size():4push_warning("No level %d, going to the menu"% index)5get_tree().change_scene_to_file(MENU)6return7get_tree().change_scene_to_packed(levels[index])
assert for “this is a bug in my code”; push_warning for “this can happen, and here is what I do about it”.
Predict the outputInteractive · disabled in the PDF
This line works while you develop. What happens in the exported release build?
GDScript
1assert(inventory.remove_item(key), "no key")
This line works while you develop. What happens in the exported release build?
Godot200 / 219
Module 19 · Performance, debugging and shipping
A frame has a budget, measured in milliseconds
At 60 frames per second, each frame has ms for everything: your scripts, physics, and drawing. Go over, and the game drops frames, which players feel as stutter. Performance work is finding where those milliseconds go. Measure first: a guess about what is slow is wrong surprisingly often.
An example frame from the profiler: scripts are over half the budget, so that is where to look first.
Debugger tab
Use it for
Profiler
Time per function, frame by frame. Start it, play the slow part, stop, read.
Visual Profiler
Time on the CPU and GPU for each part of drawing.
Monitors
Graphs over time: FPS, node and object counts, memory, draw calls.
Your own graph in the Monitors tab. Performance.get_monitor(Performance.TIME_FPS) reads a built-in one from code.
Godot201 / 219
Module 19 · Performance, debugging and shipping
Common fixes once you know what is slow
Do less per frame: cache nodes in @onready variables instead of searching for them in _process.
Stop what isn’t needed: set_process(false) on idle nodes, and a VisibleOnScreenEnabler2D to pause enemies off screen.
Fewer physics checks: put things on collision layers so a mask only checks what it can hit; use simple shapes (circles, rectangles) rather than polygons.
Fewer nodes: hundreds of bullets each with a Timer cost more than one manager that counts their lifetimes.
Reuse instead of create: an object pool, next slide, when creating and freeing shows up in the profiler.
Find the bugInteractive · disabled in the PDF
The profiler says this enemy script is slow with 200 enemies. Which line is the problem?
GDScript
Godot202 / 219
Module 19 · Performance, debugging and shipping
An object pool reuses nodes instead of freeing them
A pool makes a fixed number of bullets when the level starts, hidden and switched off. Firing takes one from the pool and switches it on; a bullet that hits something or leaves the screen is switched off and given back, never freed. Nothing is created or freed during play.
Lines 4-5: Which scene to pool and how many: set in the Inspector.
Lines 7: The bullets not in use. A typed array, so mistakes show up as errors.
Lines 9-13: All the bullets are made once, at the start, and put straight into the pool, switched off.
Lines 15-21: Taking one: none left returns null (the caller skips the shot, or the pool could grow). Otherwise it is switched on and shown.
Lines 23-26:PROCESS_MODE_DISABLED stops its _process and _physics_process; a physics body also leaves the simulation (its disable_mode). Then it waits in the list.
Fill the blanksInteractive · disabled in the PDF
A bullet from the pool keeps its old position and direction. Reset it when it is fired.
Pool only when the profiler asks. Instancing a small scene in Godot is fast, and a pool adds bugs of its own (stale state, a bullet given back twice). Reach for one when creating and freeing nodes really shows in the profiler, as with hundreds of bullets a second, not by default.
Godot203 / 219
Module 19 · Performance, debugging and shipping
Exporting: presets and export templates
To give your game to players, you export it. Godot builds the game from two parts: an export template, a copy of the engine without the editor, made for one platform, and a .pck file with your scenes, scripts and assets packed together (or embedded in the executable). Templates are downloaded once per Godot version from Editor > Manage Export Templates, and must match your editor’s version exactly.
Project > Export, then Add… a preset for each platform: Windows Desktop, Linux, macOS, Web, Android…
Pick options: the icon, the file name, which resources to include.
Press Export Project. Untick Export With Debug for the build players get.
Presets are saved in export_presets.cfg at the project root; commit it so everyone exports the same way.
Terminal
1# the same export from a terminal, as a build server would do it2mkdir-pbuild/linuxbuild/web# the folders must already exist3godot--headless--export-release"Linux"build/linux/game.x86_644godot--headless--export-release"Web"build/web/index.html
The name in quotes is the preset’s name, as it appears in the Export dialog.
QuizInteractive · disabled in the PDF
What is the difference between a debug export and a release export?
What is the difference between a debug export and a release export?
Godot204 / 219
Module 19 · Performance, debugging and shipping
Desktop, web and mobile each need something different
Platform
You get
Worth knowing
Windows, Linux
An executable and a .pck
Exports from any desktop OS.
macOS
A .app in a zip
Players get warnings unless it is signed and notarised, which needs an Apple developer account.
Web
index.html, .js, .wasm, .pck
Uses the Compatibility renderer. Must be served over HTTP, not opened as a file. C# projects can’t export to the web in Godot 4.
Android
An .apk or .aab
Needs the Android SDK, a Java JDK and a keystore to sign with; set their paths in Editor Settings.
iOS
An Xcode project
Needs a Mac with Xcode to build and sign.
Threads on the web. A web export with Thread Support turned on needs the page served with cross-origin isolation headers, which many hosts don’t send. Since Godot 4.3 the Web preset can be exported without threads, which works almost everywhere; leave thread support off unless you need it.
QuizInteractive · disabled in the PDF
You double-click the exported index.html and the game shows an error instead of starting. What is the likely reason?
You double-click the exported index.html and the game shows an error instead of starting. What is the likely reason?
Godot205 / 219
Module 19 · Performance, debugging and shipping
Putting it on itch.io
itch.io is a store for independent games where anyone can publish, free or paid. Create a project page, upload a zip per platform and tick which platform each is for. For a web build, zip the export folder with index.html at the top of the zip, set the project kind to HTML, and tick “This file will be played in the browser”.
Terminal
1# butler, itch.io's upload tool, sends only what changed2butlerpushbuild/webyour-name/your-game:html53butlerpushbuild/linuxyour-name/your-game:linux
The part after the colon is the channel: one per platform. Run butler login once first.
Put in orderInteractive · disabled in the PDF
Put shipping a web version in order.
1.Export a release build into an empty folder
2.Test it in a browser through a local server
3.Add a Web preset in Project > Export
4.Download the export templates for your exact Godot version
5.Upload the folder as a zip to itch.io as an HTML project
Godot206 / 219
Module 19 · Performance, debugging and shipping
Try it in the editor
Debug, measure and shipInteractive · disabled in the PDF
Godot207 / 219
Module 19 · Performance, debugging and shipping
Recap
The debugger pauses on errors and breakpoints; the stack trace shows how you got there.
The Remote tab is the live game’s tree; Local is the saved scene.
push_warning and push_error report and go on; assert stops, and is removed from release builds.
A frame at 60 FPS has about 16.7 ms. Measure with the profiler before changing anything.
Cache instead of searching every frame; switch off what isn’t needed; pool only when the profiler asks.
Exporting needs templates matching your version, and a preset per platform.
Web builds must be served over HTTP; itch.io takes a zip, or a butler push.
FlashcardsInteractive · disabled in the PDF
Breakpoint
A line where the running game pauses so you can inspect it.
Step Over vs Step Into
Over runs the whole line; Into goes inside the functions it calls.
Remote tab
The scene tree of the game that is running right now, with live values.
Why no work inside assert?
Release builds drop the whole assert, work included.
Frame budget at 60 FPS
About 16.7 ms for scripts, physics and drawing together.
Object pool
Nodes made once and reused, switched on and off instead of freed.
Export template
The engine without the editor, built for one platform and one version.
export_presets.cfg
Where export presets are saved; commit it.
Module 20: Expert Godot
Godot208 / 219
Module 20 · Expert Godot
Tool scripts run inside the editor
Normally a script only runs when the game runs. Put @tool on the first line and it also runs in the editor, so a node can draw itself, check its settings or build its children while you edit the scene. A setter on an exported variable (code that runs whenever the value is set) is the usual way to react when you change it in the Inspector.
res://common/zone.gd
1@tool2extendsNode2D34@exportvar radius :=32.0:5set(value):6 radius = value7queue_redraw()89@exportvar color :=Color.ORANGE:10set(value):11 color = value12queue_redraw()1314func_draw()->void:15draw_circle(Vector2.ZERO, radius, color)
Drag radius in the Inspector and the circle follows. queue_redraw() asks Godot to call _draw again.
Find the bugInteractive · disabled in the PDF
This sprite should move while the game plays, and stay still in the editor. It does the opposite. Which line is wrong?
GDScript
Going deeper
Tool scripts can damage scenes. A tool script that frees nodes, or changes values in _process, does it to the scene you are editing, and you may save the damage. Keep tool code small, guard it with Engine.is_editor_hint(), and use version control. Since Godot 4.4, @export_tool_button("Bake") on a Callable variable puts a button in the Inspector that runs it, which is safer than doing work every frame.
Godot209 / 219
Module 20 · Expert Godot
Editor plugins add docks and tools to Godot
An editor plugin is a tool script that extends EditorPlugin and lives in res://addons/<name>/ with a plugin.cfg that describes it. When you enable it in Project Settings > Plugins, Godot calls its _enter_tree; when you disable it, _exit_tree. Between the two it can add docks, buttons in the toolbar, custom node types and importers.
res://addons/level_notes/plugin.cfg
1[plugin]23name="Level Notes"4description="A dock for notes about the open scene."5author="You"6version="1.0"7script="plugin.gd"
WalkthroughInteractive · disabled in the PDF
res://addons/level_notes/plugin.gd
1@tool2extendsEditorPlugin34const DOCK :=preload("res://addons/level_notes/dock.tscn")56var dock:Control78func_enter_tree()->void:9 dock = DOCK.instantiate()10add_control_to_dock(DOCK_SLOT_RIGHT_UL, dock)1112func_exit_tree()->void:13remove_control_from_docks(dock)14 dock.free()
Lines 1-2: A plugin is always a tool script, since it only ever runs in the editor.
Lines 4: The dock is an ordinary scene of Control nodes, built in the editor like any UI.
Lines 8-10: Enabling the plugin makes the dock and puts it in a slot, here the upper right. (Godot 4.6 added add_dock() with an EditorDock, still marked experimental; add_control_to_dock works in every 4.x, but is now deprecated.)
Lines 12-14: Disabling it must undo everything it added, or the dock stays behind until the editor restarts.
Put in orderInteractive · disabled in the PDF
Put making a plugin in order.
1.Give it a name and folder; Godot writes plugin.cfg and the script
2.In Project Settings > Plugins, press Create New Plugin
3.Write _exit_tree to remove all of it
4.Tick the plugin’s Enable box and try it
5.Write _enter_tree to add what the plugin provides
Godot210 / 219
Module 20 · Expert Godot
Custom node types with class_name and an icon
You have used class_name to give a script a type. The same line also adds it to the Create Node dialog, under the class it extends, as if it were built in. Add @icon with a path to a small SVG, and it gets its own icon in the scene tree. For a reusable kit (components, a camera rig, a dialogue box) this is usually all you need, no plugin required.
Godot’s own editor icons are 16 by 16 SVGs; yours will look at home at the same size.
QuizInteractive · disabled in the PDF
You want a ShakeCamera node teammates can add from the Create Node dialog, with its own icon. What is the simplest way?
You want a ShakeCamera node teammates can add from the Create Node dialog, with its own icon. What is the simplest way?
Godot211 / 219
Module 20 · Expert Godot
GDExtension: native code when you need it
GDExtension lets you write parts of a game in a compiled language, usually C++ with the official godot-cpp bindings (Rust has a well-known community binding too), and load them as a shared library without rebuilding Godot. Classes from an extension appear in the editor like built-in nodes, and GDScript calls them like any other class.
The .gdextension file tells Godot which library to load on each platform, and the function to start it with.
QuizInteractive · disabled in the PDF
Which is the best reason to move code from GDScript to GDExtension?
Which is the best reason to move code from GDScript to GDExtension?
Godot212 / 219
Module 20 · Expert Godot
C# in Godot
Godot has a separate download, the .NET build, that adds C# as a scripting language alongside GDScript. The engine’s API is the same, with C# naming: move_and_slide() becomes MoveAndSlide(), _physics_process becomes _PhysicsProcess. You can mix both languages in one project, though calls across them are clumsier than within one.
A Godot C# class must be partial: Godot’s source generators write the other half.
QuizInteractive · disabled in the PDF
Which of these are true of C# in Godot 4? Pick all that are.
Which of these are true of C# in Godot 4? Pick all that are.
Godot213 / 219
Module 20 · Expert Godot
High-level multiplayer: peers and a server
In Godot’s high-level multiplayer, each running copy of the game is a peer with a unique id. One peer is the server, always id 1, and the others are clients connected to it. You make a peer (usually ENetMultiplayerPeer, over UDP), start it as a server or a client, and hand it to multiplayer, the scene tree’s MultiplayerAPI. From then on, nodes at the same path in every peer’s tree can talk to each other.
Mark a function with @rpc and it can be called on other peers: shoot.rpc(direction) runs it on all the other peers, shoot.rpc_id(1, direction) on the server only. The annotation says who may call it: "authority" (the default: only the node’s authority) or "any_peer"; whether it also runs locally: "call_remote" (default) or "call_local"; and how it travels: "unreliable", "unreliable_ordered" or "reliable".
GDScript
1@rpc("any_peer", "reliable")2funcrequest_shoot(direction:Vector2)->void:3ifnot multiplayer.is_server():4return5var shooter := multiplayer.get_remote_sender_id()6ifnot_can_shoot(shooter):7return# never trust a client8_spawn_bullet(shooter, direction.normalized())
Clients ask; the server checks and decides. get_remote_sender_id() says which peer sent the call.
Find the bugInteractive · disabled in the PDF
Each player node is named after its peer id. Every player moves everyone else’s character and not their own. Which line is wrong?
GDScript
QuizInteractive · disabled in the PDF
A function is marked with a plain @rpc, and nodes have their default authority. A client calls heal.rpc_id(1, 50). What happens on the server?
A function is marked with a plain @rpc, and nodes have their default authority. A client calls heal.rpc_id(1, 50). What happens on the server?
Godot215 / 219
Module 20 · Expert Godot
MultiplayerSpawner and MultiplayerSynchronizer
Two nodes do most of the replication for you. A MultiplayerSpawner watches the node at its spawn_path: when the authority adds a child made from one of the scenes in its Auto Spawn List, the spawner creates it on every peer too, and removes it when it is freed. A MultiplayerSynchronizer, added inside a scene, sends the properties you tick in its Replication panel (position, health, an animation name) from the node’s authority to the other peers.
The server adds a player; the spawner makes it everywhere; each synchronizer keeps it up to date.
QuizInteractive · disabled in the PDF
Positions sync from the server to clients, but you want each client to move its own player and send its position to the others. What decides who sends?
Positions sync from the server to clients, but you want each client to move its own player and send its position to the others. What decides who sends?
Godot216 / 219
Module 20 · Expert Godot
Under the nodes: the servers
Nodes are a friendly layer. Underneath, Godot is built from servers: RenderingServer draws, PhysicsServer2D and PhysicsServer3D simulate, AudioServer mixes. A node like Sprite2D is a thin wrapper that asks the rendering server to draw something, identified by an RID (a resource id, a handle to an object inside a server). You can call the servers yourself, skipping the nodes, for thousands of identical things.
GDScript
1extendsNode2D23@exportvar texture:Texture2D45var _items:Array[RID]=[]67func_ready()->void:8var parent :=get_canvas_item()9for _i in2000:10var item :=RenderingServer.canvas_item_create()11RenderingServer.canvas_item_set_parent(item, parent)12var at :=Vector2(randf()*1000.0, randf()*600.0)13var rect :=Rect2(at, texture.get_size())14RenderingServer.canvas_item_add_texture_rect(15 item, rect, texture.get_rid())16 _items.append(item)1718func_exit_tree()->void:19for item in _items:20RenderingServer.free_rid(item)
Two thousand drawings with no nodes at all.
QuizInteractive · disabled in the PDF
What is the catch with drawing through RenderingServer directly?
What is the catch with drawing through RenderingServer directly?
Godot217 / 219
Module 20 · Expert Godot
Reading Godot’s source and contributing
Godot is open source under the MIT licence, written in C++, on GitHub at godotengine/godot. When the documentation leaves a question open (what exactly does move_and_slide do on a slope?), the source answers it. The layout is regular: core/ for the basics (variants, objects, maths), scene/ for the nodes, servers/ for the servers, modules/ for optional parts, GDScript itself among them in modules/gdscript/, and editor/ for the editor.
Terminal
1gitclonehttps://github.com/godotengine/godot.git2cdgodot3sconsplatform=linuxbsdtarget=editor4# the editor appears in bin/
Building the editor from source with SCons. Use platform=windows or platform=macos on those systems.
Bugs: report them in the main repository’s issues, with a minimal project that shows the problem.
Feature ideas: discussed first in godot-proposals, not as pull requests.
Documentation: the class reference lives in the engine’s doc/classes/ XML files; the manual is in the godot-docs repository.
Code: small, focused pull requests that follow the contributing guide are the ones that get merged.
QuizInteractive · disabled in the PDF
You want Godot to have a built-in node for 2D ropes. Where do you start?
You want Godot to have a built-in node for 2D ropes. Where do you start?
Godot218 / 219
Module 20 · Expert Godot
Mastery: putting it all together
These questions mix everything in the course, the way real decisions do.
QuizInteractive · disabled in the PDF
Barrels, crates and slimes can all be destroyed, each drops loot from its own list, and the HUD counts how many things you broke. Which design fits Godot best?
Barrels, crates and slimes can all be destroyed, each drops loot from its own list, and the HUD counts how many things you broke. Which design fits Godot best?
QuizInteractive · disabled in the PDF
Your game runs at 60 FPS in the editor but stutters in the web build when many enemies are on screen. What do you do first?
Your game runs at 60 FPS in the editor but stutters in the web build when many enemies are on screen. What do you do first?
QuizInteractive · disabled in the PDF
A @tool script on each level reads its LevelData resource and, in the editor, sets data.enemy_count from the enemies in the scene. Two levels share one .tres. What goes wrong?
A @tool script on each level reads its LevelData resource and, in the editor, sets data.enemy_count from the enemies in the scene. Two levels share one .tres. What goes wrong?
Put in orderInteractive · disabled in the PDF
A client joins a game with a spawner. Put what happens in order.
1.The client calls create_client and sets multiplayer.multiplayer_peer
2.The server instantiates a player named after that id and adds it under the spawner’s path
3.The server’s peer_connected signal fires with the client’s id
4.The player’s _enter_tree sets its multiplayer authority to that id
5.The spawner creates the same player on every peer
Godot219 / 219
Module 20 · Expert Godot
Recap, and where to go next
@tool runs a script in the editor; guard gameplay with Engine.is_editor_hint().
Editor plugins live in addons/ with a plugin.cfg; _exit_tree removes all _enter_tree added.
class_name and @icon make a custom node type, no plugin needed.
GDExtension is for measured hot spots and native libraries; C# needs the .NET build and can’t export to the web yet.
Multiplayer: peers, a server with id 1, @rpc with modes, authority, and the spawner and synchronizer nodes.
Never trust a client: the server checks every request.
Servers and RIDs sit under the nodes, for when nodes are the bottleneck.
Godot’s source is readable, and proposals, docs and fixes are all welcome contributions.
FlashcardsInteractive · disabled in the PDF
@tool
Makes a script run in the editor as well as in the game.
Engine.is_editor_hint()
True when the code is running inside the editor.
plugin.cfg
Describes an editor plugin: its name, version and script.
GDExtension
Native libraries, usually C++ with godot-cpp, loaded without rebuilding Godot.
Server’s peer id
Always 1.
Default @rpc mode
"authority": only the node’s authority may call it.
MultiplayerSynchronizer
Sends chosen properties from a node’s authority to other peers.
RID
A handle to an object inside a server; free it with free_rid when you made it.
Build these on your ownInteractive · disabled in the PDF
Answer key
1. What a game engine is, and meeting Godot · Each frame has a time budget · QuizA game runs at 30 frames per second. Roughly how long does each frame have?C. 33.3 msOne second is 1000 ms, shared between 30 frames: 1000 ÷ 30 ≈ 33.3 ms each. The tempting 16.7 ms is the budget at 60 fps; doubling the frame rate halves the time each frame gets.
1. What a game engine is, and meeting Godot · An engine does the work every game needs · QuizWhich of these would you normally write yourself, rather than get from the engine?C. The rule that touching a coin adds 10 to the scoreDrawing, collision detection and input are the same in every game, so the engine provides them. The rule that a coin is worth 10 points is your game’s own design: only you can write it. That split, the engine for the general and you for the particular, is the whole point of an engine.
1. What a game engine is, and meeting Godot · Godot is free, open source and yours to keep · QuizYou sell your Godot game for 10 dollars and it sells well. What do you owe the Godot project?C. Nothing, apart from including its licence noticeThe MIT licence is permissive: no royalties, no fees, and your own code stays yours and private. Some licences, such as the GPL, would make you share your source, and some commercial engines take a share above a revenue limit; MIT does neither.
1. What a game engine is, and meeting Godot · You write games in GDScript, or in C# · QuizYou are new to programming and want to make a small 2D game that runs in a web browser. Which is the better first choice?B. GDScript, in the standard downloadGDScript is made for Godot, needs nothing extra, is what the docs teach, and exports to the web. C# is a fine language, but its speed rarely matters for a small game, and its web support in Godot 4 has trailed GDScript’s. C++ is possible through GDExtension (module 20), but nobody needs it to start.
1. What a game engine is, and meeting Godot · Installing Godot takes one download · Put in orderPut the steps for getting Godot running in order.1. Open the download page on godotengine.org 2. Choose the latest stable Godot 4 for your operating system (the standard edition, not .NET) 3. Unzip the download into a folder you will find again 4. Open the Godot program in that folder 5. See the Project Manager, which lists your projectsPick the stable release rather than a preview, and the standard edition unless you plan to write C#. There is nothing to install, so unzipping is the last step before running it. The first window you see is always the Project Manager, not the editor.
1. What a game engine is, and meeting Godot · The Project Manager makes and opens projects · QuizYour first game is a 2D puzzle you want people to play in their browser. Which renderer fits?C. CompatibilityBrowsers draw through WebGL 2, which only the Compatibility renderer uses. Forward+ is the most capable, but it needs desktop graphics features a browser does not offer. You can change the renderer later in the project settings, so this is not a choice you are stuck with.
1. What a game engine is, and meeting Godot · Your first scene says hello · QuizThe first time you press F5, Godot asks you to choose a main scene. Why?A. F5 runs the whole game, which starts from one scene, and none has been chosen yetA game can have many scenes (a menu, levels, a game-over screen), so Godot needs to know which one opens first. Your choice is saved in the project settings. F6 is different: it runs only the scene you are editing, which is useful for testing one piece.
2. The editor, scenes and nodes · Workspaces switch what the viewport shows · QuizYour script prints “Game started”. Where do you see it?B. In the Output panel at the bottomprint writes to the Output panel while the game runs from the editor. The Inspector shows properties, not messages. Errors land in Output too, and in more detail in the Debugger tab, so these two panels are where to look first when something goes wrong.
2. The editor, scenes and nodes · A node is one building block with one job · QuizUsing the tree above, which of these has a position it inherits from Node2D?B. Camera2DCamera2D sits under Node2D, so it inherits everything a Node2D has: position, rotation and scale. Label is a Control, which places itself differently (with a rectangle, module 10). Timer is a plain Node: it has no place in space at all, because it doesn’t need one.
2. The editor, scenes and nodes · Nodes form a tree, and children follow their parent · QuizA Sprite2D is a child of Player. The sprite’s position is (0, -20). Player moves from (100, 300) to (150, 300). Where is the sprite on screen now?C. (150, 280)A child’s position is relative to its parent: (0, -20) means “20 pixels above the player”, wherever the player is. The player is at (150, 300), so the sprite is at (150, 280). Its own position property still reads (0, -20); its global position is (150, 280). Remember y points down in 2D, so -20 is up.
2. The editor, scenes and nodes · The Inspector edits a node’s properties · Put in orderPut the steps for building a simple player scene in order.1. Make a new scene (Scene, then New Scene) 2. Choose Other Node and pick CharacterBody2D as the root, renamed Player 3. Add a Sprite2D child and drag an image into its Texture in the Inspector 4. Add a CollisionShape2D child and give it a new RectangleShape2D in Shape 5. Save the scene as player.tscnThe root comes first because every other node hangs under it. The sprite and the shape are children so they move with the body; their order doesn’t matter much, but both need their resource set in the Inspector (an empty shape does nothing and shows a warning). Saving last turns the tree into a reusable scene.
2. The editor, scenes and nodes · Run the whole game, or just this scene · QuizYour main scene is the title menu. You are editing level_3.tscn and want to try it without clicking through the menu. What do you press?B. F6F6 runs the current scene directly. F5 always starts from the main scene, so you would land on the menu. F8 stops the game. A scene that only works when started from somewhere else is a sign it depends on things it shouldn’t.
2. The editor, scenes and nodes · res:// is the project, user:// is the player’s · QuizWhere should your game write the player’s save file?B. user://save.datuser:// is meant for exactly this, and it exists on every platform, phones and the web included. Writing to res:// may seem to work in the editor, where it is just a folder, but fails once exported, because the project is packed and read-only. A fixed path like C:/ only exists on some Windows computers.
3. GDScript from zero · Variables remember values · QuizWhat does print(7 / 2) show?B. 37 and 2 are both ints, so the division is between whole numbers and the fraction is dropped: 3. Godot even warns you about it (“integer division”). For 3.5, make one side a float: 7 / 2.0 or 7.0 / 2. This catches out almost everyone once, usually when working out a percentage.
3. GDScript from zero · Type hints let Godot catch mistakes · QuizWhat type does var gold := 50 give gold?B. int:= takes the type of the starting value, and 50, with no decimal point, is an int. If gold could ever be fractional you would write 50.0. Plain var gold = 50 (no colon) is the one with no fixed type.
3. GDScript from zero · Operators calculate and compare · Fill the blanksAdd 25 to the score, then print Score: 125 using a format string.1: +=, 2: %d, 3: %+= adds to the variable in place, the short form of score = score + 25. In a format string %d stands for a whole number (%s for any value), and the % between the string and the value fills it in. Writing "Score: " + score fails: + can’t join a String and an int without str().
3. GDScript from zero · if, elif and else make decisions · Find the bugGodot won’t run this script. Which line is wrong?Line 4: if coins >= 10:One =assigns a value; two, ==, compares. A condition must compare, so if coins = 10: is an error. coins == 10 would run, but >= 10 is better: with 11 coins the door should still open. Asking “is it at least?” instead of “is it exactly?” avoids a whole family of bugs.
3. GDScript from zero · match picks one branch from many values · QuizWith the function above, what does react("potion") print?B. Healed"potion" is one of the two values in the second pattern, so that branch runs, and match stops there. GDScript’s match never “falls through” into the next branch, unlike switch in some languages; _ only runs when nothing above it matched.
3. GDScript from zero · for and while repeat work · QuizWhat does for i in range(2, 10, 3): print(i) print?A. 2, 5, 8It starts at 2 and adds 3 each time: 2, 5, 8. The next would be 11, but the end, 10, is never reached or included. The end of a range is always left out, which is why range(3) gives 0, 1, 2: three numbers, starting from 0.
3. GDScript from zero · Arrays hold lists; dictionaries look things up · Fill the blanksAdd "key" to the inventory, then print how many keys the shop has in stock (0 if it has no entry).1: append, 2: get, 3: 0append adds to the end of an array. get(key, default) reads a dictionary safely: the value if the key is there, the default if not. Here it prints 2. Reading stock["key"] would work too, but crashes the day the key goes missing.
3. GDScript from zero · Functions name a job and can give back an answer · Fill the blanksFinish is_rich, which takes a number of coins and answers whether it is 100 or more.1: func, 2: int, 3: bool, 4: returnA function starts with func. The parameter is a whole number, int, and the answer is true or false, so it returns bool. A comparison like coins >= 100 already is a bool, so it can be returned directly, with no if needed.
4. Functions, classes and types in GDScript · Every script is a class · QuizTwo player nodes in a level both use player.gd. One calls collect(5). What happens to the other one’s coins?B. Nothing: each node has its own copy of coinsThe script is the blueprint; each node built from it is a separate instance with its own properties. That is why you can have ten enemies from one script, each with its own health. Data shared by all instances needs something else, such as a static var or an autoload (module 13).
4. Functions, classes and types in GDScript · Inheritance and super reuse a parent’s code · QuizEnemy._ready() starts the enemy’s patrol. Boss extends Enemy and has this _ready. Does the boss patrol?B. No: this _ready replaces the parent’s; it needs super() to run itIn Godot 4, overriding _readyreplaces the parent’s: the engine calls only the child’s. Add super() as the first line to run the parent’s setup, then the boss’s own. Godot 3 called both automatically, so old tutorials leave super() out, and code ported from them loses behaviour silently.
4. Functions, classes and types in GDScript · Enums name a fixed set of choices · Fill the blanksMake an enum of the four directions, a variable of that type facing up, and turn it to the left.1: enum, 2: Direction, 3: Direction.LEFTenum Name { … } declares it, and Name is then a type. The values are written with the enum’s name in front, Direction.LEFT, so they can’t be confused with another enum’s LEFT. Using a plain int and remembering that 2 means left works, until someone forgets.
4. Functions, classes and types in GDScript · @export shows a property in the Inspector; @onready waits for the tree · Find the bugGodot flags an error in this script, and it can’t work when the scene starts. Which line is the cause?Line 3: @onready var label: Label = $ScoreLabelA plain property gets its value when the script’s object is created, before the node has its children or is in the scene tree, so $ScoreLabel would find nothing. Godot 4 knows this and reports it as an error: a default value that uses $ won’t find nodes before _ready() is called. @onready runs the line just before _ready, when the children are there. Any property that uses $ needs @onready.
4. Functions, classes and types in GDScript · Static typing finds bugs before you play · Quizbody is a StaticBody2D (a wall). What is body as Player?B. nullas converts only when the value really is that type; otherwise it gives null. Using the result without checking would then fail on null. That is why the code above checks body is Player first: is answers true or false without converting anything.
4. Functions, classes and types in GDScript · Godot calls your functions at set moments · Put in orderA node is created and added to the game. Put its callbacks in the order Godot calls them.1. _init() 2. _enter_tree() 3. _ready() 4. The first _process(delta)Creating the object runs _init. Adding it to the tree runs _enter_tree, and once it and its children are all in, _ready. Only then does it take part in frames. Code in _init can’t use $Child, because there is no tree yet: that is what _ready is for.
4. Functions, classes and types in GDScript · Godot calls your functions at set moments · QuizA Level node has a child Player. Both have _ready. Which runs first?B. Player._ready, because children are ready first_ready means “I and everything under me are ready”, so Godot readies the children before the parent. When Level._ready runs, it can safely use its Player. _enter_tree goes the other way: parent first, then children.
4. Functions, classes and types in GDScript · null, and nodes that are gone · Find the bugThe turret crashes the moment its target is destroyed. Which line needs to change?Line 6: if is_instance_valid(target): look_at(target.global_position)After the target is freed, target still refers to the deleted object, and reading its global_position stops the game. Guard the line with is_instance_valid(target), which is false both when it was never set and when it has been freed. (In Godot 4, target != null is also false for a freed object, but is_instance_valid says what you mean, and it is the check the docs use.) Setting target = null when it dies works too, but needs someone to remember to do it.
4. Functions, classes and types in GDScript · Functions are values: Callables and lambdas · QuizWhat does print([1, 2, 3].map(func(n): return n * n)) print?B. [1, 4, 9]map calls the lambda once per item and collects what it returns into a new array: 1×1, 2×2, 3×3. The original array is unchanged. Adding them up to 14 would be reduce; doubling would be n * 2.
5. Moving in 2D: input and the game loop · Ask Input whether an action is held, pressed or released · QuizThe player holds the jump button for half a second, and the character jumps about 30 times. Which call was probably used?A. is_action_pressedis_action_pressed is true on every frame the button is held: at 60 fps, half a second is 30 frames, so 30 jumps. A jump happens once per press, so it wants is_action_just_pressed. Held actions (running, a machine gun) want is_action_pressed.
5. Moving in 2D: input and the game loop · get_axis and get_vector turn input into a direction · QuizWhy use get_vector rather than reading two get_axis values into a Vector2 yourself?B. It keeps the length at most 1, so moving diagonally isn’t faster, and applies a dead zoneRight plus down made by hand is (1, 1), whose length is √2 ≈ 1.41, so diagonal movement would be 41% faster. get_vector limits the length to 1 and handles the stick’s dead zone. It does not force length 1: no input stays (0, 0), and a half-tilted stick stays half, which is what you want for walking slowly.
5. Moving in 2D: input and the game loop · Positions and directions are Vector2s · Fill the blanksMake the enemy step 50 pixels towards the player, and print how far apart they are.1: direction_to, 2: +=, 3: distance_todirection_to gives a direction of length 1, so multiplying by 50 makes a step of exactly 50 pixels, whatever the distance. Subtracting positions, player.position - position, points the right way too, but its length is the whole distance: you would jump straight onto the player.
5. Moving in 2D: input and the game loop · Multiply by delta to move per second, not per frame · QuizA ship moves with position.x += 200.0 * delta. The game drops from 60 to 30 fps. How far does it move in one second?B. 200 pixelsAt 30 fps delta is about 0.033, twice as big as at 60 fps, so each step is twice as long and there are half as many: 30 × 200 × 0.033 ≈ 200 pixels. That is the point of delta: speeds in pixels per second, the same everywhere.
5. Moving in 2D: input and the game loop · Velocity and acceleration make movement feel weighty · Fill the blanksAccelerate towards the wanted velocity, then move by the velocity, both per second.1: move_toward, 2: delta, 3: velocitymove_toward(target, amount) moves the vector towards target by at most amount. Acceleration is per second, so the amount for this frame is acceleration * delta. Then the position moves by the velocity, also times delta. Forgetting either delta makes the feel change with the frame rate.
5. Moving in 2D: input and the game loop · rotation is in radians, and transform.x is forward · QuizA ship drawn facing right has rotation = PI / 2. Which way is transform.x pointing on screen?B. DownA quarter turn from facing right, turning clockwise as positive angles do on a screen whose y points down, faces down: transform.x is (0, 1). In a maths textbook, with y up, the same angle points up, which is why this one surprises people.
5. Moving in 2D: input and the game loop · Wrapping round the edges of the screen · Find the bugThe ship wraps correctly left and right, but going down it reappears at the top far too early. Which line is wrong?Line 4: position.y = wrapf(position.y, 0.0, screen.y)The vertical position must wrap at the screen’s height, screen.y, not its width. On a 1280 × 720 window, wrapping y at 1280 means the ship flies 560 pixels below the bottom before it comes back. Copy-pasted lines that differ by one letter are where this kind of bug lives.
6. Physics bodies and collisions · A body collides with the shapes you give it · QuizYou add a StaticBody2D floor with a Sprite2D, but the player falls straight through it. What is most likely missing?B. A CollisionShape2D child with a shapeThe sprite is only a picture; the physics engine knows nothing about it. Without a shape, the body has nothing to collide with, and Godot shows a yellow warning triangle beside it in the Scene dock. Static bodies need no script: they simply exist.
6. Physics bodies and collisions · CharacterBody2D moves with velocity and move_and_slide · Find the bugThis top-down character crawls at a tiny fraction of its speed. Which line is wrong?Line 8: velocity = direction * speedvelocity is pixels per second, and move_and_slide already multiplies by the step’s time. Multiplying by delta too makes it 60 times slower. The rule from module 5 still holds, but here Godot applies it for you. (Godot 3 passed velocity as an argument, move_and_slide(velocity); in Godot 4 it takes none.)
6. Physics bodies and collisions · The jump’s height comes from its speed and gravity · QuizYou double jump_velocity and keep gravity the same. What happens to the height of the jump?B. It becomes four times as highHeight is : the speed is squared, so twice the speed gives four times the height (and twice the time in the air). To make a jump twice as high, multiply the speed by . Many platformers also raise gravity while falling, for a snappier landing.
6. Physics bodies and collisions · After moving, ask what the body touched · QuizWhere in _physics_process does is_on_floor() give the answer from this step’s movement?B. After move_and_slide()is_on_floor() is set by move_and_slide(). Before the call it still reports the previous step, which is usually fine for deciding whether to jump (the platformer above does that). Code that reacts to landing, like playing a sound, goes after the call.
6. Physics bodies and collisions · A RigidBody2D is moved by the physics engine · QuizYou set a RigidBody2D’s position every frame to follow the mouse, and it jitters and pushes through things. Why?B. You and the physics engine are both moving it, and the engine can’t solve collisions for a teleportA rigid body belongs to the physics engine. Setting its position teleports it each frame, so the engine finds it overlapping things and shoves back. Push it with impulses and forces instead, or, if your code should be in charge, use a CharacterBody2D or AnimatableBody2D. For direct control of a rigid body’s state, override _integrate_forces(state).
6. Physics bodies and collisions · An Area2D notices what enters it · Fill the blanksSpikes hurt any player who touches them. Connect the right signal and check the body’s type.1: body_entered, 2: isThe player is a CharacterBody2D, a body, so the signal is body_entered (area_entered is for other areas, such as a sword’s hitbox). is checks the type before calling a method only a Player has; without it, an enemy walking onto the spikes would cause an error.
6. Physics bodies and collisions · Layers say what you are; masks say what you look for · QuizWith the table above, an enemy walks over a coin. Does the coin’s body_entered fire?B. No: the coin’s mask only has layer 1, and the enemy is on layer 3An area notices the bodies whose layer is in the area’s mask. The coin looks only for layer 1, the player, so enemies pass over coins without picking them up. The enemy’s own mask doesn’t matter for this. In code, set_collision_mask_value(3, true) turns on one numbered layer of the mask.
6. Physics bodies and collisions · A raycast asks what lies along a line · Fill the blanksThe guard can see the player if a ray from its eyes to the player hits the player first, not a wall.1: target_position, 2: is_colliding, 3: get_collidertarget_position is relative to the ray, so the player’s global position is converted with to_local. A ray normally updates once per physics step; force_raycast_update() checks right now, after moving it. If a wall is in the way, the wall is what it hits, so get_collider() isn’t the player. The ray’s mask must include both walls and the player for this to work.
7. Signals: nodes talking to each other · Nodes come with signals for their own events · QuizYou want an explosion to delete itself once its one-shot animation, played by an AnimationPlayer, is over. Which signal fits?B. AnimationPlayer.animation_finishedanimation_finished fires exactly when the animation ends, whatever its length. A Timer set to the same length works until someone changes the animation and forgets the timer. tree_exited fires after the node is removed, too late to be the cause of it.
7. Signals: nodes talking to each other · Connecting a signal in the editor · Put in orderPut the steps for connecting a button’s pressed signal in the editor in order.1. Select the StartButton node in the Scene dock 2. Open the Signals dock (before Godot 4.6, the Node dock’s Signals tab) 3. Double-click pressed() 4. Pick the node whose script should receive it (the menu’s root) and click Connect 5. Write the game’s logic in the new _on_start_button_pressed() functionYou start from the node that emits, because signals are listed on their emitter. The dialog then asks who receives: that node must have a script, which gets the new handler. Godot names it _on_ plus the node and the signal, a convention worth keeping when you connect in code too.
7. Signals: nodes talking to each other · Connecting in code: signal.connect(handler) · Fill the blanksConnect the start button’s pressed signal to _on_start_pressed in code.1: pressed, 2: connect, 3: _on_start_pressedThe signal, then .connect, then the handler as a Callable: its name with no brackets. Writing _on_start_pressed() with brackets would call it right away and pass its result, nothing, to connect, which is an error. Connect in _ready, so it happens once.
7. Signals: nodes talking to each other · A handler takes the signal’s arguments · Find the bugWhen the player walks into this area, Godot reports an error instead of printing. Which line is wrong?Line 6: func _on_body_entered(_body: Node2D) -> void:body_entered passes one argument, the body, so the handler must take one, even if it doesn’t use it. A name starting with _ tells Godot it is unused on purpose, which silences the warning. The error only comes when the signal is emitted, which is why it is easy to miss.
7. Signals: nodes talking to each other · Your own signals: declare, then emit · Fill the blanksDeclare a coin_collected signal that carries the new total, and emit it when a coin is collected.1: signal, 2: emit, 3: coinssignal declares it, with its arguments typed like a function’s parameters. .emit() sends it, passing the values in the same order: here the new total. Emitting the new value saves every listener from reaching back into the player to read it.
7. Signals: nodes talking to each other · A Timer counts down and emits timeout · QuizA Timer has wait_time 2, one_shot off and autostart on. How many times does it emit timeout in the first 7 seconds?B. 3It starts with the scene and, not being one-shot, restarts after every timeout: at 2, 4 and 6 seconds. The next would be at 8. The first timeout comes after one full wait, not at 0, which is worth remembering for a spawner that should spawn at once.
7. Signals: nodes talking to each other · await pauses a function until a signal comes · QuizWhat does this print, in order?B. A, C, Bwait_then_print pauses at its await and hands control back, so _ready prints C straight away; one second later the timer fires and B is printed. To make _ready wait for it, write await wait_then_print(). Forgetting that await makes a function return early is the usual surprise.
7. Signals: nodes talking to each other · Call down, signal up · QuizThe player’s script does get_node("/root/Level/HUD").show_coins(coins). What is the main problem?B. The player breaks if the HUD is renamed, moved, or absent, such as when the player scene runs aloneThe player now depends on one exact layout of one level. Rename the HUD, put the player in another level, or press F6 on player.tscn, and it fails. Emitting coin_collected and letting the level connect it removes the dependency: the player only says what happened.
8. Instancing, groups and scene composition · preload, load, or an exported slot · QuizYour code works out the next level’s file name from a number. Which do you use to get it?B. load, because the path is only known while runningpreload happens when the script is loaded, before any of your code runs, so its path must be written out as fixed text; Godot refuses a variable there. load runs at that line, with whatever path you built. Godot caches loaded resources, so loading the same path twice doesn’t read the disk twice.
8. Instancing, groups and scene composition · Spawning a bullet at the gun’s muzzle · QuizWhat goes wrong if shoot() calls add_child(bullet), making each bullet a child of the ship?B. The bullets move and turn with the ship, as children follow their parentA child’s position is relative to its parent (module 2), so when the ship turns, every bullet in flight swings round with it, like on a stick. Bullets, effects and dropped items belong to the world, so they go into the level, or a container node in it such as Projectiles.
8. Instancing, groups and scene composition · queue_free deletes a node safely · QuizAn enemy has a Sprite2D, a CollisionShape2D and a Health child. You call queue_free() on the enemy. What happens to the children?B. They are freed with itFreeing a node frees its whole subtree, which is why one queue_free() cleans up a complex enemy. Something you want to keep, like a dropped key, should be spawned into the level as its own node, not left as a child of the enemy.
8. Instancing, groups and scene composition · Inherited scenes share a base and change the details · QuizIn bat.tscn, inherited from enemy.tscn, you want to remove the Shadow node the base has. What happens?B. You can’t delete inherited nodes; hide it, or move it out of the baseAn inherited scene can add and change, but not remove what it inherits. You can switch the node off (visible = false, or disable it), but if half the enemies don’t want a shadow, it doesn’t belong in the base. That limit is one reason to prefer the next idea, composition.
8. Instancing, groups and scene composition · Groups tag nodes so you can find them all · Find the bugThe “freeze all” power-up does nothing, with no error. Enemies add themselves to "enemies". Which line is wrong?Line 7: get_tree().call_group("enemies", "freeze")There is no group called "enemy", so the call reaches nobody, and that isn’t an error: an empty group is a valid answer. Group names are plain strings, so typos fail silently. Keep the names in one place, such as a constant (const ENEMIES := &"enemies"), and use it everywhere.
8. Instancing, groups and scene composition · Finding nodes: paths and unique names · QuizA script uses $Panel/ScoreLabel. You put ScoreLabel inside a new MarginContainer in Panel. What happens, and what avoids it?B. The path no longer finds it; marking it as a unique name and using %ScoreLabel would keep workingThe path now ends at Panel/MarginContainer/ScoreLabel, so $Panel/ScoreLabel fails when the script runs. Godot doesn’t rewrite paths in code. A unique name only needs the name, not the route, as long as it stays in the same scene. Use it for the handful of nodes a script reaches into.
8. Instancing, groups and scene composition · An enemy spawner, from the parts of this module · Put in orderPut the life of a spawned enemy in order.1. The scene is loaded once as a PackedScene 2. instantiate() builds a new enemy 3. add_child() puts it in the tree, and its _ready runs 4. It is placed at a spawn point with global_position 5. It is defeated and calls queue_free()Load once, instantiate many times. The node joins the game, and runs _ready, only when added to the tree; setting its global position right after puts it in the right place before the frame is drawn. Its life ends with queue_free, which also frees its children.
9. Sprites, animation and tweens · Pixel art needs the nearest filter · QuizYour 16 × 16 pixel-art hero, scaled up 4 times, looks soft and blurry. What fixes it?B. Setting the default texture filter to NearestThe blur comes from linear filtering blending pixels as the image is enlarged. Nearest keeps each pixel a crisp square. Redrawing at 64 × 64 would also look sharp, but it is four times the work and no longer pixel art. For pixel-perfect movement, also look at the 2D snapping settings in Rendering, 2D.
9. Sprites, animation and tweens · A sprite sheet holds frames in a grid · QuizA sheet has hframes = 8. Which frame is in row 2, column 3, both counted from 0?B. 19Frames are numbered row by row: each full row is 8 frames, so row 2 starts at 2 × 8 = 16, and column 3 is 16 + 3 = 19. Getting 26 means you swapped them (3 × 8 + 2). The rule is: frame = row × hframes + column.
9. Sprites, animation and tweens · AnimatedSprite2D plays named animations · Fill the blanksPlay the "hurt" animation once, wait for it to end, then go back to "idle".1: play, 2: await, 3: animation_finishedplay starts the animation and await pauses this function until the sprite emits animation_finished. That signal only fires for animations that don’t loop, so turn looping off for hurt in the SpriteFrames panel, or the function waits forever.
9. Sprites, animation and tweens · Flip the sprite to face where you move · Find the bugThe character faces left while running left, but snaps back to face right the moment it stops. Which line causes it?Line 2: if velocity.x != 0.0: sprite.flip_h = velocity.x < 0.0When the character stops, velocity.x is 0, and 0 < 0 is false, so flip_h turns off and it faces right. Update the facing only while there is horizontal movement; when standing still, leave it as it was. The fix is a one-line if, which GDScript allows for a single statement.
9. Sprites, animation and tweens · AnimationPlayer animates any property over time · QuizA sword’s hitbox should only hurt during the 0.1 seconds the blade swings. What is the neatest way?B. A property track on the hitbox’s CollisionShape2Ddisabled, keyed off, on, off in the attack animationKeying the hitbox inside the same animation keeps it exactly in step with the picture: slow the animation down or change its timing and the hitbox follows. A separate timer has to be kept in step by hand, and drifts the first time someone edits the animation.
9. Sprites, animation and tweens · AnimationTree manages animations as a state machine · QuizWith the state machine above, the character is in idle and the code calls playback.travel("land"). What happens?B. It goes through jump to reach land, the only way theretravel follows the transitions, finding the shortest path through them, so it plays jump on the way to land. That is the point of a state machine: the graph forbids nonsense like landing without jumping. To skip there directly, there is playback.start("land").
9. Sprites, animation and tweens · A tween animates a value from code · Find the bugGodot reports that the tweened property doesn’t exist, and the sprite never fades. Which line is wrong?Line 3: tween.tween_property($Sprite2D, "modulate:a", 0.0, 0.5)Property paths in Godot use a colon to reach a part of a property: "modulate:a", "position:x". With a dot, Godot looks for a property literally called modulate.a, finds none, and the tween does nothing. Plain properties need no colon: "modulate".
9. Sprites, animation and tweens · Easing decides how a motion speeds up and slows down · QuizA pause menu slides in from the side. Which ease makes it feel responsive, arriving quickly and settling gently?B. EASE_OUTEASE_OUT moves most at the start, so the menu reacts at once, and slows into place. EASE_IN starts slowly, which reads as lag on something the player asked for; it suits things leaving, like a panel sliding away. Linear motion looks mechanical. Most interface motion is ease-out.
10. User interfaces with Control nodes · Control nodes are rectangles that lay themselves out · QuizWhich of these should be built from Control nodes?A. A health bar in the corner of the screen; D. A health bar floating above an enemy’s headAnything that is interface, a bar, a label, a button, is a Control, wherever it is drawn. A bar over an enemy is still a ProgressBar, just a child of the enemy so it follows it. The enemy and the crate are game objects, Node2Ds with bodies.
10. User interfaces with Control nodes · Anchors pin edges to the parent; offsets add pixels · QuizA button has both left and right anchors at 0.5 and offsets of -100 and 100. The window gets wider. What happens to the button?A. It stays 200 pixels wide, centred horizontallyBoth edges are pinned to the middle of the parent (0.5) and pushed 100 pixels either side, so the button is 200 wide and stays centred as the middle moves. For a button that stretches, set the anchors apart, such as 0.1 and 0.9: then its width is a share of the parent’s.
10. User interfaces with Control nodes · Containers arrange their children for you · QuizYou try to drag a button inside a VBoxContainer to a new spot, and it snaps back. Why?B. The container sets its children’s positions and sizes, overriding yoursInside a container, the container is in charge of layout: position, and size along its direction. You change the result through the container (its separation, its order of children) and through each child’s size flags and minimum size, which is the next slide. To place something freely, keep it out of containers.
10. User interfaces with Control nodes · Size flags and minimum sizes guide the container · Fill the blanksIn a row, make the name label take all the spare width, and the button at least 120 pixels wide.1: SIZE_EXPAND_FILL, 2: custom_minimum_sizeSIZE_EXPAND_FILL is Expand and Fill together: ask for spare space and stretch into it. Only Expand would reserve the space but leave the label at its own width inside it. A minimum size of 0 for the height lets the container decide that. Both are easier to set in the Inspector, under Layout, but read the same way in code.
10. User interfaces with Control nodes · A HUD lives on a CanvasLayer · QuizYour score label drifts off screen as the camera follows the player. What fixes it?B. Put the label under a CanvasLayerA CanvasLayer is drawn independently of the camera, which is exactly what screen-fixed UI needs. Making it a child of the camera, or moving it every frame, can be made to work, but breaks with zoom, smoothing and screen shake. A CanvasLayer handles all of them.
10. User interfaces with Control nodes · Focus makes menus work with keys and gamepads · Find the bugThe menu works with the mouse, but a gamepad can’t select anything. There is no error, only a warning. Which line is wrong?Line 5: %PlayButton.grab_focus()Without brackets, grab_focus is just the method as a value, a Callable, which is then thrown away: nothing is called. Godot warns about a “standalone expression” that does nothing. With () the button takes focus, and the gamepad has somewhere to start. A menu that only works with a mouse is a common bug in games on consoles and handhelds.
10. User interfaces with Control nodes · Pausing the tree, and what keeps running · Find the bugPressing pause freezes the game and shows the menu, but pressing it again does nothing. Which line is wrong?Line 4: process_mode = Node.PROCESS_MODE_ALWAYSA pausable node stops receiving input once the game is paused, so the menu never hears the second press. PROCESS_MODE_ALWAYS keeps it listening while paused and while playing, which a menu that both opens and closes needs. (WHEN_PAUSED would miss the first press, because the game isn’t paused yet.) Set it in the Inspector, under Process, to see it at a glance.
10. User interfaces with Control nodes · Themes style every control at once · QuizYou add a full-screen ColorRect over the menu for a fade effect. Now no button can be clicked. What is the cause?B. The ColorRect catches the clicks first; its mouse_filter should be IgnoreControls on top get mouse events first, and by default most controls Stop them, so the clicks never reach the buttons below. Set the overlay’s mouse_filter to Ignore so clicks pass through. Pass handles the event and lets parents see it too. Labels default to Ignore, which is why text over a button doesn’t block it.
11. Tile maps, cameras and levels · Making a TileSet from a tile sheet · Put in orderPut the steps for setting up tiles in order.1. Add a TileMapLayer node to the level scene 2. Create a new TileSet in its Tile Set property 3. Set the TileSet’s tile size to match the sheet 4. Drag the tile sheet into the TileSet panel and let Godot cut it 5. Switch to the TileMap panel and paint cells in the viewportThe node comes first because the TileSet lives in one of its properties. The tile size has to be right before the sheet is cut, or every tile is sliced in the wrong place. Painting comes last, once there are tiles to paint with. The TileSet panel edits the palette; the TileMap panel paints with it.
11. Tile maps, cameras and levels · Painting cells, in layers · QuizTiles are 16 × 16 pixels and the layer sits at the origin. What does ground.local_to_map(Vector2(40, 20)) return?B. Vector2i(2, 1)The cell is the position divided by the tile size, rounded down: 40 / 16 = 2.5 → 2, and 20 / 16 = 1.25 → 1. Cells are whole numbers, a Vector2i, never fractions. local_to_map expects a position in the layer’s own space, which is why the code above calls to_local() first: if the layer were moved or scaled, a global position would land on the wrong cell.
11. Tile maps, cameras and levels · Terrains pick the right edge tile for you · QuizWhat do you mark on each tile so that terrain painting can choose tiles?B. Which of its sides and corners belong to the terrain (its peering bits)Terrain painting compares each cell’s neighbours with each tile’s peering bits: “my top side is grass, my bottom is dirt”. The tile whose bits match the neighbours wins. Collisions and custom data are separate per-tile properties that terrains never look at.
11. Tile maps, cameras and levels · Tiles that collide, and tiles that carry data · Fill the blanksRead the damage custom data of the cell at pos (a global position).1: local_to_map, 2: get_cell_tile_data, 3: null, 4: get_custom_dataPosition to cell with local_to_map, cell to its TileData with get_cell_tile_data, then the value with get_custom_data, by the custom data layer’s name. The null check matters: an empty cell has no tile data, and calling a method on null stops the game with an error.
11. Tile maps, cameras and levels · A camera that follows the player · QuizYou want the camera closer, so things look bigger. Which zoom do you set in Godot 4?B. Vector2(2, 2)In Godot 4, zoom is magnification: 2 means twice as big, so you see half as much of the world. Godot 3 had it the other way round (2 meant zoomed out), so old tutorials will mislead you here. Negative zoom flips the view and is never what you want.
11. Tile maps, cameras and levels · Keep the camera inside the level · Find the bugThe camera is meant to follow the player, but the screen stays still while the player walks off it. Which line is wrong?Line 3: Camera2D (a child of Player)A node moves with its parent. Here the camera is the player’s sibling, a child of the level, so it stays where the level put it. Move it under Player and it follows with no code. (The other way, a script setting global_position = player.global_position every frame, works too, and helps when the camera must stop following, say during a cutscene.)
11. Tile maps, cameras and levels · Parallax: far things move slower · QuizThe camera moves 100 pixels to the right. How far does a Parallax2D with scroll_scale = Vector2(0.25, 1) scroll on screen?A. 25 pixels, so it looks farther away than the levelScroll scale multiplies the camera’s movement: 100 × 0.25 = 25. Slower means farther away, which is the whole effect. Above 1 a layer moves faster than the level and looks nearer than the player; 0 never moves at all, like a sky.
11. Tile maps, cameras and levels · Y-sort: lower on screen draws in front · QuizWith Y-sort on, the player’s head draws over the trunk of a tree while their feet are clearly behind it. What’s the likely cause?B. The player’s origin is at the centre of the sprite instead of at the feetY-sort compares the nodes’ origins. With the player’s origin halfway up the body, they count as “lower” than the tree while their feet are still behind its base. Put the origin where the character touches the ground (offset the sprite up, keeping the node at the feet), and do the same for the tree, and the order matches what the eye expects.
11. Tile maps, cameras and levels · Moving from one level to the next · Fill the blanksFinish the door: export a scene path and change to it when the player enters.1: export_file, 2: change_scene_to_file, 3: call_deferred@export_file shows a file picker in the Inspector, limited by the filter to scenes. change_scene_to_file is a method of the SceneTree, reached with get_tree(). Calling it through call_deferred runs it once the current physics callback has finished, which avoids “can’t change this state while flushing queries” errors.
12. Sound, particles and game feel · Playing a sound · Find the bugThe coin disappears when picked up, but its chime is never heard. Which line is wrong?Line 8: hide(); set_deferred("monitoring", false); await chime.finished; queue_free()Chime, the node playing the sound, is a child of the coin, so freeing the coin frees Chime in the same frame, and the sound stops before it is heard. Hide the coin and stop it detecting (so it can’t be collected twice), wait for the sound’s finished signal, then free it. Another common fix is to play pickup sounds from a player that outlives the coin, such as one in an autoload.
12. Sound, particles and game feel · Audio buses: a mixing desk for your game · Fill the blanksSet the Music bus from a slider value between 0 and 1.1: get_bus_index, 2: set_bus_volume_db, 3: linear_to_dbBuses are found by name with AudioServer.get_bus_index, then set with set_bus_volume_db. Passing the slider’s 0.5 straight in would set +0.5 dB, louder than normal, instead of about −6 dB. At 0, linear_to_db gives minus infinity, which is silent; many games also call set_bus_mute there to be sure.
12. Sound, particles and game feel · Audio buses: a mixing desk for your game · QuizA bus is at 0 dB. You set it to −12 dB. Roughly how big is the signal’s amplitude now?B. A quarterEach −6 dB halves the amplitude, so −12 dB halves it twice: a quarter (). Decibels add where amplitudes multiply, which is why they’re used for volume. Silence is far lower, near −80 dB.
12. Sound, particles and game feel · Music that loops and fades · QuizThe music player is in each level scene. Every time the player goes through a door, the music starts again from the beginning. Why?B. Changing scene frees the old level, and its music player with it; the new level starts its ownchange_scene_to_file frees every node in the old scene, so the player and its playback position are gone. To keep music going across levels, the player must live outside the levels: in an autoload, which the next module covers, with a fade_to() like the one above.
12. Sound, particles and game feel · Particles: many tiny sprites at once · QuizYou want a puff of dust when the player lands: 12 particles, all at once, then nothing. Which settings?A. One Shot on, Explosiveness 1, Emitting set to true on landingOne Shot makes it stop after one cycle; Explosiveness 1 releases all 12 at the same moment instead of spreading them out; setting emitting = true when the player lands fires it, and setting it again later fires another puff. With One Shot off it would leak dust forever, like a chimney.
12. Sound, particles and game feel · Screen shake, done gently · Fill the blanksAn enemy hit should add 0.4 trauma to the camera.1: get_camera_2d, 2: add_trauma, 3: 0.4get_viewport().get_camera_2d() finds the active 2D camera, wherever it is in the tree, so the enemy doesn’t need a path to it. Then call the method the camera script defines. For a bigger game, a signal on an event bus (module 18) keeps the enemy from knowing about cameras at all.
12. Sound, particles and game feel · Hit stop: freeze for a few frames · Find the bughit_stop(0.08) should freeze the game for 0.08 seconds, but it freezes for more than a second. Which line is wrong?Line 3: await get_tree().create_timer(seconds, true, false, true).timeoutA timer normally runs in game time, which time_scale has slowed to 5 %. So 0.08 seconds of game time takes 0.08 / 0.05 = 1.6 real seconds. Passing ignore_time_scale = true (the fourth argument) makes the timer count real time. Keep hit stops short: 0.05 to 0.15 seconds feels punchy; more feels like lag.
12. Sound, particles and game feel · Easing, squash and stretch · QuizA menu panel should slide in quickly and settle softly into place. Which easing fits best?C. Ease out (starts fast, ends slow)Ease out moves most of the way at once, so the panel feels instant, then slows into its final spot, so it doesn’t slam. Ease in does the opposite and feels sluggish at the start and abrupt at the end; it suits things leaving the screen. Linear looks mechanical.
12. Sound, particles and game feel · Let players turn the juice down · QuizEnemies glow red when they are about to attack. What makes that warning accessible to more players?B. Add a second cue that isn’t colour: a shape over the enemy, or a soundAbout one man in twelve has some colour vision deficiency, and red and green are the usual pair that look alike. Any single colour fails someone. A second channel, an exclamation mark, a wind-up animation, a sound, carries the warning to everyone, and usually makes the game clearer for all players.
13. Game state, resources and saving · Using an autoload, and not overusing it · QuizWhat happens to an autoload node when the game calls change_scene_to_file()?B. Nothing: it stays in the tree with its variables as they wereChanging scene only replaces the current scene, one child of the root. Autoloads are other children of the root, so they are untouched: same node, same variables, no second _ready(). That is exactly why they hold state across levels.
13. Game state, resources and saving · Exporting resources, and the shared-copy trap · Find the bugFive slimes share slime_stats.tres. Hitting one slime lowers the health of all five. Which line causes it?Line 6: stats = stats.duplicate()All five stats variables point at the one resource loaded from the file, so line 9 changes it for everyone. Give each slime its own copy when it starts, with stats = stats.duplicate(). Alternatively tick Local to Scene on the resource, which makes each instanced scene get a copy. Better still, keep data that changes during play (current health) in the node, and the resource for what doesn’t (max health).
13. Game state, resources and saving · Where a game may write: user:// · QuizAn exported game saves to res://save.json. What happens?B. The write fails: res:// is read-only in an exported gameIn the editor res:// is a normal folder, so this seems to work, which is why the bug usually turns up only after exporting. In the exported game, res:// is inside the packed game files and can’t be written: FileAccess.open returns null. Always save to user://.
13. Game state, resources and saving · Settings in a ConfigFile · Fill the blanksRead the fullscreen setting, defaulting to false if it isn’t there.1: load, 2: OK, 3: get_value, 4: falseload returns an Error, and OK means it worked; on the first run there is no file and it returns ERR_FILE_NOT_FOUND. The third argument of get_value is the default, used when the key is missing, which keeps old settings files working after you add a new option.
13. Game state, resources and saving · A save file in JSON · QuizYou save {"coins": 3} as JSON and load it back. What type is data["coins"]?B. floatJSON has one kind of number, and Godot parses every number as a float, so 3 comes back as 3.0. Assigning that to a typed int variable usually converts it, but comparing or printing it can surprise you, so convert with int() on purpose. Godot types such as Vector2 don’t survive JSON at all: store x and y as two numbers.
13. Game state, resources and saving · Saving resources, and why not to load untrusted ones · QuizYour game lets players share level files they built with an in-game editor. Which format is the safe choice?B. JSON parsed with JSON.parse_string, checked before useShared files come from strangers, so they must be pure data. JSON can’t hold code; at worst it holds wrong values, which you check (is it a dictionary, are the numbers in range) before building the level. Loading someone else’s .tres can run their script on your player’s computer.
13. Game state, resources and saving · Designing saves that last · Put in orderPut loading a save in a safe order.1. Check the save file exists 2. Read its text and parse the JSON 3. Check the result is a dictionary 4. Migrate it to the current version 5. Copy its values into the game state 6. Change to the saved levelEach step guards the next: no file, nothing to parse; broken JSON, nothing to check; the wrong shape, nothing to migrate. Migrating before copying means the rest of the game only ever sees the current format. The scene change comes last, so the new level reads state that is already loaded.
14. Enemies: state machines and pathfinding · An enemy is always in one state · QuizWhat is the main thing a state machine gives an enemy that a heap of if statements doesn’t?B. Only one behaviour is active at a time, and every change of behaviour is an explicit transitionThe value is clarity: you can’t chase and attack at once, and to find out why the guard gave up you look at one transition. Speed and intelligence don’t come from the pattern; pathfinding is a separate tool, used inside a state such as Chase.
14. Enemies: state machines and pathfinding · A state machine with an enum and match · Fill the blanksAdd a FLEE state that runs away from the target when health is low.1: FLEE, 2: match, 3: State.FLEEA new state is a new enum value, a new match branch, and at least one transition into it, here from Chase when health drops. direction_to from the target to the slime points away from the target. Forgetting the transition is the usual mistake: the branch exists but nothing ever enters it.
14. Enemies: state machines and pathfinding · Doing something once on entering a state · QuizInside the Chase branch, which runs every physics frame, the code calls sprite.play("run"). What does the player see?A. The run animation, playing normallyThis one is forgiving: AnimatedSprite2D.play() does nothing new if that animation is already playing, so it runs normally. But many things aren’t forgiving: starting a Timer every frame keeps resetting it, and playing a sound every frame stacks it 60 times a second. That is why “once on entering” belongs in change_state, not in the per-frame code.
14. Enemies: state machines and pathfinding · Seeing the player: an area, then a ray · Find the bugThe enemies are children of an Enemies node placed at (400, 0). Their rays point at the wrong spot and line of sight is nonsense. Which line is wrong?Line 4: global_position, player.global_position, WALLS)position is relative to the node’s parent; the physics space works in global coordinates. While every parent sits at the origin the two match, so the bug hides, then appears the moment a parent moves. Use global_position for anything you hand to the physics server. A RayCast2D node is the other way to do this: its target_position is local to it, and it updates every physics frame.
14. Enemies: state machines and pathfinding · Steering: seek, flee and arrive · Fill the blanksWrite flee: steer away from threat at full speed.1: direction_to, 2: global_position, 3: move_towarda.direction_to(b) is the unit vector from a towards b, so from the threat towards the enemy points away from the threat. The same move_toward line as seek keeps the turn smooth. Writing global_position.direction_to(threat) would be seek, the opposite.
14. Enemies: state machines and pathfinding · Navigation: finding a way around walls · Put in orderPut setting up a navigating enemy in order.1. Add a NavigationRegion2D to the level 2. Draw the walkable outline and bake the navigation mesh 3. Add a NavigationAgent2D as a child of the enemy 4. Set the agent’s target_position to where the enemy should go 5. Each physics frame, move towards get_next_path_position()The walkable area must exist (region, then a baked mesh) before any agent can plan on it. The agent belongs to the enemy, then gets a target, and only then is there a path for get_next_path_position() to follow. Forgetting to bake is the classic mistake: the agent finds no path and the enemy stands still.
14. Enemies: state machines and pathfinding · Avoidance: crowds that don’t pile up · QuizAvoidance is on, _on_velocity_computed is connected and calls move_and_slide(), but the enemy never moves. The physics code sets velocity (the body’s) towards the next point. What’s wrong?B. It sets the body’s velocity instead of agent.velocity, so the agent never computes a safe velocityThe agent only emits velocity_computed after it is given a wanted velocity through agent.velocity. Setting the body’s own velocity skips the agent entirely, and then the callback overwrites it with whatever it had, zero. Two variables with the same name on two nodes is an easy mix-up.
14. Enemies: state machines and pathfinding · Cooldowns with timers · Find the bugThis enemy attacks once and never again. Which line is wrong?Line 9: cooldown.start() # moved inside the if, after hit_player()start() restarts the timer from the full wait time. Called every frame, it never gets to 0, so timeout never fires and can_attack stays false. Start the timer once, when the attack happens. Restarting a timer by accident is one of the most common Godot bugs, and the “enter once” idea from earlier is the cure.
14. Enemies: state machines and pathfinding · Grid pathfinding with AStarGrid2D · QuizA programmer marks the wall cells solid, then sets region and calls update(). Paths now go straight through walls. Why?A. update() rebuilds the grid for the new region and clears the solid cellsupdate() builds the grid’s points from region, cell_size and the other settings, and that resets every cell to walkable. So set the shape, call update(), and only then mark solids. When the map changes during play (a door opens), call set_point_solid(cell, false) on just that cell; no update() needed.
15. Into 3D: nodes, meshes, lights and cameras · 3D is the same tree with one more axis · QuizIn a 3D scene, which node shows a box you can see?B. MeshInstance3D with a BoxMeshA MeshInstance3D draws a mesh, and BoxMesh is a mesh Godot builds for you. A plain Node3D has a place but nothing to draw, and a CollisionShape3D is for physics: it is invisible in the game. A Sprite2D is a 2D node and does not live in 3D space at all.
15. Into 3D: nodes, meshes, lights and cameras · Y is up, and forward is minus Z · QuizA Camera3D has no rotation. Which way does it look?B. Along -zEvery node in Godot 3D looks along its own -z, which is why Vector3.FORWARD is (0, 0, -1). The tempting answer is +z, because the axis is called “z”, but +z points out of the screen at you.
15. Into 3D: nodes, meshes, lights and cameras · A transform is position, rotation and scale · Fill the blanksPut the lamp 3 metres up, and turn it 90 degrees around Y.1: position, 2: 3.0, 3: deg_to_radposition is relative to the parent; y is up, so 3 metres up is (0, 3, 0). rotation is in radians, so convert with deg_to_rad, or write rotation_degrees.y = 90.0. Writing rotation.y = 90.0 is the usual mistake: that is 90 radians, more than 14 full turns, which lands at a strange angle.
15. Into 3D: nodes, meshes, lights and cameras · The basis tells you which way a node faces · QuizA turret calls look_at(target.global_position, Vector3.UP), and the target stands exactly above it. What goes wrong?B. Godot reports an error, because the direction and the up vector are parallellook_at needs to know which way is up to build the basis, and it cannot if the direction to look is the same line as up: there is no single answer for which way is “right”. Godot prints an error and the node keeps its rotation. Pass another up vector, such as Vector3.FORWARD, in that case, or skip the call when the target is straight overhead.
15. Into 3D: nodes, meshes, lights and cameras · Meshes: primitive shapes to start with · Find the bugNo ball appears, and Godot complains about a type. Which line is wrong?Line 8: add_child(ball)The sphere is a resource: data that describes a shape, not a node. Only nodes go in the scene tree, so add_child(sphere) is an error (a SphereMesh is not a Node) and the ball, the node that would draw it, is never added. Add ball. Keep the two ideas apart: a resource describes, a node puts it in the world. (height is the full height, so a round sphere has height = 2 * radius.)
15. Into 3D: nodes, meshes, lights and cameras · Importing models made in other tools · Put in orderPut the steps for using a character model from Blender in order.1. Export it from Blender as a .glb file 2. Copy the file into the Godot project folder 3. Let Godot import it, and check the import settings 4. Make a new scene with a CharacterBody3D root 5. Drag the imported model in as a child, and add a collision shapeThe model becomes a scene only once Godot imports it, and that import is where scale and animations are set. Your own scene then wraps it: the body, collision and script are yours, and the model is just its look, so a new version of the model can replace it without losing your work.
15. Into 3D: nodes, meshes, lights and cameras · Materials: what a surface is made of · Fill the blanksMake a dull, rusty metal: fully metallic, quite rough.1: StandardMaterial3D, 2: metallic, 3: roughness, 4: material_overridemetallic and roughness both go from 0 to 1. Metal is 1; a rough surface scatters light so it has no sharp highlight. material_override replaces every surface’s material on that one node. A material is a resource, so if several nodes share it, changing it in code changes them all: call duplicate() first when you want one node to look different.
15. Into 3D: nodes, meshes, lights and cameras · Three kinds of light · QuizYou move a DirectionalLight3D 100 metres to the left without turning it. What changes in the scene?B. Nothing: a directional light only cares about its rotationA directional light stands for something so far away that its rays are parallel, like the sun, so only the direction matters. To make it morning or evening, rotate it. An OmniLight3D is the opposite: only its position matters, and it lights only what is inside its omni_range.
15. Into 3D: nodes, meshes, lights and cameras · The environment: sky, ambient light and tone · QuizYour level has a sun and a sky, but the sides of buildings facing away from the sun are pitch black. What is the first thing to check?A. The ambient light in the EnvironmentAmbient light is what lights the parts no light reaches directly. If it is off or set to a black colour, shadows are black. Set its source to the sky (or a colour) and raise its energy a little. The camera and collision have nothing to do with how bright a surface is drawn.
15. Into 3D: nodes, meshes, lights and cameras · The camera decides what you see · Find the bugPlayers say the mountains in the distance pop in and out as they walk. Which line causes it?Line 6: far = 2000.0Anything farther than far is not drawn, so mountains a few hundred metres away vanish and reappear as the player moves near the 50-metre edge. Raise far to cover the level (the default is 4000). Do not push near toward 0 to compensate: a tiny near with a huge far wastes the depth buffer’s precision and makes distant surfaces flicker against each other.
16. A 3D character: movement, physics and the camera · A character is a CharacterBody3D with a shape and a look · QuizWhy is a capsule the usual collision shape for a walking character?B. Its rounded bottom slides over small steps and edges instead of catching on themA box’s flat bottom and sharp edges catch on the lip of every step and seam between floor pieces; a capsule’s round end rides over them. Any shape works, and the shape is never drawn in the game (only in the editor, or with Visible Collision Shapes in the Debug menu). A CharacterBody3D has no mass at all: you move it yourself.
16. A 3D character: movement, physics and the camera · Gravity pulls velocity down, a jump sets it up · QuizPlayers want the jump twice as high. What should jump_velocity be, starting from 4.5?B. About 6.4The height of a jump is , so it grows with the square of the speed. Twice the height needs times the speed: . Doubling the speed to 9.0 quadruples the height. Halving gravity also doubles the height, but it makes the whole jump floaty and slow, a different feel.
16. A 3D character: movement, physics and the camera · Move relative to where the camera looks · QuizA learner writes var dir := Vector3(input.x, 0.0, -input.y). What happens when they push forward?B. The player runs toward the cameraget_vector already gives forward as a negative y, and forward in Godot is negative z, so the sign is right as it is: z = input.y. Negating it turns forward into +z, toward the camera. It is a common slip from engines where forward is +z. When movement feels mirrored, print input and dir and check each sign.
16. A 3D character: movement, physics and the camera · Mouse look: capture the mouse, read how far it moved · Find the bugWith the mouse captured, the camera turns once and then never moves again. Which line is wrong?Line 3: var dx: float = event.relative.xposition is where the pointer is, and a captured pointer is held still, so it is always about the same number. relative is how far it moved this event, which is what turning needs. Also note the input goes in _unhandled_input, not _process, so a menu that handles the click first keeps it from turning the camera.
16. A 3D character: movement, physics and the camera · A SpringArm3D keeps the camera out of walls · Put in orderBuild the third-person camera rig, in order.1. Add a Node3D called CameraPivot as a child of the player, at head height 2. Add a SpringArm3D under the pivot, and set its spring_length 3. Add a Camera3D under the spring arm 4. Keep the arm from hitting the player, with its mask or add_excluded_object 5. Turn the pivot from the mouse in _unhandled_inputEach node has one job. The pivot sits where the camera orbits around and holds the yaw and pitch; the arm sets the distance and handles walls; the camera only looks. Turning the pivot swings the arm and the camera with it, because children follow their parent’s transform.
16. A 3D character: movement, physics and the camera · Turn the model to face where it runs · QuizThe model faces an angle of 3.1 radians and should turn to −3.1. Why use lerp_angle and not lerp?B. lerp would spin it almost a full turn the long way; lerp_angle turns the 0.08 radians acrossAngles wrap: 3.1 and −3.1 are both nearly half a turn, about 0.08 radians apart. Plain lerp sees two numbers 6.2 apart and goes through 0, a full spin the wrong way round. lerp_angle knows angles wrap at a full turn and takes the shorter path.
16. A 3D character: movement, physics and the camera · Slopes, steps and sticking to the floor · QuizA ramp is 50 degrees steep and floor_max_angle is left at the default. What happens when the player runs at it?B. It counts as a wall: they slide back down and is_on_floor() is false on it50 is more than 45, so the ramp is a wall to move_and_slide(). That is the point of the setting: it stops players climbing cliffs. Raise floor_max_angle (with deg_to_rad(55.0)) if the ramp is meant to be walked, or make it gentler. And since the player is “not on floor” there, they cannot jump off it either.
16. A 3D character: movement, physics and the camera · RigidBody3D: things physics moves for you · Find the bugCrates fly toward the player when touched, instead of being pushed away. Which line is wrong?Line 8: crate.apply_central_impulse(-hit.get_normal() * push_force)The collision normal points out of the crate’s surface, toward the player. To push the crate away, use the opposite direction, -hit.get_normal(). as RigidBody3D gives null for anything else, the floor, walls, so if crate: skips them. Call it right after move_and_slide(): that call is what fills the list of collisions.
16. A 3D character: movement, physics and the camera · Area3D and raycasts in 3D · Fill the blanksA RayCast3D under the player points down 1.5 metres. Finish the ground check.1: target_position, 2: is_colliding, 3: get_collision_pointtarget_position is relative to the ray node, so (0, -1.5, 0) is straight down from wherever the ray is. is_colliding() says whether it hit, and get_collision_point() where, in world space. The ray updates once per physics frame; after moving it in code, call force_raycast_update() to check right away.
16. A 3D character: movement, physics and the camera · Prototype levels with CSG shapes · QuizYou have a CSGBox3D wall. How do you cut a doorway through it?A. Add a child CSGBox3D the size of the door, with operation set to subtractionA child set to subtract removes its volume from the parent, leaving a hole the player can walk through, collision included. It is quick to try ideas with, but CSG is rebuilt whenever it changes and is slower than plain meshes, so when the layout is settled, model the real level or bake the CSG to a mesh.
17. Shaders and visual effects · A shader is a tiny program the graphics card runs for every pixel · QuizA sprite covers 100 by 100 pixels on screen. How many times does its fragment shader run each frame?C. About 10 000 timesThe fragment shader runs once for every pixel the sprite covers: 100 × 100 = 10 000. The vertex shader runs 4 times, once per corner of its quad. This is why fragment work is where shaders get expensive: a full-screen effect runs for every pixel on the screen, millions of times a frame.
17. Shaders and visual effects · Your first shader: a canvas_item that changes colours · Fill the blanksTurn the sprite grey: work out its brightness and use it for all three channels.1: canvas_item, 2: fragment, 3: vec3dot multiplies the channels by those weights and adds them up: green counts most because eyes are most sensitive to it. A plain average, (r + g + b) / 3.0, works but makes blues look too bright. Colour is set per pixel, so it belongs in fragment(), not vertex().
17. Shaders and visual effects · UV: where on the texture this pixel is · QuizWhat does this shader draw on a square sprite?A. Black at the top fading to white at the bottomEach pixel’s grey is its UV.y, which is 0 at the top and 1 at the bottom in a canvas_item. UV.x would fade left to right. The texture is never read, so the picture is gone; only its rectangle is used. Drawing UV as a colour like this is the classic way to debug a shader.
17. Shaders and visual effects · TIME makes things move · QuizEvery tuft of grass using this shader sways exactly in step, which looks fake. What fixes it?A. Add an offset that differs per tuft, such as its world position, inside the sinTIME is the same for everyone, so the same formula gives the same swing. Add something that differs per tuft, sin(TIME * speed + offset): a uniform set from code, or the world position (in a canvas_item, (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).x in vertex()). cos is the same wave shifted, still in step for everyone.
17. Shaders and visual effects · Uniforms: knobs your scripts can turn · Find the bugThe sprite turns white on a hit and stays white; Godot reports a missing property. Which line is wrong?Line 5: tw.tween_property(mat, "shader_parameter/flash", 0.0, 0.15)A ShaderMaterial has no property called flash: its uniforms live under shader_parameter/, so the tween’s property path is "shader_parameter/flash". mix(a, b, t) returns a at t = 0 and b at t = 1, so tweening flash from 1 to 0 fades the white out.
17. Shaders and visual effects · A material is shared until you make it unique · QuizYou hit one slime and all twelve slimes on screen flash white. What is the cause?B. They share one ShaderMaterial, so its flash uniform is the same for allOne material, one set of uniform values, twelve sprites drawing with it. Make the material local to the scene or duplicate it per instance. Sharing is the default for a reason: identical materials can be drawn together, more cheaply. So only make unique what really changes per instance.
17. Shaders and visual effects · Dissolve: noise decides which pixels vanish first · Fill the blanksFinish the core of the dissolve.1: texture, 2: progress, 3: discardtexture samples the noise at this pixel’s UV, and .r takes the red channel, which is the grey value. discard skips the pixel entirely: not transparent black, simply not drawn. In a 3D spatial shader, discard stops the GPU from skipping hidden pixels early, so it costs more there than in 2D.
17. Shaders and visual effects · Spatial shaders paint 3D surfaces · QuizYou want a ghost that ignores every light in the scene and is always drawn in its own flat colour. What do you add?A. render_mode unshaded; after the shader_type linerender_mode unshaded skips lighting: ALBEDO is drawn as it is. Roughness only changes how light reflects, so it still depends on lights. canvas_item is for 2D and cannot be used on a 3D mesh. Other render modes include cull_disabled (draw both sides) and blend_add (add onto what is behind, for glows).
17. Shaders and visual effects · Visual shaders: the same thing as a graph · QuizWhich statements about visual shaders are true?A. They are converted into Godot’s shading language; C. They can use TIME, UV and textures, like text shadersA visual shader is another way to write the same program: Godot generates shader code from the graph, and it runs on the GPU like any other. It has canvas_item, spatial, particles, sky and fog modes, just as text shaders have those shader_types.
17. Shaders and visual effects · Post-processing: a shader over the whole screen · Find the bugThis shader comes from a Godot 3 tutorial and does not compile in Godot 4. Which line is wrong?Line 4: vec3 c = texture(screen, SCREEN_UV).rgb;Godot 4 removed the built-in SCREEN_TEXTURE. Declare your own sampler, uniform sampler2D screen : hint_screen_texture;, and read that. (The same goes for DEPTH_TEXTURE, now hint_depth_texture.) Old tutorials are full of Godot 3 names; when something does not exist, check the version first.
17. Shaders and visual effects · What makes shaders slow · Put in orderOrder these from cheapest to most expensive, all at 1920×1080, the same shader on each.1. A 64×64 sprite 2. A 512×512 sprite 3. One full-screen effect 4. Three full-screen effects stacked on top of each otherSame work per pixel, so the cost follows the pixels: about 4 thousand, 262 thousand, 2 million and 6 million. Stacking full-screen passes multiplies the cost, which is why it pays to combine several screen effects into one shader that reads the screen once.
18. Architecture: autoloads, custom resources and patterns · A game grows, so plan for change · QuizWhy group files by feature (player/, enemies/slime/) rather than by type (scripts/, scenes/, sprites/)?B. A change to one feature touches one folder, and a feature can be moved or deleted as a wholeGodot does not care where files are: any layout works, and the export is the same size. Feature folders help people: the slime’s scene, script and art change together, so they sit together, and deleting the slime is deleting one folder. With type folders, one feature is spread across four places. When you move files, move them in the FileSystem dock, so Godot updates every path that points at them.
18. Architecture: autoloads, custom resources and patterns · Call down, signal up · Find the bugThis coin only works in one exact tree. Which line breaks the rule?Line 6: collected.emit(1)get_parent().get_parent() assumes the coin is exactly two levels below something with add_score. Put it inside a container node, or in a test scene, and it crashes. Emitting collected lets whoever placed the coin decide what a coin is worth to them. The tempting answer, line 7, is fine: a node freeing itself is its own business.
18. Architecture: autoloads, custom resources and patterns · An event bus for news the whole game hears · Fill the blanksEmit on the bus from the player, and listen to it from the music player.1: emit, 2: connectA signal is an object: .emit() fires it, .connect(callable) subscribes a function, passed by name without parentheses. The handler then does whatever the music should do, here play_sad_song(). Notice that the player never mentions music, and the music never mentions the player: both know only Events.
18. Architecture: autoloads, custom resources and patterns · Components: behaviour as child nodes · QuizWhich is the best sign that a piece of behaviour should become a component?B. Several unrelated kinds of object need it, and it works without knowing which one it is inA component earns its place when it is reused across objects that don’t share a base class, and when it can do its job knowing only itself (health counts down; what death means stays in the owner). Length alone isn’t a reason, and splitting something only the player uses into components just adds nodes to look through.
18. Architecture: autoloads, custom resources and patterns · Custom resources hold the data · QuizTen slimes each have @export var stats: EnemyStats set to the same slime_stats.tres. One slime runs stats.health -= 5. What happens?B. All ten slimes lose health, because they share one resource objectGodot loads a resource file once and every user gets the same object, which is what makes resources cheap. So changing it changes it for everyone. Keep resources for data that doesn’t change while playing (max health, speed), and keep changing values (current health) on the node. If an instance truly needs its own copy, call stats = stats.duplicate() in _ready, or tick Local to Scene on the resource. Nothing is written to disk unless you call ResourceSaver.save.
18. Architecture: autoloads, custom resources and patterns · The command pattern: actions as objects · Fill the blanksRun a move and remember it; undo the last one.1: new, 2: push_back, 3: is_empty, 4: pop_backMoveCommand.new(by) calls _init(by). push_back (or append) adds to the end, and pop_back removes and returns the last element, so the most recent move is undone first: a stack, last in, first out. Checking is_empty() first matters: pop_back on an empty array returns null, and calling undo on null is an error.
18. Architecture: autoloads, custom resources and patterns · The state pattern: one object per state · Put in orderPut the steps of a state change in order.1. The current state’s update returns a different state 2. The machine calls exit() on the old state 3. The machine makes the new state the current one 4. The machine calls enter() on the new state 5. Next frame, the new state’s update runsexit runs while the old state is still current, so it can tidy up (stop its animation, its timers). Only then does the new state become current and enter (start its animation). Swapping before exit would call exit on the wrong state, a classic bug that leaves animations or sounds running.
18. Architecture: autoloads, custom resources and patterns · Testing GDScript with GUT or gdUnit4 · Fill the blanksFinish a GUT test that checks health never goes below zero.1: GutTest, 2: test_, 3: assert_eqThe test script extends GutTest, which gives it the asserts. GUT finds tests by the test_ prefix: a function named health_stops_at_zero would never run, and a test that never runs passes forever. assert_eq(got, expected) fails the test when they differ. In gdUnit4 the same check reads assert_int(health.health).is_equal(0) in a script that extends GdUnitTestSuite.
19. Performance, debugging and shipping · The debugger stops the game so you can look inside · QuizThe game is paused on the line var path := find_path(start, goal). You only care whether path comes out empty. Which button do you press?B. Step OverStep Over runs the whole line, including all of find_path, and pauses on the next line, where you can read path in the variables list. Step Into would take you inside find_path, line by line, which is what you want only when you suspect find_path itself. Continue runs on to the next breakpoint, maybe far away.
19. Performance, debugging and shipping · The remote scene tree shows the running game · QuizYou spawn coins from code. During play, the Local tab shows no coins under the level. What does that tell you?B. Nothing: Local is the saved scene, and nodes made by code only appear in RemoteLocal shows what is saved on disk, and code-spawned coins were never part of that file. Switch to Remote to see the live tree. Mixing the two up is a common source of “it isn’t there” confusion.
19. Performance, debugging and shipping · Printing, warnings and asserts · QuizThis line works while you develop. What happens in the exported release build?B. The key is never removed, because the whole assert line is left out of release buildsRelease builds skip assert entirely, including the expression inside it, so remove_item is never called and the player keeps the key forever. Never put work inside an assert: do the work on its own line, keep the result in a variable, and assert on the variable.
19. Performance, debugging and shipping · A frame has a budget, measured in milliseconds · Put in orderPut a performance fix in the order that works.1. Find a place in the game that is reliably slow 2. Record it with the profiler 3. Find the function that takes the most time 4. Change that one thing 5. Record the same place again and compareMeasure, change one thing, measure again. Fixing code before measuring means you may speed up something that took 0.1 ms, and changing several things at once means you can’t tell which helped. Always profile the same scene in the same way, so the numbers compare.
19. Performance, debugging and shipping · Common fixes once you know what is slow · Find the bugThe profiler says this enemy script is slow with 200 enemies. Which line is the problem?Line 6find_child searches recursively by default, so it walks the whole scene tree, every physics frame, for each of 200 enemies: that is thousands of node visits, 60 times a second. Find the player once, in _ready or an @onready variable (for example with a "player" group), and keep it. The maths and move_and_slide on the other lines are cheap.
19. Performance, debugging and shipping · An object pool reuses nodes instead of freeing them · Fill the blanksA bullet from the pool keeps its old position and direction. Reset it when it is fired.1: take, 2: null, 3: global_positionA pooled object comes back exactly as it was left, so everything a fresh one would have set in _ready must be set again on taking: position, direction, health, timers. Forgetting that is the classic pool bug: a bullet appears where the last one died. Check for null first, since the pool can run dry.
19. Performance, debugging and shipping · Exporting: presets and export templates · QuizWhat is the difference between a debug export and a release export?B. Debug builds keep checks like assert and can connect to the editor’s debugger; release builds drop them and run fasterA debug export uses the debug template: asserts run, errors are more detailed, and it can talk to the editor for remote debugging. A release export uses the optimised release template and drops that. Test with debug builds; give players release builds, and play-test the release build too, since it is what they actually run.
19. Performance, debugging and shipping · Desktop, web and mobile each need something different · QuizYou double-click the exported index.html and the game shows an error instead of starting. What is the likely reason?B. A web build must be served over HTTP; the browser blocks it when it’s opened as a local fileBrowsers refuse to load the .wasm and .pck from a file:// page. Serve the folder from a small local web server, or mark the Web preset Runnable and use the Remote Deploy button in the editor’s top-right, which runs the web build in your browser from a server Godot starts for you.
19. Performance, debugging and shipping · Putting it on itch.io · Put in orderPut shipping a web version in order.1. Download the export templates for your exact Godot version 2. Add a Web preset in Project > Export 3. Export a release build into an empty folder 4. Test it in a browser through a local server 5. Upload the folder as a zip to itch.io as an HTML projectTemplates come first: without them, the Export dialog can’t build. Export into an empty folder so old files don’t ride along. Testing locally before uploading catches missing files and the threads problem while it is still quick to fix.
20. Expert Godot · Tool scripts run inside the editor · Find the bugThis sprite should move while the game plays, and stay still in the editor. It does the opposite. Which line is wrong?Line 7: if not Engine.is_editor_hint():Engine.is_editor_hint() is true when the code runs in the editor. Here it moves the sprite only in the editor, so it slides away while you work, and the moved position is saved into your scene. The guard must be not: gameplay code runs only in the game. Every @tool script should ask this question in any callback that changes things.
20. Expert Godot · Editor plugins add docks and tools to Godot · Put in orderPut making a plugin in order.1. In Project Settings > Plugins, press Create New Plugin 2. Give it a name and folder; Godot writes plugin.cfg and the script 3. Write _enter_tree to add what the plugin provides 4. Write _exit_tree to remove all of it 5. Tick the plugin’s Enable box and try itThe Create New Plugin dialog writes the folder, plugin.cfg and a script with empty _enter_tree and _exit_tree, so you start from a working shape. Write both halves before enabling: a plugin that adds without removing leaves pieces behind each time you turn it off and on while developing.
20. Expert Godot · Custom node types with class_name and an icon · QuizYou want a ShakeCamera node teammates can add from the Create Node dialog, with its own icon. What is the simplest way?B. Give its script class_name ShakeCamera and an @iconclass_name registers a global class the editor lists with the built-in nodes; @icon sets its icon. add_custom_type in a plugin is the older way and is still around, but needs a plugin enabled. GDExtension is for code that must be native. A saved scene works too, but it is added with “Instantiate Child Scene”, not as a node type.
20. Expert Godot · GDExtension: native code when you need it · QuizWhich is the best reason to move code from GDScript to GDExtension?B. The profiler shows one GDScript function doing heavy maths on thousands of items takes most of the frameGDScript is slow at tight loops of its own, so a measured hot spot of pure computation (generating terrain, a custom pathfinder over a huge grid) is what native code fixes. move_and_slide is already C++ inside the engine, so calling it from GDScript costs little extra. Moving everything to C++ “to be fast” trades a lot of iteration speed for nothing you measured.
20. Expert Godot · C# in Godot · QuizWhich of these are true of C# in Godot 4? Pick all that are.A. You need the .NET build of Godot and the .NET SDK installed; B. Script classes are declared partial; D. Signals are declared as delegates whose names end in EventHandlerThe standard Godot download has no C#; the .NET build does, and it compiles with the .NET SDK. Classes are partial so generated code can add the glue, and a signal is a delegate named …EventHandler (the signal itself is the name without it, Died). Web export is the catch: Godot 4 cannot yet export C# projects to the web, so a browser game is a reason to choose GDScript.
20. Expert Godot · High-level multiplayer: peers and a server · Fill the blanksStart a server on port 7000 for up to 4 clients.1: ENetMultiplayerPeer, 2: create_server, 3: multiplayer_peerMake the peer, start it as a server, and give it to the MultiplayerAPI. Nothing is networked until that last assignment. The joining side is the same with create_client(address, 7000).
20. Expert Godot · RPCs and authority: who may do what · Find the bugEach player node is named after its peer id. Every player moves everyone else’s character and not their own. Which line is wrong?Line 9: if not is_multiplayer_authority():Line 6 makes the peer whose id is the node’s name its authority, in _enter_tree so it is set before anything replicates. Then only that peer should read input for it. The guard is inverted: it returns on the one peer that owns the character, and reads input on all the others. Add not, and each peer drives only its own character.
20. Expert Godot · RPCs and authority: who may do what · QuizA function is marked with a plain @rpc, and nodes have their default authority. A client calls heal.rpc_id(1, 50). What happens on the server?B. The call is refused: by default only the authority, here the server, may call itA bare @rpc means "authority" mode, and every node’s default authority is the server, so the server may call it on clients but a client may not call it on the server. That is a safe default: to accept requests from clients, mark it "any_peer", and then check in the function that the request is allowed.
20. Expert Godot · MultiplayerSpawner and MultiplayerSynchronizer · QuizPositions sync from the server to clients, but you want each client to move its own player and send its position to the others. What decides who sends?B. The multiplayer authority of the synchronizer, which you set to that client’s idA synchronizer sends from its authority. set_multiplayer_authority(id) on the player sets it for the player and, by default, all its children, synchronizer included. Client-owned movement feels responsive, but a cheating client can send any position, so competitive games keep the server as authority and send only input.
20. Expert Godot · Under the nodes: the servers · QuizWhat is the catch with drawing through RenderingServer directly?B. Nothing frees what you create: you must free_rid every RID, and you lose the editor, signals and the scene tree for themNodes free their server objects when they are freed; RIDs you create are yours to free, or they leak until the game closes. And they are invisible to the scene tree: no Inspector, no Remote tab, no collisions unless you also ask the physics server. Reach for servers after the profiler shows that node count is the problem; before that, a MultiMeshInstance2D often solves the same thing more simply.
20. Expert Godot · Reading Godot’s source and contributing · QuizYou want Godot to have a built-in node for 2D ropes. Where do you start?B. Open a proposal in godot-proposals, describing the problem it solvesNew features begin as proposals, so maintainers and users can agree that the problem is real and the design fits before anyone writes code. A big pull request with no proposal is often closed, however good. Many such ideas end up better as an add-on in the Asset Library, which you can ship today.
20. Expert Godot · Mastery: putting it all together · QuizBarrels, crates and slimes can all be destroyed, each drops loot from its own list, and the HUD counts how many things you broke. Which design fits Godot best?B. A HealthComponent child on each, a LootTable resource per kind, and an Events.thing_destroyed signal the HUD listens toThey have different base classes (a slime moves, a crate doesn’t), so a shared base class can’t work: composition can. Loot differs only in data, so it belongs in resources. The HUD is far away in the tree, so game-wide news goes through the event bus rather than objects reaching for the HUD, which would break the “call down, signal up” rule.
20. Expert Godot · Mastery: putting it all together · QuizYour game runs at 60 FPS in the editor but stutters in the web build when many enemies are on screen. What do you do first?B. Profile a debug web build (or the same scene on desktop) to see whether scripts, physics or drawing take the timeMeasure before changing anything: the fix for slow scripts (cache, pool, do less per frame) is different from the fix for drawing (fewer draw calls, simpler materials). Web builds run on the Compatibility renderer and are generally slower, so a scene with little spare time on desktop may run out of it there. GDExtension is only a reason once a script hot spot is measured.
20. Expert Godot · Mastery: putting it all together · QuizA @tool script on each level reads its LevelData resource and, in the editor, sets data.enemy_count from the enemies in the scene. Two levels share one .tres. What goes wrong?B. Both levels write to the same shared resource, so whichever level you opened last decides the count for bothOne resource file is one shared object, in the editor as in the game, and a tool script really changes it. Give each level its own resource, or compute the count when it is needed instead of storing it. Two ideas from different modules, tool scripts and shared resources, combine into a bug neither shows alone.
20. Expert Godot · Mastery: putting it all together · Put in orderA client joins a game with a spawner. Put what happens in order.1. The client calls create_client and sets multiplayer.multiplayer_peer 2. The server’s peer_connected signal fires with the client’s id 3. The server instantiates a player named after that id and adds it under the spawner’s path 4. The spawner creates the same player on every peer 5. The player’s _enter_tree sets its multiplayer authority to that idConnecting comes first, and the server hears about it through peer_connected. Only the server adds players, under the spawner’s spawn_path, so they are replicated. On each peer, the new player enters the tree and sets its authority from its name, which is why naming it after the peer id matters: every peer can work out the same owner without another message.