William Liu

Godot

Look up “Godot Tutorials” on YouTube for the basic tutorial series.

Inheritance vs Composition

Inheritance

In a world of Inheritance, we might have:

Player Object

Enemy Object

With Inheritance, we can create a parent Entity that has:

This works until we run into something like a ‘Tree’

Composition

We can have:

Player

Enemy

Tree

Game Loop (Over simplified)

Keep in mind your game will run at say 60 Frames Per Second so don’t run big for loops, complex calculations in your script lifecycles.

While(userNotQuitOrCrash): updatePhysics() Scene.updateScenePicture() # Update images you’re shown passToNodeScripts(userInput) performanceUpdate() # Most game engines will handle this for you userNotQuitOrCrash = updateAppState()

Look at the MainLoop.cpp and Main.cpp file for details on this game loop.

Nodes and Resources

Top main classes are ‘Nodes’ and ‘Resources’

Nodes

Node classes are functionality and behavior.

Example Nodes

Resources

In the FileSystem (bottom left default), you can create a new ‘Resource’ class. A resource is saved to the disk (file system).

Example Resources

Script Lifecycle

Our game objects go through a life cycle of:

Lifecycle details:

  1. “Enter Tree” - A scence is loaded from disk or created by a script
  2. “Ready” - The root node of the newly instantiated scene is added as a child of the root viewport or to any child of it
  3. “Player Input” - Every node of the newly added scene will receive the enter_tree() notification in top to bottom order
  4. “Physics Processing” - The ready() notification is called when a node and all its children are inside the active scence
  5. “Process” - While the object is active in the scence, callbacks such as the _input(), _process(), _physics_process() are called if used
  6. “Exit Tree” - When a scene is removed, they receive the exit scene notification in bottom to top order So all the children will exist scene before the root node can exit

Virtual Methods

Virtual Methods allow subclasses of the base class type that has the virtual method to override the method. If there is virtual, Godot will handle virtual method calls if they are overridden. For example, a Node class has the following methods:

Node Methods

We can override these following virtual methods

_enter_tree()

The _enter_tree() method is always called when a scene (gameobject) enters the SceneTree. The notification is Top-to-Bottom order.

How _enter_tree() plays:

root
  Parent
    Child1
      GrandChild1
    Child2

the order will be:

Before our scene is loaded, it’ll be an inactive Node to an active Node.

E.g.

      SceneTree
          |
      Root Viewport
          |
Inactive: Node -> Active: Node (`_enter_tree()` is when a Node becomes Active)

When do you need to use _enter_tree()?

_ready() method

Example

Root
  Child1
    GrandChild1
      GreatGrandChild1
    AnotherGrandChild1
  Child2
    GrandChild2

will call:

GreatGrandChild1 _ready() method is called
GrandChild1 _ready() method is called
AnotherGrandChild1 _ready() method is called
Child1 _ready() method is called
GrandChild2 _ready() method is called
Child2 _ready() method is called
Root _ready() method is called

Does the Root have children? If so, go to Child. If not, did the _ready() method get called?

Note: Just because a node has been deleted/set to inactive (no longer there in the SceneTree), it doesn’t mean it’s been removed from memory.

When to use?

Delta Time w/ _process()

Delta Time (elapsed time) is a concepted used by programmers in relation to hardware and network responsiveness. In Godot, delta time is a float value. For game programming, we refer to the elapsed time between the last frame that was drawn and the current frame that is drawn.

Issues between different computer speeds:

F1 -> delta time -> F2 -> delta time -> F3 -> delta time -> F4

Say we have a game running 4FPS and one with 60FPS.

We have a framerate dependent calculation

We can have a framerate independent by using delta time

Godot gives you two virtual methods to handle delta time:

_process(delta):
    pass

_physics_process(delta):
    pass

_process(delta) method

When to use:

When not to use:

Never remove vsync (default on); if you do, then our _process(delta) method is called as quickly as possible and we will not be frame independent anymore.

_process_physics(delta) method

When to use:

When not to use:

physics vs physics_process opinions

Player Input

Player Input includes:

The easiest way for Godot to handle keyboard input is through the Application’s “Input Map Tab”

4 common input virtual methods from Node Class:

How does this fit in?

User Input -> OS -> Scene Tree -> Root ViewPort ->
  1. Input
  2. Control Node (GUI Input)
  3. Unhandled Input

