Static typing in GDScript
In this guide, you will learn:
- How to use types in GDScript
- static types can help you avoid bugs
Where and how you use this new language feature is entirely up to you: you can use it only in some sensitive GDScript files, use it everywhere, or write code like you always did!
Static types can be used on variables, constants, functions, parameters, and return types.
Note
Typed GDScript is available since Godot 3.1.
A brief look at static typing
With typed GDScript, Godot can detect even more errors as you write code! It gives you and your teammates more information as you’re working, as the arguments’ types show up when you call a method.Itemnode, then anInventory. To add items to the inventory, the people who work with your code should always pass anItemto theInventory.addmethod. With types, you can enforce this:
warning system. From version 3.1, Godot gives you warnings about your code as you write it: the engine identifies sections of your code that may lead to issues at runtime, but lets you decide whether or not you want to leave the code as it is. More on that in a moment.# In 'Item.gd'.class_name Item# In 'Inventory.gd'.class_name Inventoryfunc add(reference: Item, amount: int = 1): var item = find_item(reference) if not item: item = _instance_item_from_db(reference) item.amount += amount
PlayerController. You’ve probably stored a node in a variable before, and typed a dot to be left with no autocomplete suggestions: This is due to dynamic code. Godot cannot know what node or value type you’re passing to the function. If you write the type explicitly however, you will get all public methods and variables from the node: In the future, typed GDScript will also increase code performance: Just-In-Time compilation and other compiler improvements are already on the roadmap! Overall, typed programming gives you a more structured experience. It helps prevent errors and improves the self-documenting aspect of your scripts. This is especially helpful when you’re working in a team or on a long-term project: studies have shown that developers spend most of their time reading other people’s code, or scripts they wrote in the past and forgot about. The clearer and the more structured the code, the faster it is to understand, the faster you can move forward.How to use static typing
var health: int. This forces the variable’s type to always stay the same:
Godot will try to infer types if you write a colon, but you omit the type:var damage: float = 10.5const MOVE_SPEED: float = 50.0
Currently you can use three types of… types:var life_points := 4var damage := 10.5var motion := Vector2()
- Built-in
Object,Node,Area2D,Camera2D, etc.)- class_name feature to register types in the editor.
Note
You don’t need to write type hints for constants, as Godot sets it automatically from the assigned value. But you can still do so to make the intent of your code clearer.
Custom variable types
You can use any class, including your custom classes, as types. There are two ways to use them in scripts. The first method is to preload the script you want to use as a type in a constant:const Rifle = preload("res://player/weapons/Rifle.gd")var my_rifle: Rifle
class_namekeyword when you create. For the example above, your Rifle.gd would look like this:extends Node2Dclass_name Rifle
class_name, Godot registers the Rifle type globally in the editor, and you can use it anywhere, without having to preload it into a constant:var my_rifle: Rifle
Variable casting
Type casting is a key concept in typed languages. Casting is the conversion of a value from one type to another.extends Area2D. You want it to collide with the Player, aKinematicBody2Dwith a script calledPlayerControllerattached to it. You use theon_body_enteredsignal to detect the collision. With typed code, the body you detect is going to be a genericPhysicsBody2D, and not yourPlayerControlleron the_on_body_enteredcallback.PhysicsBody2Dis your Player with theascasting keyword, and using the colon:again to force the variable to use this type. This forces the variable to stick to thePlayerControllertype:func _on_body_entered(body: PhysicsBody2D) -> void: var player := body as PlayerController if not player: return player.damage()
bodydoesn’t extendPlayerController, theplayervariable will be set tonull. We can use this to check if the body is the player or not. We will also get full autocompletion on the player variable thanks to that cast. Note If you try to cast with a built-in type and it fails, Godot will throw an error.Safe lines
You can also use casting to ensure safe lines. Safe lines are a new tool in Godot 3.1 to tell you when ambiguous lines of code are type-safe. As you can mix and match typed and dynamic code, at times, Godot doesn’t have enough information to know if an instruction will trigger an error or not at runtime.$Timer. GDScript supports duck-typing, so even if your timer is of typeTimer, it is also aNodeand anObject, two classes it extends. With dynamic GDScript, you also don’t care about the node’s type as long as it has the methods you need to call.($Timer as Timer),($Player as KinematicBody2D), etc. Godot will ensure the type works and if so, the line number will turn green at the left of the script editor. Unsafe line (line 7) vs Safe Lines (line 6 and 8) Note You can turn off safe lines or change their color in the editor settings.Define the return type of a function with the arrow ->
->after its declaration, followed by the return type:func _process(delta: float) -> void: pass
voidmeans the function does not return anything. You can use any type, as with variables:
You can also use your own nodes as return types:func hit(damage: float) -> bool: health_points -= damage return health_points <= 0
# Inventory.gd# Adds an item to the inventory and returns it.func add(reference: Item, amount: int) -> Item: var item: Item = find_item(reference) if not item: item = ItemDatabase.get_instance(reference) item.amount += amount return item
Typed or dynamic: stick to one style
Typed GDScript and dynamic GDScript can coexist in the same project. But it’s recommended to stick to either style for consistency in your codebase, and for your peers. It’s easier for everyone to work together if you follow the same guidelines, and faster to read and understand other people’s code. Typed code takes a little more writing, but you get the benefits we discussed above. Here’s an example of the same, empty script, in a dynamic style:
And with static typing:extends Nodefunc _ready(): passfunc _process(delta): pass
extends Nodefunc _ready() -> void: passfunc _process(delta: float) -> void: pass
body_enteredsignal in a dynamic style:
And the same callback, with type hints:func _on_Area2D_body_entered(body): pass
func _on_area_entered(area: CollisionObject2D) -> void: pass
CollisionObject2D, with your own type, to cast parameters automatically:func _on_area_entered(bullet: Bullet) -> void: if not bullet: return take_damage(bullet.damage)
bulletvariable could hold anyCollisionObject2Dhere, but we make sure it is ourBullet, a node we created for our project. If it’s anything else, like anArea2D, or any node that doesn’t extendBullet, thebulletvariable will benull.Warning system
Note GDScript warning system.Cases where you can’t specify types
will trigger errors. You can’t use Enums as types:
You can’t specify the type of individual members in an array. This will give you an error:enum MoveDirection {UP, DOWN, LEFT, RIGHT}var current_direction: MoveDirection
var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]
forloop, as each element theforkeyword loops over already has a different type. So you cannot write:
Two scripts can’t depend on each other in a cyclic fashion:var names = ["John", "Marta", "Samantha", "Jimmy"]for name: String in names: pass
# Player.gdextends Area2Dclass_name Playervar rifle: Rifle
# Rifle.gdextends Area2Dclass_name Riflevar player: Player
Summary
Typed GDScript is a powerful tool. Available as of version 3.1 of Godot, it helps you write more structured code, avoid common errors, and create scalable systems. In the future, static types will also bring you a nice performance boost thanks to upcoming compiler optimizations.
