gdscript中的静态键入

在本指南中,您将学习:

  • 如何在gdscript中使用类型

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

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

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

注解

类型化gdscript自godot 3.1起可用。

简单介绍静态类型

使用类型化的gdscript,godot可以在编写代码时检测到更多的错误!当你工作时,它会给你和你的队友更多的信息,当你调用一个方法时,参数的类型会出现。

假设您正在编程一个库存系统。你编码了一个 Item 节点,然后是 Inventory . 要将项目添加到库存中,使用代码的人员应始终通过 ItemInventory.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 .

您以前可能在变量中存储了一个节点,并键入了一个不带自动完成建议的点:

code completion options for dynamic

这是由于动态代码。godot无法知道要传递给函数的节点或值类型。但是,如果显式地编写类型,则将从节点获取所有公共方法和变量:

code completion options for typed

将来,类型化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. Built-in

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

  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在编辑器中全局注册步枪类型,您可以在任何地方使用它,而无需将其预加载到常量中:

var my_rifle: Rifle

变量铸造

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

想象一下你游戏中的敌人 extends Area2D . 你想让它和玩家碰撞,一个 KinematicBody2D 用一个脚本 PlayerController 附属于它。你用的是 on_body_entered 检测碰撞的信号。对于类型化代码,您检测到的主体将是通用的 PhysicsBody2D 而不是你的 PlayerController_on_body_entered 回调。

你可以检查一下这个 PhysicsBody2D is your Player with the as casting keyword, and using the colon : again to force the variable to use this type. This forces the variable to stick to the `` playercontroller``类型:

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

当我们处理自定义类型时, body 不延伸 PlayerController , the player 变量将设置为 null . 我们可以用这个来检查身体是否是玩家。我们也将得到完整的自动完成的播放器变量感谢该演员。

注解

如果您尝试使用内置类型进行强制转换,但失败了,Godot将抛出一个错误。

安全线

你也可以使用铸造来确保安全的生产线。安全行是godot 3.1中的一个新工具,用于告诉您何时不明确的代码行是类型安全的。正如您可以混合和匹配类型化代码和动态代码一样,godot有时没有足够的信息来知道指令是否会在运行时触发错误。

当您得到一个子节点时,就会发生这种情况。让我们以一个计时器为例:使用动态代码,可以使用 $Timer. GDScript supports duck-typing ,所以即使您的计时器是类型 Timer ,它也是一个 Node 和一个 Object ,它扩展了两个类。使用动态gdscript,您也不关心节点的类型,只要它有您需要调用的方法。

当您得到一个节点时,可以使用casting来告诉godot您期望的类型: ($Timer as Timer)($Player as KinematicBody2D) 等。godot将确保类型正常工作,如果是这样,则脚本编辑器左侧的行号将变为绿色。

Safe vs Unsafe Line

安全与不安全线路

注解

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

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

要定义函数的返回类型,请编写破折号和直角括号。 -> 在声明之后,后跟返回类型:

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 , the bullet 变量将为 null .

预警系统

警告系统补充了类型化的gdscript。它可以帮助您避免在开发过程中很难发现的错误,这可能导致运行时错误。

可以在项目设置中的名为 GDScript

warning system project settings

警告系统项目设置

您可以在脚本编辑器的状态栏中找到活动gdscript文件的警告列表。下面的示例有3个警告:

warning system example

警告系统示例

若要忽略一个文件中的特定警告,请插入表单的特殊注释。 #warning-ignore:warning-id ,或单击警告说明右侧的“忽略”链接。godot将在相应的行上添加注释,代码将不再触发相应的警告:

warning system ignore example

警告系统忽略示例

警告不会阻止游戏运行,但您可以根据需要将其转换为错误。这样,除非修复所有警告,否则您的游戏将无法编译。前往 GDScript 要启用此选项的项目设置部分。以下是与上一个示例相同的文件,打开错误时出现警告:

warnings as errors

警告为错误

无法指定类型的情况

为了总结这个介绍,让我们来介绍一些您不能使用类型提示的情况。以下所有示例 将触发错误 .

不能将枚举用作类型:

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

总结

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