GDScript 中的静态类型

在本指南中,您将了解:

  • 如何在 GDScript 中使用类型
  • 这种静态类型可以帮助你避免错误

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

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

笔记

从 Godot 3.1 开始可以使用 Typed 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. 核心类和节点(ObjectNodeArea2D, Camera2D,等等)
  3. 您自己的自定义类。查看新的class_name 功能以在编辑器中注册类型。

笔记

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

自定义变量类型

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

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

第二种方法是class_name在创建时使用关键字。对于上面的示例,您的 Rifle.gd 将如下所示:

extends Node2D
class_name Rifle

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

var my_rifle: Rifle

变量铸造

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

想象一下你的游戏中有一个敌​​人,那个. 您希望它与 Player 发生碰撞,并附有一个名为的脚本 。您使用该 信号来检测碰撞。使用类型化代码,您检测到的主体将是通用的,而不是您 的回调。extends Area2DKinematicBody2DPlayerControlleron_body_enteredPhysicsBody2DPlayerController_on_body_entered

您可以PhysicsBody2D使用as cast 关键字检查这是否是您的 Player ,并:再次使用冒号强制变量使用此类型。这会强制变量坚持 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 支持duck-typing,所以即使你的定时器是 type Timer,它也是 aNode和 an Object,它扩展了 两个类。使用动态 GDScript,您也不必关心节点的类型,只要它具有您需要调用的方法即可。

当你得到一个节点时,你可以使用强制转换来告诉 Godot 你期望的类型:,等。 Godot 将确保类型有效,如果是这样,脚本编辑器左侧的行号将变为绿色。($Timer as Timer)($Player as KinematicBody2D)

不安全与安全线

不安全线(第 7 行)与安全线(第 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

打字或动态:坚持一种风格

类型化 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_Area2D_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 警告系统

无法指定类型的情况

为了结束这个介绍,让我们介绍一些不能使用类型提示的情况。下面的所有示例都会触发错误

您不能将枚举用作类型:

enum MoveDirection {UP, DOWN, LEFT, RIGHT}
var current_direction: MoveDirection

您不能指定数组中单个成员的类型。这会给你一个错误:

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

您不能强制在for循环中分配类型,因为for关键字循环的每个元素已经具有不同的类型。所以你 不能写:

var names = ["John", "Marta", "Samantha", "Jimmy"]
for name: String in names:
    pass

两个脚本不能以循环方式相互依赖:

# Player.gd

extends Area2D
class_name Player


var rifle: Rifle
# Rifle.gd

extends Area2D
class_name Rifle


var player: Player

概括

Typed GDScript 是一个强大的工具。从 Godot 3.1 版开始可用,它可以帮助您编写更多结构化的代码,避免常见错误并创建可扩展的系统。将来,由于即将进行的编译器优化,静态类型还将为您带来不错的性能提升。