GODOT: Learning from the Tutorials

Stuff that I learned from the Godot Step-by-Step starter tutorials. I’m hoping I can shorten someone else’s journey (plus this is how I document stuff).

A Godot Game is a tree of Scenes (*.tscn files from the project explorer) and Scenes are made up of Nodes (the primitive types in a Godot game).

In the 2D starter tutorial we made four scenes:

  • Main.tscn
  • Player.tscn
  • Mob.tscn
  • HUD.tscn

You can see from the list of scenes (below) that the logical grouping of a Main controller scene – a Player and a Mob (Bad Guys) scene and the HUD scene is split out. The Main scene is the starting point for a Godot game. Scenes can be instantiated (many times as objects within another Scene – and edited independently – or all objects can be changed globally by editing the *.tscn) .

As you can see above – each Scene had a related Godot Script file (*.gd) that holds the code for that Scene (more on that later).

Scenes are your basic building block and design element. For each component in the game – make a Scene.

Scenes are a tree of Nodes. For example, the Scene for the Player uses the following Nodes: a CharacterBody2D node, a Sprite2D node, a Camera2D node, and a CollisionShape2D node.

Nodes have Properties and can receive callbacks to update every frame. They can be extended with new properties (and functions). They can be nested under another node as a child (the tree structure).

Properties can be edited in the Inspector Window to the Right of the Godot Editor. The Inspector displays a node’s properties in “Title Case” which are available to GDScript code (As lowercase – with words separated by an underscore – hover over Properties in the editor to check them).

Example: a script on a 2D Sprite Character that starts with:

extends Sprite2D

Which grants access to all the inherited properties of a 2D Sprite visible in the Inspector – which means you can write:

var texture = load("res://icon.svg")
$Sprite2D.texture = texture

Doing Stuff

Game Window Size Settings

Click on Project  -> Project Settings to open the project settings window and in the left column, open the Display Window tab. There, set “Viewport Width” to 480 and “Viewport Height” to 720.

Player Input

There are two main ways to process input :

  1. The built-in input callbacks, mainly _unhandled_input(). Like _process(), is a built-in virtual function that is called every time there is a keypress. Use if if you need to react to events that don’t happen every frame (like hitting Space to jump). To learn more about input callbacks, see Using InputEvent.
  2. The Input singleton. A singleton is a globally accessible object. Godot provides access to several in scripts. It’s the right tool to check for input every frame.

We use the Input singleton here as we need to know if the player wants to turn or move every frame.

Managing input is usually complex, no matter the OS or platform. To ease this a little, a special built-in type is provided, InputEvent. This datatype can be configured to contain several types of input events. Input events travel through the engine and can be received in multiple locations, depending on the purpose.

Here is a quick example, closing your game if the escape key is hit:

func _unhandled_input(event):
    if event is InputEventKey:
        if event.pressed and event.keycode == KEY_ESCAPE:
            get_tree().quit()

However, it is cleaner and more flexible to use the provided InputMap feature, which allows you to define input actions and assign them different keys. This way, you can define multiple keys for the same action (e.g. the keyboard escape key and the start button on a gamepad). You can then more easily change this mapping in the project settings without updating your code, and even build a key mapping feature on top of it to allow your game to change the key mapping at runtime!

You can set up your InputMap under Project > Project Settings > Input Map and then use those actions like this:

func _process(delta):
    if Input.is_action_pressed("ui_right"):
        # Move right.

Animations

Adding animations to 2D Animation Node is a little non-obvious.

To create one, find the Sprite Frames property under the Animation tab in the Inspector and click “[empty]” -> “New SpriteFrames”. Click again to open the “SpriteFrames” panel: (Note that there are two tabs sitting next to each other there in the panel – both called Sprite Frames – which is kind of like an expanded info box for the first Sprite Frames tab – doesn’t look obvious but it is when you know it’s there).

On the left is a list of animations. Click the “default” one and rename it to “up”.

The Animation Sprite Pane is called “Sprite Frames” down the bottom (in blue) – (it’s easy to lose it if you click on something else and then go looking for it in the obvious Animation footer): Add sprites by clicking on the little “folder” icon (next to the play/pause strip).

Anti-Aliasing

I was a bit disappointed with how scenes looked when I started playing with my own demo scenes but once I found the Anti-Aliasing settings I got more comfortable. Under Project -> Project Settings.

The difference between this:

and this:

GDScript

GDScript is an indent-based language – which is really annoying. I make tabs and spaces by muscle memory all the time when coding and it bites me in the ass. Cutting and Pasting code is also fraught with danger. But annoyances aside I do really like it as a scripting language – it’s simple, well designed (in that interfaces are consistent and logical) and does what you want with a minimum of fuss.

The only other gripe is that there are a lot of setting strings for references in the editor so you need to be careful when using them in code that you spell them correctly and are aware that they are case sensitive. For example you may have set an animation called “funnyWalk1” in the editor so when you code it in GDScript you need to use something like below:

if velocity.x != 0:
    $AnimatedSprite2D.animation = "funnyWalk1"

It would be nice if there was some kind of style checker or linter that could roll over your code for strings and do a fuzzy match against the instances in the editor and tell you when you used FunnyWalk1 or funnywalk1 or funnywolk1 or funmyWalk1 etc.

Inspector Access

Using the export keyword on the first variable speed allows us to set its value in the Inspector

@export var speed = 400 # How fast the player will move (pixels/sec).

Functions

func _process(delta):
    rotation += angular_speed * delta

Here, rotation is a property inherited from the class Node2D, which Sprite2D extends. It controls the rotation of our node and works with radians.

The func keyword defines a new function. After it, we have to write the function’s name and arguments it takes in parentheses. A colon ends the definition, and the indented blocks that follow are the function’s content or instructions.

Note: Notice how _process(), like _init(), starts with a leading underscore. These are Godot’s built-in functions you can override to communicate with the engine.

_process() is called every frame (it’s the equivalent of Update() in Unity).

I have my Snippets of GD Script here: https://github.com/zuluonezero/GodotSnippets

Differences Between Godot 3 and 4.

Spacial renamed to Node3d in v4.

Godot 4.0: nonexistent function “instance” in base PackedScene Instance is Godot 3, on 4 is instantiate()

PathFollow, offset (the path length moved from the beginning in distance units). PathFollow2D, you’ll see that it no longer has offset as an attribute – the new name is “progress”.

V3 has linear_interpolation in V4 it has been replaced with lerp.

Feel free to Comment