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.

GDScript 中的静态类型

在本指南中,您将学会:

  • 如何在 GDScript 中使用类型

  • 静态类型可以帮助您避免 bug

在何处以及如何使用此新语言功能完全取决于您: 您可以只在某些敏感的GDScript文件中使用它, 也可以在任何地方使用它, 或者像往常一样编写代码!

静态类型可用于变量、常量、函数、参数、返回类型。

静态类型简介

有了GDScript中的类型,Godot在编写代码时甚至可以检测到更多错误!它会在你工作时为你和你的团队提供更多信息. 因为当你调用方法时, 会显示出参数的类型.

想象你正在编写背包系统。您编写一个 Item(道具)节点,然后再编写一个 Inventory(背包)。要将道具添加到背包中,使用代码的人员应始终将 Item 传递给 Inventory.add 方法。有了类型,您就可以强制执行以下操作:

# In 'item.gd'.
class_name Item
# In 'inventory.gd'.
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

类型化的GDScript的另一个显著优点是新的 警告系统 . 从版本3.1开始,Godot会在您编写代码时向您发出有关代码的警告: 引擎会识别代码中可能导致运行时出现问题的部分, 但您可以决定是否要保留代码. 稍后详细介绍.

静态类型还为您提供了更好的代码补全选项. 下面, 您可以看到一个名为 PlayerController 类的动态和静态类型补全选项之间的区别.

您之前可能已经将节点存储在变量中, 并输入了一个点, 但是没有代码自动补全建议:

动态类型的代码补全选项

这是因为动态代码. Godot无法知道您传递给函数的节点或值类型. 但是, 如果您明确地声明了类型, 则将从该节点类型获取所有公共方法和变量:

静态类型的代码补全选项

将来, 类型化的GDScript也将提高代码性能: 实时编译和其他编译器改进已经出现在路线图上!

总体而言, 类型化编程可为您提供更加结构化的体验. 它有助于防止错误并改善脚本的自我描述能力. 当您在团队中或长期项目中工作时, 这特别有用: 研究表明, 开发人员将大部分时间都花在阅读别人的代码或他们过去编写并忘记的脚本上. 代码越清晰, 越结构化, 理解得就越快, 前进的速度就越快.

如何使用静态类型

要定义变量或常量的类型, 请在变量名称后加上一个冒号, 后跟它的类型. 例如 var health: int . 这将强制变量的类型保持不变:

var damage: float = 10.5
const MOVE_SPEED: float = 50.0

如果您写冒号但省略写类型时,Godot将尝试推测类型:

var life_points := 4
var damage := 10.5
var motion := Vector2()

当前, 您可以使用三类…类型:

  1. 内置类型

  2. 核心类和节点(ObjectNodeArea2DCamera2D 等)

  3. 您自己定义的类。查看新的 class_name 特性,以便在编辑器中注册类型。

备注

您不需要为常量编写类型提示, 因为Godot会根据分配的值自动设置. 但是您仍然可以这样做, 以使代码的意图更加清晰.

自定义变量类型

您可以将任何类(包括您的自定义类)用作类型. 有两种在脚本中使用它们的方法. 第一种方法是将要用作类型的脚本预加载为常量:

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

如果使用 class_name ,Godot会在编辑器中注册一个全局 Rifle 类型, 您可以在任何地方使用它而无需将其预加载到常量中:

var my_rifle: Rifle

变量转换

类型转换是类型语言的关键概念. 转换是将值从一种类型转换为另一种类型.

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 on_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.

您可以使用 as 转换关键字检查这个 PhysicsBody2D 是否是您的游戏角色, 并再次使用冒号 : 来强制变量使用这种类型. 这会强制变量坚持使用 PlayerController 类型:

func _on_body_entered(body: PhysicsBody2D) -> void:
    var player := body as PlayerController
    if not player:
        return

    player.damage()

在处理自定义类型时, 如果 body 没有扩展为 PlayerController , 则 player 变量将被设置为 null . 我们可以用它来检查物体是否为游戏玩家角色. 多亏了类型转换, 我们还将获得 player 变量的代码自动补全功能.

备注

如果您尝试使用内置类型进行转换并失败, 则Godot将引发错误.

安全行

您也可以使用转换来确保安全行. 安全行是Godot 3.1中的新工具, 可以告诉您歧义的代码行何时是类型安全的. 由于您有时会混合类型化代码和动态代码, 有时如果指令在运行时触发错误,Godot可能没有足够的信息判断.

当您需要获得子节点时会发生这种情况。让我们以计时器为例:使用动态代码,您可以使用 $Timer 获取节点。GDScript 支持鸭子类型,所以即使您的计时器是 Timer 类型,它也扩展了 NodeObject 这两个类。使用动态 GDScript,只要节点具有您需要调用的方法,您也不必关心节点的类型。

You can use casting to tell Godot the type you expect when you get a node: ($Timer as Timer), ($Player as CharacterBody2D), etc. Godot will ensure the type works and if so, the line number will turn green at the left of the script editor.

不安全 vs 安全的行

不安全代码 (第 7 行) vs 安全代码 (第 6 行和第 8 行)

备注

可以在编辑器设置中关闭安全行或更改其颜色.

使用箭头 -> 定义函数的返回类型

要定义函数的返回类型, 请在声明后写一个短划线和一个右尖括号 ->, 后跟返回类型:

func _process(delta: float) -> void:
    pass

类型 void 表示函数不返回任何内容. 您可以使用任何类型, 如变量:

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

Define 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. Primitive types, builtin classes, and custom classes may be used as types. Nested array types are not supported.

var scores: Array[int] = [10, 20, 30]
var vehicles: Array[Node] = [$Car, $Plane]
var items: Array[Item] = [Item.new()]
# var arrays: Array[Array] -- 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.

静态还是动态:坚持一种风格

强类型的 GDScript 和动态 GDScript 可以共存于同一项目,但还是建议选择其一,以确保代码库和同伴间的一致性。如果你们遵循相同的规范,那么大家都可以更轻松地协作,阅读和理解他人的代码也会更加迅速。

类型化的代码需要编写更多, 但是您将获得上面讨论的好处. 下面是内容一样的空脚本示例, 首先是使用动态风格:

extends Node


func _ready():
    pass


func _process(delta):
    pass

其次是使用静态类型:

extends Node


func _ready() -> void:
    pass


func _process(delta: float) -> void:
    pass

如您所见, 您也可以对引擎虚函数的参数进行类型声明. 与任何方法一样, 信号回调参数也可以使用类型. 下面是一个动态风格的 body_entered 信号示例:

func _on_area_2d_body_entered(body):
    pass

以及具有类型提示的, 相同的回调:

func _on_area_entered(area: CollisionObject2D) -> void:
    pass

您可以自由替代类型, 例如把参数转换为您自己的类型 CollisionObject2D:

func _on_area_entered(bullet: Bullet) -> void:
    if not bullet:
        return

    take_damage(bullet.damage)

bullet 变量在这里保存任何继承了 CollisionObject2D 的节点类, 但我们需要确保它是我们的 Bullet , 即那个我们为项目创建的节点类型. 如果它是其他任何东西, 比如 Area2D , 或任何不扩展 Bullet 的节点, bullet 变量值将是 null .

警告系统

备注

关于GDScript警告系统的文档已移至 GDScript 警告系统.

A case where you can't specify types

To wrap up this introduction, let's mention a case where you can't use type hints. This will trigger a syntax error.

You can't specify the type of individual members in an array:

var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]

总结

Typed GDScript is a powerful tool. 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.