8 changes to GDScript in Godot 4 you'll want to know about


As I’m going through my Godot content that needs updating, I’ve come across a number of small changes to GDScript 2, which I’ve decided to capture in this post. Plus, I’m going to throw in a few bonus notes about some handy new features in GDScript 2 you might want to know about. Let’s dive in!

Referencing functions

In GDScript 1, you had to manually create an object that referenced a function if you wanted to pass it around, which was done via the Funcref class. In GDScript 2, we now have Callables, which can work similar to funcrefs via manual function reference creation if desired, but are also automatically created when a function is referenced by name, meaning you can now simplify your code a bit by just referencing a function by name and using the syntax my_function.call() when you want to call it.

func print_message(msg: String) -> void:
    print(msg)

func _ready() -> void:
    var func = print_message
    func.call('Hello')
    func.call('There!')

Asynchronous code

In GDScript 1, we used the yield command to wait for a signal to emit. This was commonly used to do things like wait for an animation or a timer to finish.

# Godot 3 / GDScript 1
# Wait for animation to complete before printing out the message
$AnimationPlayer.play('explode')
yield($AnimationPlayer,'animation_finished')
print('You exploded!')

We now use the await keyword instead, which is a bit more in line with other asynchronous programming environments and, combined with callables, means we can just directly reference the signal we want to wait for rather than having to make a function call with a string parameter of the signal name:

# Godot 4 / GDScript 2
# Wait for animation to complete before printing out the message
$AnimationPlayer.play('explode')
await $AnimationPlayer.animation_finished
print('You exploded!')

If you’re really into asynchronous programming, there’s a bit more that you might want to know so be sure to check out the docs, but this should be enough to get a lot of you on your way so I’ll leave it at this.

Super functions

To call a function from a superclass, we used to use the syntax .my_function(), which was a bit non-conventional and hard to read. We now instead use the super keyword, such as with super.my_function(). Another improvement with this new structure is that simply calling super() will call the same function you are in from the superclass.

class MyClass:
    func say_hello():
        print('Hello!')
    
    func say_goodbye():
        print('Later!')

class MyOtherClass extends MyClass:
    func say_hello():
        super()
        print('Howdy!')
        super.say_goodbye()

Changing and reloading scenes

This one is a bit simpler, but the syntax for changing scenes in Godot 4 has changed slightly to make it clearer which of the two functions you should use. In Godot 3, you had change_scene for changing scenes by path and change_scene_to for changing to a provided PackedScene. In Godot 4, change_scene is now change_scene_to_file and change_scene_to is now change_scene_to_packed. Much clearer!

const NEXT_SCENE = preload('res://my_scene.tscn')

# Change scene by string path
get_tree().change_scene_to_file('res://my_scene.tscn')

# Change scene using a loaded scene
get_tree().change_scene_to_packed(NEXT_SCENE)

Annotations

Annotations are another important change in GDScript 2 you should be aware of. Rather than using keywords to denote special functionality like we did in GDScript 1, such as typing export to make a variable editable in the inspector, we now use annotations, which begin with @ followed by the term you want.

@export
var my_var

We also have some new terms for exporting, though, that make it a bit clearer what you’re doing. As an example, in Godot 3 we’d export a float within a range using the syntax export (float, MIN, MAX) var my_float, but this would now look like @export_range(MIN, MAX) var my_float: float. Alternatively, you can place annotations on their own line:

@export_range(MIN, MAX)
var my_float: float

There’s a lot you can do with exports, so be sure to check out the docs for more info.

Other annotations are similarly named the same as their previous keyword, so I’ll just list them out here:

  • @tool
  • @onready
  • @icon(path)
  • @rpc
  • @export, plus a whole lot of variations (see the link above)

Bonus - A few new features

And now, I’ll throw in a few bonus tidbits regarding some new GDScript functionality.

Enums as types

This is one that I’m particularly happy about. Rather than taking a variable that represents a value from an enumerator and typing it as an integer, which would allow invalid values to be set to it, enumerators can now be used as types, raising warnings or errors depending on how egregious you get with your code. An example:

enum WeaponClass {
    Blunt,
    Piercing,
    Melee
}

enum Rarity {
    Common,
    Rare,
    Epic
}

# Valid
var selected_class: WeaponClass = WeaponClass.Blunt

# Valid, but raises a warning
selected_class = 1

# Raises an error
selected_class = Rarity.Common

Exporting resources

Another big feature as of Beta 2 is the ability to use custom resources as an export type and get some help from the inspector when creating or setting that variable, though this feature is still a little rough around the edges and has some additional work planned for it so you may not want to go all in on it just yet.

# combat_stats.gd
class_name CombatStats
extends Resource

# player.gd
@export var combat_stats: CombatStats

Typed arrays

The last new addition I’ll mention today is that of typed arrays. Now, you can specify what type of data should go in your array and the engine will check your code for you!

# All good
var int_array: Array[int] = [1, 2, 3]

# "3" will raise an error in the editor
var str_array: Array[String] = ['item 1', 'item 2', 3]