Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
GDScript 中的靜態型別
在本指南中,我們將學到:
how to use static typing in GDScript;
that static types can help you avoid bugs;
that static typing improves your experience with the editor.
Where and how you use this language feature is entirely up to you: you can use it only in some sensitive GDScript files, use it everywhere, or don't use it at all.
靜態型別可以在變數、常數、函式、參數、與回傳型別上使用。
靜態型別簡介
With static typing, GDScript can detect more errors without even running the code. Also type hints give you and your teammates more information as you're working, as the arguments' types show up when you call a method. Static typing improves editor autocompletion and documentation of your scripts.
Imagine you're programming an inventory system. You code an Item
class,
then an Inventory
. To add items to the inventory, the people who work with
your code should always pass an Item
to the Inventory.add()
method.
With types, you can enforce this:
class_name Inventory
func add(reference: Item, amount: int = 1):
var item := find_item(reference)
if not item:
item = _instance_item_from_db(reference)
item.amount += amount
Static types also give you better code completion options. Below, you can see the difference between a dynamic and a static typed completion options.
You've probably encountered a lack of autocomplete suggestions after a dot:

This is due to dynamic code. Godot cannot know what value type you're passing to the function. If you write the type explicitly however, you will get all methods, properties, constants, etc. from the value:

小訣竅
If you prefer static typing, we recommend enabling the Text Editor > Completion > Add Type Hints editor setting. Also consider enabling some warnings that are disabled by default.
Also, typed GDScript improves performance by using optimized opcodes when operand/argument types are known at compile time. More GDScript optimizations are planned in the future, such as JIT/AOT compilation.
整體上來說,加上型別能帶來更結構化的體驗,有助於避免錯誤以及讓腳本能自行說明功能。對於在團隊中合作或長期專案來說特別實用:研究指出,開發者花費較多的時間閱讀其他人或自己以前寫過但已經忘記的程式碼。程式碼越清楚、結構越明白,就越容易能理解,並能讓開發者更快開始工作。
如何使用靜態型別
To define the type of a variable, parameter, or constant, write a colon after the name,
followed by its type. E.g. var health: int
. This forces the variable's type
to always stay the same:
var damage: float = 10.5
const MOVE_SPEED: float = 50.0
func sum(a: float = 0.0, b: float = 0.0) -> float:
return a + b
Godot will try to infer types if you write a colon, but you omit the type:
var damage := 10.5
const MOVE_SPEED := 50.0
func sum(a := 0.0, b := 0.0) -> float:
return a + b
備註
There is no difference between
=
and:=
for constants.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. Also, this is useful for typed arrays (like
const A: Array[int] = [1, 2, 3]
), since untyped arrays are used by default.
What can be a type hint
Here is a complete list of what can be used as a type hint:
Variant
. Any type. In most cases this is not much different from an untyped declaration, but increases readability. As a return type, forces the function to explicitly return some value.(Only return type)
void
. Indicates that the function does not return any value.Native classes (
Object
,Node
,Area2D
,Camera2D
, etc.).Global, native and custom named enums. Note that an enum type is just an
int
, there is no guarantee that the value belongs to the set of enum values.Constants (including local ones) if they contain a preloaded class or enum.
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
The second method is to use the class_name
keyword when you create.
For the example above, your rifle.gd
would look like this:
class_name Rifle
extends Node2D
If you use 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
Specify the return type of a function with the arrow ->
To define the return type of a function, write a dash and a right angle bracket ->
after its declaration, followed by the return type:
func _process(delta: float) -> void:
pass
The type void
means the function does not return anything. You can use any type,
as with variables:
func hit(damage: float) -> bool:
health_points -= damage
return health_points <= 0
You can also use your own classes as return types:
# 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
Covariance and contravariance
When inheriting base class methods, you should follow the Liskov substitution principle.
Covariance: When you inherit a method, you can specify a return type that is more specific (subtype) than the parent method.
Contravariance: When you inherit a method, you can specify a parameter type that is less specific (supertype) than the parent method.
範例:
class_name Parent
func get_property(param: Label) -> Node:
# ...
class_name Child extends Parent
# `Control` is a supertype of `Label`.
# `Node2D` is a subtype of `Node`.
func get_property(param: Control) -> Node2D:
# ...
Specify the element type of an Array
To define the type of an Array
, enclose the type name in []
.
An array's type applies to for
loop variables, as well as some operators like
[]
, []=
, and +
. Array methods (such as push_back
) and other operators
(such as ==
) are still untyped. Built-in types, native and custom classes,
and enums may be used as element types. Nested array types
(like Array[Array[int]]
) are not supported.
var scores: Array[int] = [10, 20, 30]
var vehicles: Array[Node] = [$Car, $Plane]
var items: Array[Item] = [Item.new()]
var array_of_arrays: Array[Array] = [[], []]
# var arrays: Array[Array[int]] -- disallowed
for score in scores:
# score has type `int`
# The following would be errors:
scores += vehicles
var s: String = scores[0]
scores[0] = "lots"
Since Godot 4.2, you can also specify a type for the loop variable in a for
loop.
For instance, you can write:
var names = ["John", "Marta", "Samantha", "Jimmy"]
for name: String in names:
pass
The array will remain untyped, but the name
variable within the for
loop
will always be of String
type.
Type casting
Type casting is an important concept in typed languages. Casting is the conversion of a value from one type to another.
Imagine an Enemy
in your game, that extends Area2D
. You want it to collide
with the Player
, a CharacterBody2D
with a script called PlayerController
attached to it. You use the body_entered
signal to detect the collision.
With typed code, the body you detect is going to be a generic PhysicsBody2D
,
and not your PlayerController
on the _on_body_entered
callback.
You can check if this PhysicsBody2D
is your Player
with the as
keyword,
and using the colon :
again to force the variable to use this type.
This forces the variable to stick to the PlayerController
type:
func _on_body_entered(body: PhysicsBody2D) -> void:
var player := body as PlayerController
if not player:
return
player.damage()
As we're dealing with a custom type, if the body
doesn't extend
PlayerController
, the player
variable will be set to null
.
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.
備註
The as
keyword silently casts the variable to null
in case of a type
mismatch at runtime, without an error/warning. While this may be convenient
in some cases, it can also lead to bugs. Use the as
keyword only if this
behavior is intended. A safer alternative is to use the is
keyword:
if not (body is PlayerController):
push_error("Bug: body is not PlayerController.")
var player: PlayerController = body
if not player:
return
player.damage()
You can also simplify the code by using the is not
operator:
if body is not PlayerController:
push_error("Bug: body is not PlayerController")
Alternatively, you can use the assert()
statement:
assert(body is PlayerController, "Bug: body is not PlayerController.")
var player: PlayerController = body
if not player:
return
player.damage()
備註
若試著轉換型別為內建型別而失敗的話,Godot 會拋出錯誤。
安全行
You can also use casting to ensure safe lines. Safe lines are a tool 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.
Godot 無法推定型別的狀況通常發生在取得子節點時。以 Timer 為例,我們使用動態程式碼來取得節點並保存在 $Timer
中。GDScript 支援 鴨子型別 ,所以即使 Timer 是 Timer
型別,Timer 同時也會是其繼承的 Node
與 Object
兩個類別。使用動態 GDScript 時,只要節點上有我們需要的方法,就不需要去在意節點是什麼型別。
我們可以使用型別轉換來告訴 Godot 在取得節點的時候預期取得什麼型別,如 ($Timer as Timer)
, ($Player as KinematicBody2D)
…等。Godot 會確保該型別是否有效,而有效的話則會將腳本編輯器左邊的行號變成綠色。

