Godot - GDScript

Prints and Debugs in terminal
  • Prints the elements 'text_1' and 'text_2', separated by a 'space' between them:

    • prints('text_1', 'text_2')

  • Prints the elements 'text_1' and 'text_2' separated by 'tab':

    • printt('text_1', 'text_2')

  • Prints 'text' as an 'Error' message, with red highlight, printed in the Output area: (the push_error()  sounds more correct, as it references the lines of the 'error'.)

    • printerr('text')

  • Prints 'text' as an 'Error' message, with red highlight and Engine indicators, printed in the Debugger area:

    • push_error('text')

  • Prints 'text' as a 'Warning' message, with yellow highlight and Engine indicators, printed in the Debugger area:

    • push_warning('text')

Useful code for debug with prints
var count := 0

func _process(_delta) -> void:
    count += 1
    if !count % 30:  ## will print once every 30 frames.
        print()
Skip a function or operation without crashing ('pass')
if true:
    pass

func set_direction():
    pass
Line break
if 2 == 2 \
    and x != y \
    and "psps" > "cat":
    x = y
    emit_signal("succeeded")
My configs
  • Project Settings  -> Advanced Settings  -> debug/gdscript/warnings/ .

  • Unused Parameter.

    • Ignore

    • Reasonable to ignore since adding _  each time while prototyping is tedious.

  • Unused Signal.

    • Ignore

    • Important for Global Signals.

  • Untyped Declaration.

    • Warn

  • Unsafe Property Access.

    • Warn

  • Unsafe Method Access.

    • Warn

  • Unsafe Cast.

    • Warn

    • Unknown effect.

  • Unsafe Call Argument.

    • Warn

  • Int as Enum without Cast

    • Ignore

    • Annoying warning when testing enums.

Operations

Logical Operations
  • Negation:

    • if my_node is not Node3D:

    • or

    • if not my_node is Node3D:

Ternary Operations
  • Documentation .

  • var slowness = 0 if current_effects["Slowness"] == null else current_effects["Slowness"].value

  • variable = (1 - (0 if (current_effects["Slowness"] == null) else (current_effects["Slowness"].value) / 100.0))

    • Be careful in this part: '(1 - (0'! Important to separate what belongs to IF and what does not!

  • print("greater than zero" if (counter > 0) else "less than or equal to zero")

Bitwise Operations
  • OR | : "the result is an append".

  • XOR ^ : "the result is a de-append"

  • AND & : "the result is the intersection"

  • Define an Enum:

enum effect {
    SLOWNESS = 1
    STUN = 2
    SILENCE = 4
    PUSH = 8
    POISON = 16
    #...    
}
  • Boolean evaluations:

(a & b): ## True if there is an intersection between a and b.
()
  • Operations:

a |= b   # append
a ^= b   # (?) de-append
a &= ~b  # remove 'b' from 'a'

Strings

String formatting
  • Formatting:

var elapsed_time = float(Time.get_ticks_msec() - start_time_ms) / 1000.0
time.set_text("%0.2f seconds" % elapsed_time)

Arrays

Map array
points.assign(points.map(func(point : Vector2i) -> Vector2i:
    return point + Vector2i(-1, 0)
    ))
  • Map:

    • Performs mapping and returns the mapping result.

    • Return is always of type Array .

  • Assign:

    • Takes all elements from an array and puts them into itself, converting types.

      • In this case, converts the return of map  to Array[Vector2i] , since points : Array[Vector2i] .

    • Assign seems to clear the current array, but it's strange.

Functions

Methods

Get Method List
  • Generally the SCRIPT has fewer  methods than the NODE.

  • Sometimes you can use the SCRIPT for a faster lookup, but sometimes the desired functions only appear on the NODE.

print((get_script() as Script).get_method_list().size())    ## 104
print(get_method_list().size())                             ## 194

Anonymous Functions, Callables

Set / Get
@export var surfaceColors: PackedFloat32Array = [1.0] :
    get:
        return surfaceColors
    set(value):
        surfaceColors = value
Lambda Functions
area.area_entered.connect(func(area : Area2D):
    #code
    )
Callables
  • % Great explanation .

    • Very cool.

    • Demonstrated uses:

      • Function to profile execution time of other functions.

      • Function to periodically emit another function.

      • Array .map()  method.

      • Array .filter()  method.

Signals

