脚本

介绍

在Godot 3.0之前,编写游戏脚本的唯一选择是使用 GDScript . 现在,Godot有四个(是的,四个!)官方语言和动态添加额外脚本语言的能力!

这是很好的,主要是由于提供了大量的灵活性,但它也使我们的工作支持语言更加困难。

不过,Godot中的“主要”语言是GDScript和visualscript。选择它们的主要原因是它们与Godot的集成程度,因为这使得经验更加平滑;两者都有光滑的编辑器集成,而C# 和C++需要在单独的IDE中进行编辑。如果你是静态类型语言的大迷,那么用C#和C++来代替。

GDScript

GDScript 如前所述,是Godot语中使用的主要语言。由于它与Godot的高度集成,与其他语言相比,使用它有一些积极的地方:

  • 它简单、优雅,并且设计为其他语言的用户熟悉,如Lua、Python、Squirrel等。

  • 加载和编译速度极快。

  • 编辑器集成是一种乐趣,与节点、信号和许多其他与正在编辑的场景相关的项目的代码完成。

  • 有内置的向量类型(如向量、转换等),这使得大量使用线性代数非常有效。

  • 与静态类型语言一样有效地支持多个线程——这是使我们避免虚拟机(如Lua、Squirrel等)的限制之一。

  • 不使用垃圾收集器,因此它需要一点自动化(大多数对象都是引用计数的),这是由确定性决定的。

  • 它的动态特性使得如果需要更多的性能,则不需要重新编译引擎,所以很容易优化C++中的代码段(通过GDATA)。

如果您还没有决定,并且有编程经验,特别是动态类型语言,请使用gdscript!

VisualScript

从3.0开始,Godot提供 Visual Scripting . 这是一种典型的“块和连接”语言的实现,但适用于Godot的工作方式。

对于非程序员,甚至对于希望让部分代码更容易被其他人访问的有经验的开发人员(如游戏设计者或艺术家),可视化脚本是一个很好的工具。

程序员也可以使用它来构建状态机或定制的可视化节点工作流——例如,对话系统。

.NET/c#

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

它在性能和易用性之间有一个极好的折衷点,尽管必须知道它的垃圾收集器。

因为Godot使用 Mono .NET运行时,理论上任何第三方.NET库或框架都可以用于在Godot中编写脚本,以及任何符合公共语言基础结构的编程语言,如F、Boo或Clojureclr。但实际上,C#是唯一官方支持的.NET选项。

GDATEST/C++

最后,我们对3版本最聪明的补充:GdEnter允许在C++中编写脚本,而不需要重新编译(或重新启动)Godot。

由于我们使用内部C API桥,所以可以使用任何C++版本,并将生成的共享库的编译器品牌和版本混合在一起工作得很好。

这种语言是性能的最佳选择,不需要在整个游戏中使用,因为其他部分可以用gdscript或可视化脚本编写。然而,API是清晰和易于使用的,因为它类似于,主要是Godot的实际C++ API。

更多的语言可以通过GDNative接口提供,但请记住,我们没有官方支持它们。

编写场景脚本

在本教程的其余部分中,我们将设置一个由按钮和标签组成的GUI场景,在其中按下按钮将更新标签。这将证明:

  • 编写脚本并将其附加到节点。

  • 通过信号连接UI元素。

  • 编写可以访问场景中其他节点的脚本。

在继续之前,请务必阅读 GDScript 参考。这是一种设计简单的语言,参考资料也很短,因此只需几分钟就可以大致了解这些概念。

场景设置

使用从“场景”选项卡访问的“添加子节点”对话框(或按 Ctrl+A )要创建具有以下节点的层次结构:

  • 面板

    • 标签

    • 纽扣

场景树应该如下所示:

../../_images/scripting_scene_tree.png

使用二维编辑器定位和调整按钮和标签的大小,使它们看起来像下面的图像。可以从Inspector选项卡设置文本。

../../_images/label_button_example.png

