­
学习在GODOT3.1中编写类型化的GDSCRIPT | Godot-StartUP | indienova 独立游戏

Godot-StartUP

创建于:2018-07-28

创建人: Justus

44 信息 154 成员
讨论基于Godot以及Unity引擎的游戏开发经验,理论和最佳实践。共享一些通用思路以启发另一种生产工具中的实践。独立开发群QQ: 122017359

学习在GODOT3.1中编写类型化的GDSCRIPT

JamBob 2018-10-02

文章来源

本文的作者:Nathan Lovato

原文地址在:http://gdquest.com/tutorial/game-design/godot/gdscript/typed-gdscript/

Image title

Godot 3.1的GDscript可选输入语法。你将将学习:

  • 如何在GDscript中使用类型
  • 静态类型能帮助你避免错误

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

Image title

为什么你要学习GDscript的类型定义?

在GDscript中给变量定义类型,Godot可以检测到更多错误!它会在你工作时为你和你的团队提供更多信息,因为当你调用方法时,参数的类型很明显。

如下代码所示,在类 Inventory中有add方法中reference:Item表示参数reference是Item类型,amount : int表示参数amount是int类型

"""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的动态类型和静态类型自动完成的区别。

在你没有给body定义类型的时候,自动完成是没有提示的:

Image title

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

Image title

将来,GDScript类型还将提高代码性能:Just In Time编译和其他编译器改进已经在规划图上了!

总的来说,类型化编程给你提供了一种更结构化的体验。它有助于防止错误并改进脚本的自文档化方面。当你在团队或长期项目中工作时,这一点尤其有用:研究表明,开发人员将大部分时间花在阅读其他人的代码或过去编写的脚本上,而忘记了这些内容。代码越清晰,结构越结构化,理解起来就越快,前进的速度就越快。

如何在GODOT 3.1中使用静态类型?

要定义变量或常量的类型,请在变量名称后面加上一个冒号,后跟其类型。例如var health : int。这会强制变量的类型始终保持不变:

Image title

如果你写一个冒号,Godot会尝试推断类型,但你省略了类型:

Image title

目前你可以使用三种类型的......类型:

  1. 内置类型
  2. 核心类和节点(Object,Node,Area2D,Camera2D,等等)
  3. 你自己的自定义类。查看新的class_name功能以在编辑器中注册类型

自定义变量类型

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

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

变量转换

类型转换是类型化语言中的一个关键概念。我们从另一种类型的类型转换成另一种类型


想象一下你的游戏中的敌人,extends Area2D类型。你想让它与Player发生碰撞,一个extend kinematicBody2D的PlayerController脚本。使用on_body_entered信号来检测碰撞。使用类型化代码,你检测到的主体将是通用的PhysicsBody2D,而不是PlayerController_on_body_entered回调。


你可以PhysicsBody2D使用ascast关键字检查这是否是你的Player ,并:再次使用冒号强制变量使用此类型。这会强制变量保持PlayerController类型:

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

当我们处理自定义类型时,如果body未扩展PlayerController,则player变量将设置为null。我们可以用它来检查身body是否是玩家。通过这样的转换,使用player时可以获得PlayerController全部方法和变量的自动完成

Image title

安全线

你还可以使用转换来确认安全线。安全线是Godot 3.1中的一个新工具,用于告诉你什么时候不明确的代码行是类型安全的。由于你可以混合并匹配键入的和动态的代码,有时,Godot没有足够的信息来判断一条指令是否会在运行时触发错误。


你获得子节点时会发生这种情况。让我们以一个Timer为例:使用动态代码,你可以获取节点$Timer。GDscript支持duck-typing,所以即使你的计时器是类型Timer,它extend自Node和Object。使用动态GDscript,你不需要关心节点的类型,只要它有需要调用的方法。


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

Image title

Image title

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

要定义函数的返回类型,请->在声明后写一个破折号和一个右尖括号,然后是返回类型:

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

类型化(typed)or DYNAMIC:坚持一种风格

类型化GDscript和动态GDscript可以在同一个项目中共存。但我建议,为了代码库的一致性,以及同行的一致性,应该坚持使用这两种风格。如果你遵循相同的指导原则,那么每个人都可以更容易地一起工作,并且可以更快地阅读和理解其他人的代码。


类型化代码需要更多的编写,但是你可以获得我们上面讨论的好处。下面是一个相同的空脚本示例,使用DYNAMIC样式:

extends Node
    func _ready():
        pass
    func _process(delta):
        pass

并使用静态类型:

extends Node
    func _ready() -> void:
        pass
    func _process(delta : float) -> void:
        pass

如你所见,你还可以将类型与引擎virtual methods一起使用。与任何方法一样,信号回调也可以使用类型。下面是动态样式的body_entered信号:

func _on_Area2D_body_entered(body):
    pass

和相同的回调,类型提示:

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

你可以自由替换,例如PhysicsBody2D,使用你自己的类型,自动转换参数:

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

该bullet变量可以持有任何CollisionObject2D,但我们要确保这是我们的Bullet,我们为我们的项目创建了一个节点。如果它是其他任何东西,比如一个Area2D或者没有extend 的任何节点Bullet,那么bullet变量就是null。


警告系统

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

你可以在项目设置中配置警告GDscript:

Image title

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

Image title

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

Image title

警告不会阻止游戏运行,但如果你愿意,可以将它们变成错误。这样,除非你修复所有警告,否则你的游戏将无法编译。前往GDscript项目设置的部分以打开此选项。这是与前一个示例相同的文件,并在启用错误时显示警告:

Image title

无法指定类型的情况

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

你不能将Enums用作类型

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,它已经可以帮助你编写更多结构化代码,帮助你避免常见错误,并创建可扩展系统。将来,由于即将进行的编译器优化,静态类型也将为你带来不错的性能提升。

(转发自:原日志地址

近期喜欢的会员

 

加入 indienova

  • 建立个人/工作室档案
  • 建立开发中的游戏档案
  • 关注个人/工作室动态
  • 寻找合作伙伴共同开发
  • 寻求线上发行
  • 更多服务……
登录/注册