Signal creation
signal attacked
signal item_dropped(name : String, amount : float)
Signal connection
  • Connect Signals via manual connection in Godot interface.

    • This is possible both for built-in Signals and custom signals created with signal my_signal .

  • Using Object.connect() .

  • Using Signal.connect() .

var player = player.new()

# 5: Connect the 'hit' signal to 'self' via the function '_on_player_hit'.
player.hit.connect(_on_player_hit)

# 6: Connect the 'hit' signal to 'self' via the function '_on_player_hit'; passing parameters ('sword', 100).
player.hit.connect(_on_player_hit.bind("sword", 100))
  • Prefer using Signal.connect()  over Object.connect() , as it references the Signal without using strings.

  • Methods (3,4) and (7,8) are used only if you want to connect not necessarily to 'self', but to another node.

  • About parameter use in connection:

    • Parameters are normally (almost always) passed during signal emission: signal.emit(arg1, arg2)  or emit_signal('signal', arg1, arg2) .

    • Using .bind(arg1, arg2)  during connection implies that parameters arg1  and arg2  will always be passed when the signal is emitted.

Signal emission
  • Using Object.emit_signal() .

  • Using Signal.emit() .

# Emit the signal.
health_changed.emit()

# Emit the signal; passing parameters (health, max_health).
health_changed.emit(health, max_health)
  • Prefer Signal.emit()  over Object.emit_signal() , for referencing the Signal without strings.

Typing

Static Typing
Checking variable type
  • Returns the number corresponding to the variable type, associated with an ENUM described in @GlobalScope  under 'Variant.Type'.

    • int typeof(x : Variant)

Changing variable type
  • Convert to 'string':

    • String str(x : Variant)

    • String var_to_str(x : Variant)

Enums
  • "you can get that enum with: EnumName.keys()[the_int] ".

Casting
  • Unsafe Cast .

    • Dubious implementation and gives warnings when it shouldn't, ~kinda.

  • TLDR (I think):

    • Use:

      • var drag_data : DragData = data

    • Do not use:

      • var drag_data := data as DragData

Data Structures vs RefCounted vs Resource

  • "if I make myobject.array = myarray , then when I change the myobject.array , the myarray  is not changed. When I do the same thing, but instead of making the object hold an array, I make it hold a Resource or RefCounted, it works, like so myobject.resource = myresource  , so when I change myobject.resource  the myresource  changes as well."

  • "This also works with myobject.node = mynode , but it was said that this could cause some memory leak problems."

    • As stated in RefCounted documentation: no need to use free(), as these objects do it automatically when not in use. Same for Resource.

  • The choice between RefCounted and Resource depends on whether you want to use this data in the inspector.

    • RefCounted cannot be used in @export ; this causes an error.

Classes

Class
  • class_name MyClass

  • Class Constructor.

    • func _init() -> void:

    • Can pass parameters.

Inner Class
class Test extends Node3D:
    pass


class Test extends Hitbox:
    pass
  • Inner Class Constructor.

    • func _init() -> void:

    • Can pass parameters.

Accessing a variable inside a Node or Class
  • With a dot:

    • node.variable_name

  • With a string:

    • node["variable_name"]

Keyword super()

  • "super() is used to call the parent's class method from within the child overriding method."

class A
func say():
  print("A")

class B
extends A
func say():
  super() # will call A.say()
  print("B")
  • B.new().say()  : will print A then B.

Information transfer via Script inheritance (libraries / class)

  • Method with extends "res://path"  or extends _class_name_ :

    • Advantages: @export var  functionalities work normally. You can call all functions and properties directly, without accessing them as my_library._function-or-property_name .

    • Disadvantages: Only one 'extends' per script, creating strong dependencies on the library for scripts inheriting this way.

  • Method with var my_library = _class_name_.new() :

    • Advantages: Allows your own 'extends', making scripts that inherit libraries this way have weak dependency on the library.

    • Disadvantages: You need to use my_library._function-or-property_name  every time you want to use a resource in the library. @export var  functionalities inside the class do not work. You cannot @export var  in a script and use it as a class because scripts/classes are Objects and cannot be exported.

  • Method with @export var my_resource : Resource :

    • Advantages: Very flexible, allowing resources contained in the Resource to be used globally or locally.

    • Disadvantages: Requires 'extends Resource', limiting usability in some cases. The interface is unintuitive and requires practice.

  • Method with Dictionary or Array:

    • Advantages: Easy to implement, compact.

    • Disadvantages: Less versatile, may require scanning the Dictionary/Array depending on the task.