非安全行 (第 7 行) vs 安全行 (第 6 行與第 8 行)
備註
Safe lines do not always mean better or more reliable code. See the note above
about the as
keyword. For example:
@onready var node_1 := $Node1 as Type1 # Safe line.
@onready var node_2: Type2 = $Node2 # Unsafe line.
Even though node_2
declaration is marked as an unsafe line, it is more
reliable than node_1
declaration. Because if you change the node type
in the scene and accidentally forget to change it in the script, the error
will be detected immediately when the scene is loaded. Unlike node_1
,
which will be silently cast to null
and the error will be detected later.
備註
可以在編輯器設定中關閉安全行或更改安全行的色彩。
靜態或動態:只選擇一種風格
有型別的 GDScript 與動態 GDScript 可以在同一個專案中共存,但我們建議固定一種風格來在程式碼中保持一貫性,團隊成員也應固定來保持一貫。若大家都遵守同一個方針的話,也更容易一起工作並更快地閱讀理解他人的程式碼。
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:
extends Node
func _ready():
pass
func _process(delta):
pass
And with static typing:
extends Node
func _ready() -> void:
pass
func _process(delta: float) -> void:
pass
As you can see, you can also use types with the engine's virtual methods.
Signal callbacks, like any methods, can also use types. Here's a body_entered
signal in a dynamic style:
func _on_area_2d_body_entered(body):
pass
And the same callback, with type hints:
func _on_area_entered(area: CollisionObject2D) -> void:
pass
警告系統
備註
Detailed documentation about the GDScript warning system has been moved to GDScript 警告系統.
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.
We have a number of warnings aimed specifically at users of typed GDScript. By default, these warnings are disabled, you can enable them in Project Settings (Debug > GDScript, make sure Advanced Settings is enabled).
You can enable the UNTYPED_DECLARATION
warning if you want to always use
static types. Additionally, you can enable the INFERRED_DECLARATION
warning
if you prefer a more readable and reliable, but more verbose syntax.
UNSAFE_*
warnings make unsafe operations more noticeable, than unsafe lines.
Currently, UNSAFE_*
warnings do not cover all cases that unsafe lines cover.
Common unsafe operations and their safe counterparts
UNSAFE_PROPERTY_ACCESS
and UNSAFE_METHOD_ACCESS
warnings
In this example, we aim to set a property and call a method on an object
that has a script attached with class_name MyScript
and that extends
Node2D
. If we have a reference to the object as a Node2D
(for instance,
as it was passed to us by the physics system), we can first check if the
property and method exist and then set and call them if they do:
if "some_property" in node_2d:
node_2d.some_property = 20 # Produces UNSAFE_PROPERTY_ACCESS warning.
if node_2d.has_method("some_function"):
node_2d.some_function() # Produces UNSAFE_METHOD_ACCESS warning.
However, this code will produce UNSAFE_PROPERTY_ACCESS
and
UNSAFE_METHOD_ACCESS
warnings as the property and method are not present
in the referenced type - in this case a Node2D
. To make these operations
safe, you can first check if the object is of type MyScript
using the
is
keyword and then declare a variable with the type MyScript
on
which you can set its properties and call its methods:
if node_2d is MyScript:
var my_script: MyScript = node_2d
my_script.some_property = 20
my_script.some_function()
Alternatively, you can declare a variable and use the as
operator to try
to cast the object. You'll then want to check whether the cast was successful
by confirming that the variable was assigned:
var my_script := node_2d as MyScript
if my_script != null:
my_script.some_property = 20
my_script.some_function()
UNSAFE_CAST
warning
In this example, we would like the label connected to an object entering our
collision area to show the area's name. Once the object enters the collision
area, the physics system sends a signal with a Node2D
object, and the most
straightforward (but not statically typed) solution to do what we want could
be achieved like this:
func _on_body_entered(body: Node2D) -> void:
body.label.text = name # Produces UNSAFE_PROPERTY_ACCESS warning.
This piece of code produces an UNSAFE_PROPERTY_ACCESS
warning because
label
is not defined in Node2D
. To solve this, we could first check if the
label
property exist and cast it to type Label
before settings its text
property like so:
func _on_body_entered(body: Node2D) -> void:
if "label" in body:
(body.label as Label).text = name # Produces UNSAFE_CAST warning.
However, this produces an UNSAFE_CAST
warning because body.label
is of a
Variant
type. To safely get the property in the type you want, you can use the
Object.get()
method which returns the object as a Variant
value or returns
null
if the property doesn't exist. You can then determine whether the
property contains an object of the right type using the is
keyword, and
finally declare a statically typed variable with the object:
func _on_body_entered(body: Node2D) -> void:
var label_variant: Variant = body.get("label")
if label_variant is Label:
var label: Label = label_variant
label.text = name
Cases where you can't specify types
To wrap up this introduction, let's mention cases where you can't use type hints. This will trigger a syntax error.
You can't specify the type of individual elements in an array or a dictionary:
var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy] var character: Dictionary = { name: String = "Richard", money: int = 1000, inventory: Inventory = $Inventory, }
Nested types are not currently supported:
var teams: Array[Array[Character]] = []
總結
Typed GDScript is a powerful tool. It helps you write more structured code, avoid common errors, and create scalable and reliable systems. Static types improve GDScript performance and more optimizations are planned for the future.