Skip to content

Godot Engine

Created: 2020-04-21 08:38:17 -0700 Modified: 2020-07-12 21:44:10 -0700

  • 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
  • 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
  • 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).

  • 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.
  • 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 from CollisionObject, e.g. CharacterBody2D), you need to make sure input_pickable is true and then connect the input_event signal (reference)
    var area2d: Area2D = $Area2D
    area2d.input_event.connect(self._on_input_event)
    func _on_input_event(viewport: Viewport, event: InputEvent, _shape_idx: int) -> void:
    if event is InputEventMouseButton:
    var mouse_event: InputEventMouseButton = event
    if mouse_event.pressed and mouse_event.button_index == MOUSE_BUTTON_LEFT:
    # do something here
    viewport.set_input_as_handled()
  • 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).
  • 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:

  • 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",
  • 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.
  • 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:
    # warning-ignore:return_value_discarded
    • 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.
  • 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:

  • 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.

  • 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:

Remember, 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)

  • Godot is…
    • Free
    • Open-source
  • Godot’s design philosophy from a coding standpoint is different from other editors (reference)

Goal: 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.
# (this code all goes in a Camera2D node. Make sure you set anchor = Camera2D.ANCHOR_MODE_DRAG_CENTER)
# Center camera on mouse
global_position += get_local_mouse_position()
# Zoom now that we have our new center
zoom = new_zoom_value
# Move the camera back to where the mouse used to be
global_position -= get_local_mouse_position()

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.
  • 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.

func _ready():
set_region(true)
set_region_rect(Rect2(0,64,32,32))

There’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.

Positioning labels or other UI through code

Section titled Positioning labels or other UI through code

Making a Label and setting its text doesn’t immediately update its size (reference). Call get_minimum_size() instead of size.

Go to Theme Overrides → Styles → Normal → Make a StyleBoxFlat

To do this through GDScript (reference):

var new_stylebox_normal: StyleboxFlat = $MyButton.get_theme_stylebox("normal").duplicate()
new_stylebox_normal.bg_color = Color.DARK_GREEN
$MyButton.add_theme_stylebox_override("normal", new_stylebox_normal)
# Later, you can remove the stylebox override:
$MyButton.remove_theme_stylebox_override("normal")

When 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.

If 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: 500

To use this: 500

  • 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.

  • 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

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 property

This happened to me when I had this code:

add_child(viewport_container)
viewport_container.add_child(viewport)
viewport.add_child(gameplay_scene)

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.

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.

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

Especially when trying to export builds, I get a lot of lag. It seems like the Mobile renderer may help with that (reference).

Full error:

E 0:01:19:0818 _process_sys: Condition "peer > 0 && !connected_peers.has(peer)" is true.
<C++ Source> modules/multiplayer/scene_multiplayer.cpp:317 @ _process_sys()

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 tiles

In this screenshot below, see how some of the bones are darkened? They can’t actually be placed as tiles because they’re not tiles. 600

The solution is to click the three dots highlighted above in red and select “Create Tiles in Non-Transparent Texture Regions” Pasted image 20240320090629.png

Tileset has tiles outside of the texture area

Section titled Tileset has tiles outside of the texture area

The problem looks something like this: 500

…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): 500

…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: Pasted image 20240320092550.png

The problem is that the option above isn’t enabled by default. To fix this, reload the scene (Scene → Reload Saved Scene).

See this; you generally just need to set the stretch mode to canvas_items in the project settings.