Annotations

Export Variables

Export FLAGS
@export: Export variables to Inspector
  • Exporting variables while defining a 'type' or 'range'.

    • @export var amount : float = 52.0

    • @export_range(0.0, 100.0, 3.0) var amount : float = 52.0

  • Improve visualization of exports in Inspector by defining a Group or Subgroup: (Very useful!)

    • @export_group(name: String, prefix: String = "")

    • @export_subgroup(name: String, prefix: String = "")

  • Resource export :

    • @export var resource : _class_name

  • Custom Resource export :

    • For this to work, the corresponding Resource Script must have a 'class_name', making it a valid Class to use as a 'Resource type'.

    • @export var resource : _custom_class_name

  • Not sure if these options work:

    • @export @onready var node : NodePath = get_node(node)

    • @export @onready var node : Node = get_node(node)

On Ready

@onready
  • Difference between @onready var  and var :

    • "var means you're setting the variable on initialization - which is before child creation. While @onready var  means you're creating the variable, waiting until all children have been created, and then setting the value after the node enters the tree."

Static Variables

  • Can be used to share information between instances of the same Scene:

    • .

Execution

Frequency of calling ' _physics_process() ' and its execution order
  • NCarter: "Everything is called in sequence, it'll never run two process-type functions simultaneously.

  • The way _physics_process()  works is that the engine looks at how long the last frame worked and then runs a loop which calls _physics_process()  as many times as is necessary to compensate.

  • Normally your vsynced frame rate would be 60Hz and the default _physics_process()  rate is also 60Hz, so you just get one _physics_process()  per frame. If you're actually running at 120Hz, you'll get two _physics_process()  calls per frame (for each object). If you're running at 45Hz or something, you'll get a mixture of one and zero _physics_process()  calls per frame. Like I say, these are called in a loop, so you get _process  once per frame and zero or more _physics_process()  calls one after another in the same frame. It doesn't do anything fancy like try to space out the _physics_process()  calls so that they are called at regular intervals in real time, so there is no way they can overlap each other for that reason. I think there's something in there to deal with snowballing _physics_process()  cost, which would happen if _physics_process()  is taking so much time up that it makes the frame take longer, and then even more _physics_process()  calls are needed on the next frame. I think it has a maximum number of calls it will make in a single frame, and the consequence will be that the action slows down; it's that, or if the _physics_process()  takes too long to call, then it stops rendering to catch up."

Execution time
  • .

Notifications

Execution order

'set_deferred' and 'call_deferred' (Delay a task until the next frame)
  • call_deferred, yield & Threads .

  • set_deferred  is used to change a property. Only 1 parameter can be passed.

    • hitbox.set_deferred("disabled", true)

  • call_deferred  ("callable deferred") is used to call a 'callable' (function/method). Multiple parameters can be passed according to the number expected by the function/method.

    • hitbox.call_deferred("explode_hitbox", 'hell yeah!', true)

  • A 'queue' is made before executing the task, waiting for the next frame to run it. This is important when altering a physics process of the engine or doing something extreme to a node.

    • Some examples where it might be important to use one of these methods:

      • queue_free() ;

      • Changing the shape of CollisionShapes2D or CollisionPolygons2D;

      • Toggle 'Monitoring', 'Monitorable', Layers, and Collision Masks.

Await
  • Documentation .

  • Create a timer in the script.

  • Use await  by creating a Timer and waiting for its 'timeout' Signal:

    • This method creates a 'temporary Timer' at the top of the SceneTree containing this Node, while waiting for the 'timeout' Signal to indicate the Timer has ended, allowing the Script to continue from the await . The 'temporary Timer' created by .create_timer()  can only exist at the top of a SceneTree and is deleted automatically when its time ends.

    • The create_timer  time is given in seconds.

    • await get_tree().create_timer(0.2).timeout

  • Use await  with an existing node while waiting for its Signal.

$AniPlayer.play('animation')
await $AniPlayer.animation_finished
  • Note: Timers created in the SceneTree  cannot be paused unless parameters of the .create_timer()  call are changed.

Limitations

Pass function arguments out of order and/or by name
func _ready():
    test(b = 'hello') # crashes here

func test(a = 2, b = 'nice', c = null)
    print(a, b, c)