Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

GDScript 静态类型编程

在本指南中,你将学会:

  • 如何在 GDScript 中使用静态类型编程;

  • 静态类型编程可以帮助你避免问题;

  • 静态类型编程可以提升编辑器的使用体验。

这项语言功能的使用场合、使用方式完全取决于你:你可以只在部分敏感的 GDScript 文件中使用,也可以在所有地方都使用,甚至可以完全不使用。

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

静态类型编程简介

使用 GDScript 静态类型编程,Godot 在编写代码时甚至可以帮你检测到更多代码错误,在你工作时为你和你的团队提供更多信息,当你调用方法时,会显示出参数的类型。静态类型编程也能改善编辑器的自动补全体验,其中也包括 documentation

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

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

你之前可能已经将节点存储在了变量中,打了一个句点符号,却没有代码自动补全提示:

动态类型的代码补全选项。

由于动态代码是动态的,因此 Godot 无法得知你传递给函数的值的类型。可如果你明确地声明了类型,则将从该节点类型获取所有公共方法和变量:

静态类型的代码补全选项。

小技巧

若偏向静态类型编程,建议开启编辑器选项**文本编辑器 > 补全 > 添加类型提示**,顺便也可以考虑开启默认关闭的 某些选项

同时,在操作数/参数类型在编译时已知时,静态类型编程编写的 GDScript 代码还能通过优化后的操作码提升代码运行性能。未来还计划进行更多 GDScript 方面的优化,如 JIT/AOT 编译。

总体而言,静态类型编程可为你提供更加结构化的体验,有助于避免代码错误,改善脚本的文档生成能力。当你在团队中或长期项目中工作时,静态类型编程将会特别有用。研究表明,开发人员将大部分时间要么都花在阅读别人的代码上,要么都花在阅读他们以前编写过但后来忘掉的脚本上。代码越清晰、越结构化,开发人员理解得也就越快,项目开发的速度也就越快。

如何使用静态类型编程

要定义变量、参数、常量的类型,请在名称后写一个英文冒号,再写上类型。例如 var health: int。这样就能够让变量的类型始终保持一致:

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 就会尝试推导类型:

var damage := 10.5
const MOVE_SPEED := 50.0
func sum(a := 0.0, b := 0.0) -> float:
    return a + b

备注

  1. 对于常量而言, =:= 没有区别。

  2. 常量不需要写类型提示,Godot 会自动根据所赋的值设置该常量的类型,你仍然可以写上类型提示来让代码更整洁。同时,这样写对于类型化数组也还是很有用的(比如 const A: Array[int] = [1, 2, 3]),因为默认使用的是无类型的数组。

类型提示可以是什么

下面列出的是所有可以用作类型提示的东西:

  1. Variant,任何类型。大多数情况下与不写类型声明差不多,但能够增加可读性。作为返回类型时,能够强制函数显式返回值。

  2. (仅作返回类型使用) void。表示函数不返回任何值。

  3. 内置类型

  4. 原生类(ObjectNodeArea2DCamera2D 等)。

  5. 全局类

  6. 内部类

  7. 全局具名常量与内部具名常量。注意:枚举是 int 类型的数据,不能保证一个值属于该枚举。

  8. 包含预加载类和枚举的常量(或局部常量)。

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

const Rifle = preload("res://player/weapons/rifle.gd")
var my_rifle: Rifle

而第二种方法则是在创建类时使用 class_name 关键字。对于上面的示例, Rifle.gd 看起来就像这样::

class_name Rifle
extends Node2D

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

var my_rifle: Rifle

Specify the return type of a function with the arrow ->

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

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

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

func hit(damage: float) -> bool:
    health_points -= damage
    return health_points <= 0

你还可以使用自定义类作为返回类型::

# 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

协变与逆变

继承基类方法时,应遵循 里氏代换原则

协变: 继承方法时,你可以为子类方法指定一个比该子类方法的父类方法更为具体的返回值类型(比如**子类**)。

逆变: 继承方法时,你可以为子类方法指定一个比该子类方法的父类方法更不具体的返回值类型(比如**超类**)。

示例(以匿名 setter/getter 函数为例):

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 [].

数组的类型适用于“for”循环变量以及一些运算符,如“[]”、“[]=”和“+”。部分数组方法(如`push_back``)和运算符(如``==``)依旧对数组类型不敏感。内置类型、引擎原生类型、自定义类型及枚举均可以用作元素类型。不支持嵌套数组类型。

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 error