编写脚本

简介

在Godot 3.0版本之前,编写游戏脚本的唯一选择是使用 GDScript.如今,Godot已经有四种(没错,是四种!)官方语言,并具有动态添加额外的脚本语言的能力!

这很好,主要在于提供了大量的灵活性,但也使得我们对语言的支持工作变得更加困难.

不过,Godot中的”主要”语言是GDScript和VisualScript.选择它们的主要原因是它们与Godot的整合程度,这使得体验更佳顺畅;两者都有非常好的编辑器集成,而C#和C++需要在外部IDE中进行编辑.如果您是静态语言的忠实粉丝,也可以使用C#和C++.

GDScript

如上所述, GDScript 是Godot使用的主要语言.由于与Godot的高度集成,使用它与其他语言相比有一些积极意义:

  • 简单,优雅,设计上为Lua、Python、Squirrel等语言用户所熟悉.

  • 快速加载和编译.

  • 编辑器集成非常令人愉快,有属于被编辑场景的节点、信号、以及其它条目的代码补全功能.

  • 有内建的矢量类型(比如Vector、Transform等),大量使用线性代数时非常有效.

  • 像静态类型语言一样高效地支持多线程——这是我们避免使用诸如Lua、Squirrel等虚拟机的原因之一.

  • 不使用垃圾回收器,因此它以牺牲一小部分自动化为代价,换取了确定性(大多数对象都是引用计数).

  • 如果需要更高的性能,它的动态特性可以很容易地优化C ++中的代码段(通过GDNative),而无需重新编译引擎.

如果还没决定好,并且对编程有经验,特别是动态类型语言,那就选择GDScript吧!

可视化脚本

从3.0开始,Godot开始支持 可视化脚本.这是”节点和连线”语言的典型实现,但适用于Godot的开发方式.

对非程序员来说,可视化编程是一个很好的工具,甚至对于经验丰富的开发人员来说也是如此,他们想让部分代码易于被他人访问,比如游戏设计师和美术工作者.

程序员也可以用它创建状态机或自定义可视节点工作流程,例如对话系统.

.NET / C

由于微软的C#是游戏开发者的最爱,我们增加了官方支持.C#是一种成熟的语言,人们为它编写了大量代码,而且支持增加得益于微软的慷慨捐赠.

它在性能和易用性之间有很好的折衷,尽管必须注意它的垃圾回收器.

由于Godot使用 Mono .NET运行时,因此理论上任何第三方.NET库或框架都可用于编写Godot脚本,以及任何符合通用语言标准架构的语言,如F#、Boo或ClojureCLR.但实际上,C#是唯一官方支持的.NET选项.

GDNative / C++

最后是3.0版本的一个亮点:GDNative允许使用C++编写脚本,无需重新编译(甚至都不用重启)Godot.

任意C++版本都可以使用,并且由于我们使用内部C API桥接,因此生成的共享库使用的混合编译器品牌和版本可以完美地工作.

这种语言是性能上的最佳选择,它不需要在整个游戏中都使用,因为其他部分可以用GDScript或Visual Script编写.它的API清晰且易于使用,类似于Godot的实际C++ API.

通过GDNative接口可以使用更多语言,但请记住我们没有官方支持.

编写场景脚本

在教程剩下的部分里,我们将建立一个GUI场景,包含一个按钮和一个标签,当按下按钮时会更新标签.这将会演示:

  • 编写脚本并附加到节点上.

  • 通过信号来连接到UI元素.

  • 编写脚本,使其能访问场景中的其它节点.

在继续之前,请确保先略过并标记 GDScript 参考. 这是一种设计得很简单的语言,并且该参考文献各部分,以使您可以更容易地获得概念的概述.

场景设置

如果上一教程的”实例化”项目仍然开着,请将它关闭(“项目”->”退出到项目列表”)并创建一个新项目.

使用从”添加子场景/Add Child Node”选项访问的”添加子节点”对话框(或通过按Ctrl + A键)来创建具有以下节点的层次结构:

  • 面板

    • 标签

    • 按钮

场景树看起来应该是这个样子:

../../_images/scripting_scene_tree.png

使用2D编辑器定位并调整按钮和标签的大小,使其看起来像下面的样子.可以在 属性检查器(Inspector) 选项卡中设置文本.

../../_images/label_button_example.png

最后,保存场景,用例如 sayhello.tscn 这样的名字.

添加脚本

右击面板(Panel)节点, 在弹出菜单中选择 附加脚本(Attach Script):

../../_images/add_script.png

脚本创建对话框会弹出.这个对话框允许您设置脚本语言、类名和其它相关选项.

在GDScript里,文件本身代表了类,所以类名一栏无法编辑.

我们要附加脚本的节点是个Panel(面板),所以继承一栏会被自动填入``Panel``.这是我们想要的,因为脚本的目的就是扩展这个面板节点的功能.

最后,输入脚本的路径名然后选择新建(Create):

../../_images/script_create.png