Input works Bottom to Top order with by first looking to see if the _input(event) method was called on any of the nodes, then bottom to top again on _gui_input(event), then _unhandled_input(event), etc.

Just keep in mind you have to exit the InputEvent otherwise the input event will be passed along.

Control Class

Control < CanvasItem < Node < Object

func _unhandled_input(InputEvent event):

So what does this mean?

You don’t want player input at higher levels (e.g. _input, _gui_input) because you want priority to go to say “Pausing the Game”. You don’t want a player to shoot their gun while the game is paused.

func_unhandled_key_input(event):

This will only get called if a key press was/is detected (mouse movement does not activate this function). Consider using this for input dealing with key presses.

CollisionObject._input_event()

Viewport

InputEvent Class

Example code:

func _input(event):
    if event is InputEventKey:
        if event.scancode == KEY_RIGHT:
            print("Right key pressed")
    else:
        print(event)

_exit_tree()

All code logic inside the _exit_tree() virtual method gets called when the node is removed from the scene tree.

Code:

func _exit_tree():
    # recommended below - doesn't do anything until you add or remove nodes
    # self.queue_free()
    pass

# Notice it takes no arguments and returns back no value (void)

The easiest thing to do as a beginner is to delete the node from memory after it has left the scene tree. Recommended to use queue_free() inside the _exit_tree() method for all Nodes because memory management is complex. Best not to have memory leaks and just delete everything that leaves the scene tree.

Two ways of deleting from memory

Adding and Removing Nodes from Scene Tree

Remember that removing an Object/Node from the Scene Tree does not remove the Node from Memory. You need to use Node.queue_free() or Object.free(). When a node is removed from the scene it is an Orphaned Node, but not deleted from Memory.

add_child(<node_object>):
    pass

remove_child(<node_object>):
    pass

Memory Management

You can use a technique called Object Pooling for handling memory performance, which is more performant than Node.queue_free(). The theory is you have an Active node (e.g. a sprite node) that is on a Scene, then we create a new variable in another node and point this new variable to the old node. We then remove our Active node from Scene and can restore from the variable. This is a more advanced technique (again, beginners should just use Node.queue_free() for now).

Pivot Point / Offsets

A Pivot Point (aka Offset) is the reference point (a pair value in an x,y coordinate system) for positioning, rotating, and scaling a game object. A pivot point is where the transformation portion of our Node object gets affected at. We want our pivot point in the center of our images, which makes boundary calculations easy to do.

We can create a complete image using Skeletal Animation/Rigging instead of a Spritesheet.

Window Basics

Resolution is pixels in each direction (width x height) with an Aspect Ratio of 16:9 (x 120), the ratio of width to its height

For game programming, there are two different window values we can retrieve:

  1. Player Device Screen/Resolution (e.g. laptop, monitor, cellphone)
  2. Game Window / Resolution Size (our game), where we draw our images

Godot offers a global OS singleton class

1. Player Device Screen/Resolution
# getter method only
screen = OS.get_screen_size()

# returns a Vector2(float x, float y)

2. Game View Resolution
# setter
vector2 = Vector2(float x, float y)
OS.set_window_size(vector2)

# Getter returns Vectors2
game = OS.get_window_size()

Window Position

Game Window offset is top left

vector2 = Vector2(float x, float y)
OS.set_window_position(vector2)
position = OS.get_window_position()

Window Resizing (Default: true)

# getter method
OS.is_window_resizable()  # returns boolean

# setter method (if you don't want user to resize
OS.set_window_resizable(false)

Full Screen or Borderless Window

OS.set_window_fullscreen(true)

OS.set_borderless_window(true)  # unable to move

OS Orientation

If you develop for mobile, pick landscape (Godot default) OR portrait.

SCREEN_ORIENTATION_LANDSCAPE = 0
SCREEN_ORIENTATION_PORTRAIT = 1

Built-in Scaling

Godot offers us a way through project settings to handle built-in scaling for us under

Project Setting > Display > Window > Stretch (One Size Fits All Solution)

Recommended Starting Settings:

For mobile game dev, would recommend a mix of one size fits all method in addition to scaling for individual images

Before applying scaling, pick a Static Game Window Size Width during Development (e.g. 1920 x 1080). Images need to have a width and height that can fit your game without scaling

Calculations:

New Resolution / Original Resolution = Conversion New Ratio = Conversion * Original Ratio

e.g. Resolution: 1920 x 1080 = Image Ratio 1:1 Resolution: 100 x 100 = Image Ratio 0.052: 0.052 for X aspect change (100/1920 = 0.052), 0.092: 0.092 for Y aspect change (100/1080 = 0.092)

Images

Summary

CanvasItem Class

CanvasItem Class(Visibility)

Sprite Class

Properties:

Texture Class

Methods:

Image Class

Methods:

You do not want to edit and save images directly through the Godot editor (use an image editing software)

Coding Notes

E.g.

extends Sprite

func _enter_tree():
    myCustomCode()

func _physics_process(_delta):
    myCustomCode()

func myCustomCode() -> void:
    pass

Collision

Collision Shapes

CollisionShape2D

The first property Shape tells us what kind of Shape this collision object is, e.g. RectangleShape2D. The simpler the shape, the more performant your game will be (Simpler Shapes > Polygons) If multiple CollisionShape2D objects are “ON”, Godot uses the UNION of all of those objects

Allows you to pick:

CollisionShape2DPolygon

Easier to work with, especially if your polygon changes dynamically over time More expensive, but allows more detailed shapes (i.e. more Vertices/edges). Basically the same as CollisionShape2D with two extra properties:

PhysicsBody2D

Godot gives us the options of:

StaticBody2D

RigidBody2D

KinimaticBody2D

Methods:

Randomness

Two categories:

Input Randomness

Output Randomness

True Random Numbers vs Pseudorandom Number

True Random Numbers are hard; uses a measurement/algorithm system Pseudorandom is given to us by a RandomNumberGenerator Class with a default seed

Functions

Timers

Can have count up timers (e.g. speed run), count down timers (e.g. pass a level in X time), refresh timer for skills, new monsters spawning. We can write with custom code or use a Timer node.

raw code

Use raw code when you want to do something other than counting down.

Example basic raw code for a timer

var timeCounter: float = 0.0
var timeLeft: int = 10

func _physics_process(delta) -> void:
    timeCounter += delta
    if( int(timeCounter) == 1):
        timeLeft -= 1
        timeCounter = 0
        print(timeLeft)

Timer node

Properties:

Methods:

How to put it together:

  1. Add a Time Node to the Scene Tree
  2. Connect your function call to the timeout() signal method
  3. Call the start(float time) method By default, will restart again

Code Example:

extends Timer

func _read() -> void:
    wait_time = 3.0
    connect("timeout", self, "printHello")
    start()

func printHello():
    print("Hello World")

CanvasItem Class

Methods

How do I draw things on the Screen with CanvasItem?

Camera2D Node

Methods

Control Node

Basics:

Viewport

General Setup of Viewports

Methods:

Changing Scenes

Example of changing Scenes: Splash Screen -> Main Menu -> Game -> Credits

You can change scenes 3 ways:

Changing Scene Functionality

These will delete the current scene immediately when called.

SceneTree Global Class

change_scene()

Code:

get_tree().change_scene(String path)

var sceneTwo:String="res://Scene2.tscn"
get_tree().change_scene(sceneTwo)

change_scene_to()

Code:

get_tree().change_scene_to(PackedScene packed_scene)
var sceneTwo = preload("res://Scene2.tscn")
get_tree().change_scene_to(sceneTwo)

Error Enums

If changing a scene runs into an error, you’ll run into these Error Enum values:

Buttons

Buttons are needed to have a pressable item so we can create:

States of Clickable UI (General)

UI can have the following states:

Button Node

Button < BaseButton < Control < CanvasItem < Node < Object

Comes with 5 states:

If we attach a script, we can get the following methods and properties:

The Toggle Mode lets you toggle between staying pressed or popping back to a default state

Button Signals:

TextureButton Node

Great for amazing looking buttons

TextureButton < BaseButton < Control < CanvasItem < Node < Object

5 states:

Persistent Data

Code:

Say you have a Singleton with:


#PlayerStats.gd
var level = 1
var player_name = "Will"

#You can access this globally through other scripts with
PlayerStats.player_name

Steps:

  1. Open File
  2. Open Stream Type (there’s different types, including Input Stream, Output Stream, Input/Output Stream)
  3. Read/Write File # depends on the stream
  4. Close File

How to save:

Paths: