Godot Engine
Created: 2020-04-21 08:38:17 -0700 Modified: 2020-07-12 21:44:10 -0700
Basics
Section titled Basics- They have a step-by-step getting-started guide here
- If you don’t want an extra console window when you start the engine, then start it from an existing CMD
- A list of all global constants is here
- Visual assets like sprites or 3D models are typically provided by sites like OpenGameArt or itch.io
- To measure the framerate of a particular scene, play the scene/game, click “Debugger” in the bottom panel, then click “Monitor” and show the FPS. You can disable vsync in the Project Settings if you want it to be uncapped.
- The building blocks in Godot are nodes, and they form scenes, and scenes can be instanced just like a node so that you manage all of the components more easily.
- When modifying instances of a scene, you’re modifying just that instance except in the case of shared resources like a material. In that case, making a new material will just affect the single instance. If you want to alter the material’s properties without making a new one, then select “Make Unique” using the arrow next to the resource:
- When a modification from the source scene exists, you’ll see a gray “revert” button:
- When saving anything, you can only save to the project itself, so you’ll see special paths like res:// (reference).
- Project settings are saved to project.godot, but the editor has a GUI for it via Project → Project settings.
- You can also save custom properties here for use in your game if you want (sort of like environment variables)
- Design language: Godot says that their design language is what separates them from other game engines (reference). They say to apply these steps
- List every entity that your game has that would be visible to a player (e.g. projectiles, players, features like a store)
- Draw a diagram indicating “ownership”
- Make scenes out of each entity represented
- They support four scripting languages: GDScript, VisualScript, C#, and C++.
- Localization is supported directly through the editor (see Project Settings → Localization). If you dynamically set up a string through a script, then you’ll need to use the tr function.
- To add arbitrary keyboard keys as input, go to Project → Project Settings → Input Map → Add. It’s easy to set up.
- There’s a concept of groups that you can use for ease of coding in your game. This can be done through scripting or through the UI itself. A group could be for something like alerting enemies that you’ve entered their zone (reference).
- add_to_group(“players”)
- is_in_group(“players”)
- Basics of scripts
- If you ever have a scene with just a single node, and that node has a script, then the scene doesn’t really need to exist; you could instead just make a member variable whose type is the same as the script, e.g. var stats:Stats = Stats.new(starting_stats)
- You can only have one script per node.
- Folder structure (this is not enforced at all, but this seems like a good convention)
- root
- assets
- sprites
- fonts
- audio
- scripts
- scenes
- assets
- root
- If you want to scale your entire game, I don’t think Project → Project Settings → Display → Window → Stretch → Shrink is a good idea because you may end up chopping off parts of your UI/game. Instead, use Project → Project Settings → Display → Window → Size → Test Width / Height. This will change the size of the window, not the viewport. You may also want to change the Stretch properties so that you scale appropriately.
- Beyond that, you can add a Camera and modify its zoom property.
- In general, you’ll want a Sprite to be a child of its RigidBody2D or Area2D.
- Most issues that you run into with the editor itself can usually be fixed by going back to the Project List (ctrl+shift+Q) rather than restarting Godot completely.
- If you want to instantiate a scene and still modify its children, e.g. to customize a collider, you can right-click the instantiated scene and toggle “Editable Children”.
- Tween easings don’t always exactly match easings.net, so someone made a chart
- If you make changes to the workspace or a particular node while the game is running, then it’ll persist those changes even after the game has stopped. If you don’t want this behavior, then you’ll need to click “Remote” when the game is running and make the changes through the inspector, not the workspace (the workspace always seems to make changes locally).
Contributing
Section titled ContributingIf modifying the documentation, make sure to build it first (reference).
Accommodating different resolutions
Section titled Accommodating different resolutionsThe official documentation is a really good resource for this. Here are some things I learned:
- HiDPI support is pretty awkward because
screen_get_scale
is only implemented on macOS (proposal to implement on other platforms here). What this means is that if you’re on macOS with a retina display, even if you use the macOS settings to set your resolution to1920x1080
, the viewport in Godot will be3840x2160
.
Building for macOS
Section titled Building for macOSThere are preview versions released on the official Godot site, but if you need a nightly version, you’ll have to dip in to their GitHub Actions (scroll down to “Artifacts” on a page like this). However, you can’t just double-click those artifacts to run them; you still have to go through a bunch of build steps. And if you want C# support, then you can’t even use those artifacts. 😢
Instead, here are the full steps I ran to build a Mono version for macOS (adapted from here):
- Get the Godot source
- Clone the repo
- Make sure it has whatever fix you’re trying to incorporate by searching through the code on your machine.
- Get the Vulkan SDK. This is needed if you’re building from the
master
branch.- I just got the SDK installer. I didn’t check any additional options during installation. I also didn’t need to reboot, start a new terminal, or otherwise write down any paths after the installation.
brew install scons
scons platform=macos arch=arm64 module_mono_enabled=yes
(themodule_mono_enabled
comes from these instructions)- This took about two minutes to run. I only built for ARM64 because that’s my computer architecture. I could still export non-ARM builds from an ARM version of the editor.
- This will produce
bin/godot.macos.editor.arm64.mono
, but it lacks “glue” (reference)../bin/godot.macos.editor.arm64.mono --headless --generate-mono-glue modules/mono/glue
- Build the managed libraries:
./modules/mono/build_scripts/build_assemblies.py --godot-output-dir=./bin
- At this point, you should be able to run
./bin/godot.macos.editor.arm64.mono
and develop your game. - To export using this custom-built version of Godot, you need to build export templates (reference). For this, even though I’m only exporting from ARM, I want to export a universal build, so I had to run all of the commands on the page. Each
scons
command took about 90 seconds. thelipo
ones are practically instantaneous.scons platform=macos target=template_release arch=x86_64 module_mono_enabled=yes
scons platform=macos target=template_debug arch=x86_64 module_mono_enabled=yes
scons platform=macos target=template_release arch=arm64 module_mono_enabled=yes
scons platform=macos target=template_debug arch=arm64 module_mono_enabled=yes
lipo -create bin/godot.macos.template_release.x86_64 bin/godot.macos.template_release.arm64 -output bin/godot.macos.template_release.universal
lipo -create bin/godot.macos.template_debug.x86_64 bin/godot.macos.template_debug.arm64 -output bin/godot.macos.template_debug.universal
- You should now have
bin/godot.macos.template_release.universal
andbin/godot.macos.template_debug.universal
. Run these commands: - Now, if you try to export from Godot, it’ll tell you the path where it’s trying to find export templates. For me, it was
~/Library/Application Support/Godot/export_templates/4.3.rc.mono/macos.zip
, so I just created that directory and movedmacos.zip
there. Then, in Godot, “refresh” the UI by clicking a different preset and then clicking “macOS” again, and you should be able to export. - I then got a build error:
The SDK 'Godot.NET.Sdk/4.3.0-rc' specified could not be found.
. This means we need to rerunbuild_assemblies.py
with a local NuGet source (reference).- These steps are pretty quick and don’t require running any other steps; you can just click the “build” button in Godot as soon as
build_assemblies
is done.
- These steps are pretty quick and don’t require running any other steps; you can just click the “build” button in Godot as soon as
Performance
Section titled Performance- If you have lots of collisions going on (e.g. a bullet-dodging game) use circles for all collisions since they’re fast. If something is more rectangular in nature, draw many circles for it.
Collisions
Section titled Collisions- The collision layer is a bitmask of what an object is considered to be. The collision mask is a bitmask of what an object scans. “A contact is detected if object A is in any of the layers that object B scans, or object B is in any layer scanned by object A.” (reference)
- You can give your collision layers friendly names via Project Settings → General → Layer Names → 2D Physics
- To see if the mouse has clicked an
Area2D
(or anything that inherits fromCollisionObject
, e.g.CharacterBody2D
), you need to make sureinput_pickable
istrue
and then connect theinput_event
signal (reference) - For a RigidBody2D to be able to collide physically and also have a function to detect collision, both contact_monitor and contacts_reported must be set correctly (true and >0 respectively).
- [09:22] thesbros: On the rigidbody. https://docs.godotengine.org/en/3.1/classes/class_rigidbody2d.html “contact_monitor must be true and contacts_reported greater than 0.”
- You can generate a CollisionPolygon2D from a sprite automatically (reference)
- Select a sprite in the scene tree
- Click “Sprite” toward the top of the editor
- The result is something like this:
- These may not be performant unless you have very few of them.
- Always, always, always make your collision-handling “parent” like Area2D/KinematicBody2D contain your sprites. The reason why is because the body is what’s detecting collisions, so if it’s a child of a sprite, then you’ll need extra code to move the sprite as the body gets adjusted.
- You can more easily edit the layers by clicking the triple dots so that you don’t have to hover over the graphical representation:
Using with Visual Studio Code
Section titled Using with Visual Studio Code- Some features are a little rough around the edges here, so get used to using the internal editor to fill in any gaps (i.e. become friendly with the F3 shortcut).
- The Visual Studio Code extension that I installed was this one.
- At least on macOS, Godot’s language server uses port 6005 by default, so I changed the corresponding VSCode setting:
"godot_tools.gdscript_lsp_server_port": 6005
- I made a setting for specifying Godot’s executable:
- macOS:
"godot_tools.editor_path": "/Applications/Godot.app/Contents/MacOS/Godot",
- Windows:
"godot_tools.editor_path": "C:\tools\Godot\Godot_v3.2.1-stable_win64.exe",
- macOS:
- At least on macOS, Godot’s language server uses port 6005 by default, so I changed the corresponding VSCode setting:
- In Godot → Editor → Editor Settings… → General → Text Editor → External, choose “Use external editor”. I also set the editor’s path:
- The path is
/Applications/Visual Studio Code.app
on macOS. - Set the Exec Flags based on your editor as mentioned here, that way it’ll open to the right line/column.
- The path is
- After setting this up, you should just be able to click the “script” button to open in Visual Studio Code
- If you want to ignore any linting issues, you’ll need to do something like this on the line above the erroneous code:
- Godot’s editor can add this line for you automatically.
- To debug GDScript, follow these instructions. To debug C#, look at my C# notes for Godot.
- If you’re using an external editor but trying to run through Godot itself, then the breakpoints that you set in the external editor won’t work. Also, Godot itself doesn’t seem to get your updated scripts all that often, so the scripts it does show may be out-of-date.
Navigation (AKA pathfinding)
Section titled Navigation (AKA pathfinding)- This page has some helpful troubleshooting information, e.g. if your agent is jittering or can’t reach its destination.
- You shouldn’t set the
TargetPosition
every single frame. I’m not totally sure how you should make an enemy chase the player if the player is constantly moving. Maybe just request a new path every 0.5 seconds? - If you’re getting stuck on a corner, consider setting PathDesiredDistance and TargetDesiredDistance higher. It’s possible that your agent’s velocity is higher than the desired distance.
- You shouldn’t set the
- Navigation with meshes works by making polygonal regions that indicate walkability. If you want an obstacle, you actually have to cut out a portion of that region. Godot can handle this for you, but it’s not super obvious how, and it’s apparently changing from 4.2 → 4.3, so I’ll just write some quick notes:
- To add an obstacle to a tilemap, you need a
NavigationRegion2D
with its Source Geometry Mode set to something like Group Explicit. By doing this, you can add meshes and static colliders to a named group and they’ll be removed from the nav mesh (that way you don’t need the meshes and colliders to be children of the navigation region). Just keep in mind that the colliders need to beStaticBody2D
instances, notRigidBody2D
; the point is that they’re not allowed to move since moving them would require rebaking the mesh. TheStaticBody2D
can be anywhere in the scene tree, but it needs a global group added to it. The parents of theStaticBody2D
don’t need that label.
- To add an obstacle to a tilemap, you need a
- Keep in mind that navigation is not the same as collision. This means that if your character is too close to walls, for example, that collision shapes won’t change anything.
Scene inheritance
Section titled Scene inheritanceSometimes, you’ll have a scenario like this:
- You have a scene that has the structure that you want
- You want to derive children based on that scene that have different scripts
In that case, you can use scene inheritance. Just make a new scene and instantiate a scene into it without having a root node:
As you can see, the nodes in that scene turn yellow, and hovering over the root shows what the scene inherits from.
Another indicator that this is the pattern you want is if you find yourself trying to call SetScript
and casting the type of the scene to something else.
Note that there is no such thing as an abstract scene. If you want this in practice, you can make a script called Abstract.cs
and just throw an Exception
from its _Ready
function (you may potentially need to check the class name at runtime if the base class needs its _Ready
function for initialization).
Tilemaps (reference)
Section titled Tilemaps (reference)Basic tilemap usage
Section titled Basic tilemap usage- Pro-tip: use “Distraction Free Mode” when working with tilemaps so that you can see more! It’s the “expand” icon at the upper right of the editor.
- To set one-way (i.e. directional) collision on a tile map, see these instructions.
- As of Godot 4, you can use one tilemap with many layers, e.g. for background, foreground, etc. (reference). Just modify the “Layers” property of the TileMap in the Inspector, then choose the layer you want to draw to using the TileMap tool at the bottom of the editor.
- Tilemap steps (reference)
- Make a new scene
- Ctrl+A → TileMap
- In the Inspector for the TileMap
- Set cell properties to the tilesize
- Note: this does not customize the grid itself that shows in purple-ish in the picture below:
- Set cell properties to the tilesize
-
As you can see, the tiles don’t align with the grid itself. To configure that, you first have to select a tile (click “new single tile”, click the magnet to enable snapping, then drag any selection). At that point, the inspector will show you the snap options where you can configure the step size (reference).
-
Make a new TileSet
-
Edit the TileSet. You’ll see the bottom panel change.
-
Drag your spritesheet into the left side of the bottom panel
-
Click “New Single Tile” and the magnet to snap to the grid, then click a tile.
-
Name your tile in the right side if you want. This is really important because at least at the time of writing, it’s really hard to tell what you’re editing.
-
At this point, if you click your TileMap node in the scene view and then start drawing tiles using the panel on the right
- “New Atlas” is how you can use the whole spritesheet as tiles.
-
Note: you can only set a collision layer at the level of a whole TileMap, not for individual tiles.
Autotiles
Section titled Autotiles- Click “New Autotile” and drag a contiguous region around your tiles
- Click “Bitmask” and indicate which quadrants are “filled” (see “bitmask explanation” for an explanation):
- It’s possible to use a 3x3 grid for more control. You’ll find it in the Inspector → Selected Tile → Autotile Bitmask (reference):
- Here’s an example of a 3x3 (minimal), although I probably didn’t fill everything in correctly:
If you need to “expand” your input graphic so that all of the connections are covered (e.g. you only have ~16 tiles and need ~48), you can use Webtyler.
Bitmask explanation
Section titled Bitmask explanationRemember, they’re not magic! Godot is just connecting shapes that it can match from the bitmasks that you set up. I hastily annotated a small map with red squares to represent the bitmasks:
Suppose we never set up a bitmask for this ”+” shape that you see in the bottom middle:
The map would go from this:
…to this:
(it’s subtle, but you can see the black pixels at the corners of the middle tile have disappeared since Godot no longer found a ”+” connector)
Comparison to other game engines (reference)
Section titled Comparison to other game engines (reference)- Godot is…
- Free
- Open-source
- Godot’s design philosophy from a coding standpoint is different from other editors (reference)
Camera zooming to the mouse
Section titled Camera zooming to the mouseGoal: get zooming to work like it does in Google Maps where the camera zooms on the mouse cursor. Normally, cameras in Godot zoom on their center, not some target’s center.
The theory behind this is that you want to perform the following steps:
- Center the camera on the mouse, that way the mouse’s position and the camera’s center are the same.
- Zoom in.
- Move the camera so that it’s centered on the mouse’s screen coordinates. Remember: the mouse won’t have moved during this time.
Gotchas:
- Make sure you’re not binding this to both the key up and down of an input, or this won’t work. E.g. I had code just checking for
MOUSE_BUTTON_WHEEL_UP
, which caused two events to be fired per frame. Presumably Godot didn’t run its own internal update of a transform somewhere, so the correct zoom/positioning code on my end led to incorrect results. - This method used to work in Godot 3, but it doesn’t work in Godot 4 and I can’t figure out why. Godot inverted how zooming works (in Godot 3, bigger numbers meant you were zooming out), but I still couldn’t get it to work even with that in mind.
Sprites / spritesheets
Section titled Sprites / spritesheets- Spritesheets can be done by just loading a particular region of a sprite. This can be done through the “Region” section of the Inspector or via code.
Testing
Section titled TestingThere’s been an issue open since 2020 about testing in Godot (at least for C#). In general, these are true as of today:
- If you want to test something that involves only C#, just use any unit-testing library
- If you want to test something that involves any Godot-specific code, you need Godot to be running.
- There are lots of options for this sort of thing—some that run in Godot itself as a plugin (e.g. gdUnit4), some that run outside of Godot or in VSCode.
- I decided to use the Chickensoft stuff from here for my C# game.
- On macOS, I hit this issue where the engine crashes after running the code from the command line:
/Applications/Godot_mono.app/Contents/MacOS/Godot --run-tests --quit-on-finish --path /Users/adam/tmp/test_chickens/godotgame/MyGameName
- I can work around that by getting rid of
--quit-on-finish
and just manually quitting. - It may be a screen-recording issue? I only noticed that a dialog had popped up when I went to reboot my machine, so I didn’t get to test this out.
- I can work around that by getting rid of
UI (user interface)
Section titled UI (user interface)Positioning labels or other UI through code
Section titled Positioning labels or other UI through codeMaking a Label
and setting its text doesn’t immediately update its size (reference). Call get_combined_minimum_size()
instead of size
, and if that still doesn’t work (e.g. the CanvasItem
was only just shown in the scene tree), then wait for process_frame
, e.g. await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
(reference).
Notes:
- The reason why you have to get the combined minimum size is because
MinimumSize
can report a size a smaller thanCustomMinimumSize
, soCombinedMinimumSize
is the larger of the two, which is the actual minimum size. I don’t understand whatMinimumSize
actually represents (the documentation says it’s the “internal minimum size”). - Invisible nodes may not ever have their proper sizes reported, in which case you would have to make them visible first.
Coloring button backgrounds
Section titled Coloring button backgroundsGo to Theme Overrides → Styles → Normal → Make a StyleBoxFlat
To do this through GDScript (reference) (WARNING: this may cause Godot to freeze/crash on exit due to this issue (which should be fixed by this PR, but if not, consider keeping references to styleboxes from GDScript)):
Screen coordinates
Section titled Screen coordinatesWhen working with 2D scenes, the UI isn’t just automatically placed in screen coordinates. I believe this is what you’d use a CanvasLayer
for (reference). follow_viewport_enabled
should be off in that case.
Theme editor
Section titled Theme editorIf you want many components in a UI to use the same theme, make a theme in the inspector, then modify it using the “Theme” button at the bottom of the editor:
To use this:
- Start by clicking the ➕ button at the upper right and selecting something like “Button”.
- From there, you can customize all of the shared properties of a button. This still isn’t the most straightforward though.
- Click the ➕ button to the left of one of the style boxes to add a new one
- Select “New StyleBoxFlat”
- Click the StyleBoxFlat that you just made to change the inspector to that style box
You can apply a theme to a control node and it will be inherited by all children.
Also, you can apply a default theme across your whole project using Project settings → GUI → Theme → Custom (it’s literally just the name “Custom” for right now).
Hotkeys
Section titled Hotkeys- F1-F3: select different workspaces (2D, 3D, Script)
- Shift+F1: access documentation directly in the game
- Ctrl+A: add node
- After adding the node, pressing Enter on it will rename it.
- F5: play game (this starts with the default scene, which is configured in Project settings → Application → Run → Main scene)
- F6: play scene
- F8: stop scene
- Ctrl+D: duplicate node
- Ctrl+click (a line in a script): bring up references in the editor directly
- Ctrl+shift+O: quick-open scene
- Ctrl+alt+O: quick-open script
Troubleshooting
Section titled TroubleshootingFinding orphaned nodes
Section titled Finding orphaned nodesOrphaned nodes are just nodes that were created but do not exist in the scene tree, meaning Godot won’t know when to free them (even in C# when their last reference is removed). Orphans on their own aren’t a problem; maybe you want a pool of nodes in memory that you can reuse. However, when you don’t want them, you can use PrintOrphanNodes()
to find them. This will print something like this, so make sure to name your nodes!
To even know whether you have orphans, you can look at the monitor:
Jittery camera movement when hitting boundaries
Section titled Jittery camera movement when hitting boundariesI ran into an issue when making Skeleseller where I put camera-update code in _PhysicsProcess
instead of _Process
. As a result, people with high-refresh-rate monitors ran into issues with panning to the sides of the map or zooming in and out to the maximum extents.
I just needed to use the correct _Process
method to accommodate refresh rates.
Printing stops working (reference, reference2)
Section titled Printing stops working (reference, reference2)I don’t know what caused this, but I had to restart the editor to fix it. Don’t just assume that print(“hit this code”) isn’t being hit just because you don’t see the log.
The reference links suggest that you may have disabled stdout through the project settings.
ViewportContainer not rendering children until manually modifying a property
Section titled ViewportContainer not rendering children until manually modifying a propertyThis happened to me when I had this code:
The problem is that I was adding the children to the ViewportContainer after adding the ViewportContainer to the scene tree. I just put the first line at the end and it worked.
Lines are appearing between tiles in a tilemap (reference)
Section titled Lines are appearing between tiles in a tilemap (reference)E.g. this:
Just enable “Use Pixel Snap” in the Project Settings to fix this.
Note that if you’re only seeing this for an HTML export, then you likely have to use GLES 3.0 instead of 2.0.
Sprites bleed outside of their boundaries in a spritesheet (reference)
Section titled Sprites bleed outside of their boundaries in a spritesheet (reference)E.g. this:
To fix this, click your spritesheet in the FileSystem tab, then at the upper left next to “Scene”, you’ll see “Import”. Click “Import”, turn “Filter” off, then click “Reimport”. UPDATE: it’s now in Project Settings → Default Texture Filter
It’s also possible that you have a SubViewport
that needs snap_2d_transforms_to_pixel
set to true
(for whatever reason, it doesn’t seem to respect the project-wide setting). At least in my case, that particular issue was caused by resizing the game window on macOS despite not having any scaling involved. It’s possible that it’s this known issue.
Text within a SubViewport
looks bad
Section titled Text within a SubViewport looks badI never found a good solution for this. A solution is to put any text into a viewport that does look good, but this is quite unwieldy. For example, imagine you have a character whose name should float above their head in world space. You would need to move that label to another viewport entirely, which means putting it in a different scene and then somehow linking the character scene with its name scene.
Lag on macOS
Section titled Lag on macOSEspecially when trying to export builds, I get a lot of lag. It seems like the Mobile renderer may help with that (reference).
peer > 0 && !connected_peers.has(peer)
(reference)
Section titled peer > 0 && !connected_peers.has(peer) (reference)Full error:
This apparently isn’t an error; it’s just a warning saying that we got an update for an object that no longer exists (or something like that).
Tileset has unusable, darkened tiles
Section titled Tileset has unusable, darkened tilesIn this screenshot below, see how some of the bones are darkened? They can’t actually be placed as tiles because they’re not tiles.
The solution is to click the three dots highlighted above in red and select “Create Tiles in Non-Transparent Texture Regions”
Tileset has tiles outside of the texture area
Section titled Tileset has tiles outside of the texture areaThe problem looks something like this:
…where you have orange squares indicating tiles and they’re outside of the texture region. This can happen when you set a TileSet texture, have Godot automatically create tiles (see below):
…and then enlarge the Texture Region Size, which will bump the automatically created tiles off of the texture.
To fix this, you could either manually erase tiles or choose this option:
The problem is that the option above isn’t enabled by default. To fix this, reload the scene (Scene → Reload Saved Scene).
GetCamera2D
(kw: get_camera_2d
) returns null
Section titled GetCamera2D (kw: get_camera_2d) returns nullE.g.
When this happened to me, I was doing GetTree().Root.GetCamera2D()
, and it was returning null
. It turns out that it’s because I had a scene tree like this:
- Root
- SubViewportContainer
- SubViewport
- Camera2D
- ElementWithAScriptTryingToGetTheCamera
- SubViewport
- SubViewportContainer
The problem is that my active camera is in a SubViewport
, but GetTree().Root
will return the rootmost node, not your SubViewport
. To fix this, just call GetViewport().GetCamera2D()
, and it’ll get the nearest viewport ancestor.