然后将创建脚本并将其添加到节点上.您可以在”场景”选项卡中的节点旁边的”打开脚本”图标,以及”属性检查器”下的脚本属性中看到:

../../_images/script_added.png

要编辑脚本,请选择这两个按钮中的一个,在上图中两个按钮都被高亮突出显示.这将带您到脚本编辑器,其中将包含默认模板:

../../_images/script_template.png

这里没有多少内容.``_ready()`` 函数在节点及其所有子节点进入活动场景时被调用.**注意:** _ready() 并不是构造器;构造器是 _init().

脚本的作用

脚本为节点添加行为.它用于控制节点的功能以及与其他节点(子节点,父节点,兄弟姐妹等)的交互方式.脚本的局部作用域是节点.换句话说,脚本继承了该节点提供的功能.

../../_images/brainslug.jpg

处理信号

当某种特定类型的动作发生时,信号被”发出”,并且它们可以连接到任意脚本实例的任意函数.信号主要用于GUI节点,尽管其他节点也有信号,您甚至可以在自己的脚本中定义自定义信号.

在此步骤中,我们将”按下”信号连接到自定义函数.形成连接是第一部分,定义自定义功能是第二部分.对于第一部分,Godot提供了两种创建连接的方式:通过编辑器提供的可视界面、或通过代码.

虽然在本系列教程的其余部分中,我们将使用代码方法,但现在还是让我们介绍一下编辑器界面的工作方式,以备将来参考.

在场景树中选择 Button 节点,然后选择 节点(Node) 选项卡.接下来,确保已选择 信号(Signals).

../../_images/signals.png

如果稍后在 BaseButton 下选择 pressed() 并单击右下角的 Connect ... 按钮,则将打开连接创建对话框.

../../_images/connect_dialogue.png

对话框的顶部显示了场景的节点列表,其中发射节点的名称会以蓝色突出显示.在这里选择”面板”节点.

对话框的底部显示了将要创建的方法的名称.方法名字默认会包含发出节点的名字(在当前例子中为”Button”),即:”_on_[EmitterNode]_[signal_name]“.

至此,已经介绍了有关如何使用可视界面的指南.但是,这是一个脚本教程,因此,为了学习起见,让我们深入研究手动过程!

为此,我们将介绍一个可能是Godot程序员最常使用的函数: Node.get_node().此函数使用场景中相对于拥有脚本的节点的节点路径来获取节点.

为了方便起见,删除 extends Panel 下面的所有内容.您将手动填写脚本的其余部分.

由于Button和Label是有脚本附加的Panel下的兄弟节点,因此可以通过在 _ready() 函数下输入以下内容来获取Button引用:

GDScript

C#

  1. func _ready():
  2. get_node("Button")
  1. public override void _Ready()
  2. {
  3. GetNode("Button");
  4. }

接下来,编写一个在按下按钮时将被调用的函数:

GDScript

C#

  1. func _on_Button_pressed():
  2. get_node("Label").text = "HELLO!"
  1. public void _OnButtonPressed()
  2. {
  3. GetNode<Label>("Label").Text = "HELLO!";
  4. }

最后,使用 Object.connect() 将按钮的”按下”信号连接到 _ready().

GDScript

C#

  1. func _ready():
  2. get_node("Button").connect("pressed", self, "_on_Button_pressed")
  1. public override void _Ready()
  2. {
  3. GetNode("Button").Connect("pressed", this, nameof(_OnButtonPressed));
  4. }

脚本最终看起来是这个样子:

GDScript

C#

  1. extends Panel
  2. func _ready():
  3. get_node("Button").connect("pressed", self, "_on_Button_pressed")
  4. func _on_Button_pressed():
  5. get_node("Label").text = "HELLO!"
  1. using Godot;
  2. // IMPORTANT: the name of the class MUST match the filename exactly.
  3. // this is case sensitive!
  4. public class sayhello : Panel
  5. {
  6. public override void _Ready()
  7. {
  8. GetNode("Button").Connect("pressed", this, nameof(_OnButtonPressed));
  9. }
  10. public void _OnButtonPressed()
  11. {
  12. GetNode<Label>("Label").Text = "HELLO!";
  13. }
  14. }

运行场景并按下按钮,您应该得到以下结果:

../../_images/scripting_hello.png

您好啊!恭喜您完成了您的第一个场景脚本.

注解

关于本教程的一个常见的误解是 get_node(path) 的工作原理.对于给定的节点,``get_node(path)`` 搜索其直接子节点.在上面的代码中,这意味着Button必须是Panel的子项.如果Button是Label的子项,则获得它的代码是:

GDScript

C#

  1. # Not for this case,
  2. # but just in case.
  3. get_node("Label/Button")
  1. // Not for this case,
  2. // but just in case.
  3. GetNode("Label/Button")

另外,请记住,节点是通过名称而不是类型来引用的.

注解

连接对话框的右侧面板用于将特定值绑定到所连接函数的参数.您可以添加和删除不同类型的值.

代码方法还使用第四个 Array 参数启用此功能,该参数默认为空.随时阅读 Object.connect 方法以获取更多信息.