最后,用名称保存场景,例如 sayhello.tscn .

添加脚本

右键单击面板节点,然后从上下文菜单中选择“附加脚本”:

../../_images/add_script.png

将弹出脚本创建对话框。此对话框允许您设置脚本的语言、类名和其他相关选项。

在gdscript中,文件本身表示类,因此“类名”字段不可编辑。

我们将脚本附加到的节点是一个面板,因此继承字段将自动用“panel”填充。这就是我们想要的,因为脚本的目标是扩展面板节点的功能。

最后,输入脚本的路径名并选择创建:

../../_images/script_create.png

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

../../_images/script_added.png

要编辑脚本,请选择其中一个按钮,这两个按钮都将在上图中突出显示。这将使您进入脚本编辑器,其中将包括默认模板:

../../_images/script_template.png

那里没有什么。这个 _ready() 当节点及其所有子节点进入活动场景时调用函数。 注: _ready() 不是构造函数;而是构造函数 _init() .

脚本的角色

脚本向节点添加行为。它用于控制节点如何工作以及如何与其他节点(子节点、父节点、兄弟节点等)交互。脚本的本地作用域是节点。换句话说,脚本继承该节点提供的函数。

../../_images/brainslug.jpg

处理信号

当某些特定的操作发生时,信号会“发出”,并且它们可以连接到任何脚本实例的任何函数。信号主要在GUI节点中使用,尽管其他节点也有信号,您甚至可以在自己的脚本中定义自定义信号。

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

虽然我们将在本教程系列的其余部分中使用代码方法,但让我们来介绍编辑器接口的工作方式,以供将来参考。

在场景树中选择按钮节点,然后选择“节点”选项卡。接下来,确保您选择了“信号”。

../../_images/signals.png

如果在“baseButton”下选择“pressed()”,然后单击右下角的“connect…”按钮,将打开连接创建对话框。

../../_images/connect_dialogue.png

在左下角是创建连接所需的关键内容:实现要触发的方法(此处表示为nodePath)的节点和要触发的方法的名称。

左上角部分显示场景节点列表,其中发光节点的名称以红色突出显示。在这里选择“panel”节点。选择节点时,底部的节点路径将自动更新,以指向从发射节点到选定节点的相对路径。

默认情况下,方法名称将包含发出节点的名称(在本例中为“button”),从而 _on_[EmitterNode]_[signal_name] . 如果您确实选中了“生成函数”复选按钮,那么编辑器将在设置连接之前为您生成函数。

最后给出了如何使用可视化界面的指南。然而,这是一个脚本教程,所以为了学习,让我们深入到手动过程!

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

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

因为按钮和标签是附加脚本的面板下的同级,所以可以通过在 _ready() 功能:

func _ready():
    get_node("Button")
public override void _Ready()
{
    GetNode("Button");
}

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

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

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

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

最后的脚本应该如下所示:

extends Panel

func _ready():
    get_node("Button").connect("pressed", self, "_on_Button_pressed")

func _on_Button_pressed():
    get_node("Label").text = "HELLO!"
using Godot;

// IMPORTANT: the name of the class MUST match the filename exactly.
// this is case sensitive!
public class sayhello : Panel
{
    public override void _Ready()
    {
        GetNode("Button").Connect("pressed", this, nameof(_OnButtonPressed));
    }

    public void _OnButtonPressed()
    {
        GetNode<Label>("Label").Text = "HELLO!";
    }
}

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

../../_images/scripting_hello.png

为什么,你好!祝贺你编写了第一个场景的脚本。

注解

关于本教程的一个常见误解是 get_node(path) 作品。对于给定节点, get_node(path) 搜索它的直系子女。在上面的代码中,这意味着按钮必须是面板的子级。如果button不是label的子级,则获取它的代码为:

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

另外,请记住节点是由名称引用的,而不是由类型引用的。

注解

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

代码方法还通过第4个 Array 默认为空的参数。请随时阅读 Object.connect 方法获取更多信息。