何时使用场景与脚本

我们已经介绍了场景和脚本的不同之处. 脚本使用命令性代码定义引擎类扩展, 而场景使用声明性代码.

结果, 每个系统的功能都不同. 场景可以定义扩展类的初始化方式, 但不能定义其实际行为. 场景通常与脚本结合使用, 以便场景充当脚本声明性代码的扩展.

匿名类型

单独使用脚本 可以 完全定义场景的内容. 从本质上讲,Godot编辑器所做的, 仅在其对象的C++构造函数中.

但是, 选择哪个来使用, 可能是一个两难问题. 创建脚本实例与创建引擎类相同, 而处理场景需要更改API:

GDScript

C#

  1. const MyNode = preload("my_node.gd")
  2. const MyScene = preload("my_scene.tscn")
  3. var node = Node.new()
  4. var my_node = MyNode.new() # Same method call
  5. var my_scene = MyScene.instance() # Different method call
  6. var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene
  1. using System;
  2. using Godot;
  3. public class Game : Node
  4. {
  5. public readonly Script MyNodeScr = (Script)ResourceLoader.Load("MyNode.cs");
  6. public readonly PackedScene MySceneScn = (PackedScene)ResourceLoader.Load("MyScene.tscn");
  7. public Node ANode;
  8. public Node MyNode;
  9. public Node MyScene;
  10. public Node MyInheritedScene;
  11. public Game()
  12. {
  13. ANode = new Node();
  14. MyNode = new MyNode(); // Same syntax
  15. MyScene = MySceneScn.Instance(); // Different. Instantiated from a PackedScene
  16. MyInheritedScene = MySceneScn.Instance(PackedScene.GenEditState.Main); // Create scene inheriting from MyScene
  17. }
  18. }

此外, 由于引擎和脚本代码之间的速度差异, 脚本的运行速度将比场景慢一些. 节点越大和越复杂, 将它构建为场景的理由就越多.

命名的类型

在某些情况下, 用户可以在编辑器内, 将脚本注册为新类型. 在节点或资源创建对话框中, 将其显示为新类型, 并带有可选图标. 在这些情况下, 用户使用脚本的能力更加流畅. 而不必…

  1. 了解他们想要使用的脚本的基本类型.

  2. 创建一个该基本类型的实例.

  3. 将脚本添加到节点.

    1. (拖放方法)

      1. 在文件系统停靠面板中找到脚本.

      2. 将脚本拖放到场景停靠面板中的节点上.

    2. (属性方法)

      1. 向下滚动到属性检查器的底部, 找到 脚本 属性并选择它.

      2. 从下拉列表中选择 加载.

      3. 从文件对话框中选择脚本.

使用一个注册的脚本, 脚本类型将像系统中的其他节点和资源一样成为创建选项. 不需要做任何上述工作. 创建对话框甚至还有一个搜索栏, 用于按名称查找类型.

有两种用于注册类型的系统…

  • 自定义类型

    • 仅限编辑器. 类型名称在运行时中不可访问.

    • 不支持继承的自定义类型.

    • 一个初始化工具. 使用脚本创建节点. 仅此而已.

    • 编辑器没有对该脚本的类型感知, 或其与其他引擎类型或脚本的关系.

    • 允许用户定义一个图标.

    • 适用于所有脚本语言, 因为它抽象处理脚本资源.

    • 设置使用 EditorPlugin.add_custom_type.

  • Script Classes

    • 编辑器和运行时均可访问.

    • 显示全部继承关系.

    • 使用脚本创建节点, 但也可以从编辑器更改或扩展类型.

    • 编辑器知道脚本, 脚本类和引擎c++类之间的继承关系.

    • 允许用户定义一个图标.

    • 引擎开发人员必须手动添加对语言的支持(名称公开和运行时可访问性两者).

    • 仅适用于Godot 3.1+版本.

    • 编辑器扫描项目文件夹, 并为所有脚本语言注册任何公开的名称. 为公开此信息, 每种脚本语言都必须实现自己的支持.

这两种方法都向创建对话框添加名称, 特别是脚本类, 还允许用户在不加载脚本资源的情况下访问类别名称. 在任何地方都可以创建实例, 和访问常量或静态方法.

有了这些功能, 由于它赋予用户易用性, 人们可能希望它们的类型是没有场景的脚本. 那些正在开发的插件或创建供设计人员使用的内部工具, 将以这种方式使事情变得更轻松.

不足之处在于, 这也意味着很大程度上必须使用命令式编程.

Script 与 PackedScene 的性能

在选择场景和脚本时, 最后一个需要考虑的方面是执行速度.

随着对象内容的增加, 脚本创建和初始化所需的内容也会大大增加. 创建节点层次结构就说明了这一点. 每个Node的逻辑可能有几百行代码.

下面的代码示例创建一个新的 Node, 更改名称, 分配脚本, 将其未来的父级设置为其所有者, 以便保存到磁盘中, 最后将其添加为 “主” 节点的子级:

GDScript

C#

  1. # Main.gd
  2. extends Node
  3. func _init():
  4. var child = Node.new()
  5. child.name = "Child"
  6. child.script = preload("Child.gd")
  7. child.owner = self
  8. add_child(child)
  1. using System;
  2. using Godot;
  3. public class Main : Resource
  4. {
  5. public Node Child { get; set; }
  6. public Main()
  7. {
  8. Child = new Node();
  9. Child.Name = "Child";
  10. Child.Script = ResourceLoader.Load<Script>("child.gd");
  11. Child.Owner = this;
  12. AddChild(Child);
  13. }
  14. }

这样的脚本代码比引擎端的C++代码要慢很多. 每条指令都要调用脚本API, 导致后端要进行多次 “查找”, 以找到要执行的逻辑.

场景有助于避免这个性能问题。PackedScene (场景包)是场景继承的基础类型,定义了使用序列化数据创建对象的资源。引擎可以在后端批量处理场景,并提供比脚本好得多的性能。

总结

最后, 最好的方法是考虑以下几点:

  • 如果希望创建一个基本工具, 它将在几个不同的项目中重用, 以及可能提供给不同技能水平的人使用.(包括那些不认为自己是个程序员的用户), 它很可能是一个脚本, 有一个自定义名称/图标.

  • 如果有人想创造一个特定于他们的游戏的概念, 那么它应该是一个场景. 场景比脚本更容易跟踪/编辑, 并提供更多的安全性.

  • 如果有人想命名一个场景, 他们仍然可以在3.1版本中, 通过声明一个脚本类并给它一个场景作为常量来实现这一点. 实际上, 该脚本变成了一个命名空间:

    GDScript

    1. # game.gd
    2. extends Reference
    3. class_name Game # extends Reference, so it won't show up in the node creation dialog
    4. const MyScene = preload("my_scene.tscn")
    5. # main.gd
    6. extends Node
    7. func _ready():
    8. add_child(Game.MyScene.instance())