资源

节点和资源

在本教程之前, 我们重点研究Godot中的 Node 类, 因为它是您用来编码行为的类, 并且引擎的大多数功能都依赖于该类. 还有另一个同样重要的数据类型: Resource.

节点 为您提供功能: 它们绘制精灵, 3D模型, 模拟物理, 排列用户界面等. 资源数据容器 . 它们自己不能做任何事情: 而是, 节点使用资源中包含的数据.

Godot从磁盘保存或加载的任何内容都是一种资源. 无论它是场景( .tscn.scn 文件), 图像, 脚本……这是一些 资源 示例: Texture, Script, Mesh, Animation, AudioStream, Font, Translation.

当引擎从磁盘加载资源时, 它只加载一次 . 如果该资源的副本已在内存中, 则每次尝试再次加载该资源将返回相同的副本. 由于资源只包含数据, 因此无需复制它们.

每个对象(无论是节点还是资源)都可以导出属性. 属性有很多类型, 例如String, integer, Vector2等, 并且任何这些类型都可以成为资源. 这意味着节点和资源都可以包含资源以作为属性:

../../_images/nodes_resources.png

外部与内置

有两种保存资源的方法. 它们是:

  1. 外部 , 对于场景, 作为单独文件保存在磁盘上.

  2. 内置,保存在它们所附加的 .tscn.scn 文件内。

更具体地说, 这是一个 Sprite 节点中的一个 Texture:

../../_images/spriteprop.png

点击资源预览可以使我们查看和编辑资源的属性.

../../_images/resourcerobi.png

Path 属性告诉我们资源来自何处. 在这里, 它来自一个叫 robi.png 的PNG图像. 当资源来自这样的文件时, 它属于外部资源. 如果您去掉这个路径或此路径为空, 则它将成为内置资源.

保存场景时, 将在内置资源和外部资源之间进行切换. 在上面的示例中, 如果删除路径 "res://robi.png" 并保存,Godot会将图像保存在 .tscn 场景文件中.

备注

即使您保存一个内置资源, 当多次实例化一个场景时, 引擎也只会加载该场景的一个副本.

从代码中加载资源

有两种方法可以从代码加载资源. 首先, 您可以随时使用 load() 函数:

GDScriptC#

  1. func _ready():
  2. var res = load("res://robi.png") # Godot loads the Resource when it reads the line.
  3. get_node("sprite").texture = res
  1. public override void _Ready()
  2. {
  3. var texture = (Texture)GD.Load("res://robi.png"); // Godot loads the Resource when it reads the line.
  4. var sprite = GetNode<Sprite>("sprite");
  5. sprite.Texture = texture;
  6. }

您也可以 预加载(preload) 资源. 与 load 不同, preload 会从硬盘中读取文件, 并在编译时加载它. 因此, 您不能使用一个变量化的路径调用预加载: 您需要使用常量字符串.

GDScriptC#

  1. func _ready():
  2. var res = preload("res://robi.png") # Godot loads the resource at compile-time
  3. get_node("sprite").texture = res
  1. // 'preload()' is unavailable in C Sharp.

加载场景

场景也是资源, 但也有一个陷阱. 保存到磁盘的场景是 PackedScene 类型的资源. 该场景被打包在资源内部.

要获取场景的实例, 您必须使用 PackedScene.instance() 方法.

GDScriptC#

  1. func _on_shoot():
  2. var bullet = preload("res://bullet.tscn").instance()
  3. add_child(bullet)
  1. private PackedScene _bulletScene = (PackedScene)GD.Load("res://bullet.tscn");
  2. public void OnShoot()
  3. {
  4. Node bullet = _bulletScene.Instance();
  5. AddChild(bullet);
  6. }

此方法在场景的层次结构中创建节点, 对其进行配置, 然后返回场景的根节点. 然后, 您可以将其添加为任何其他节点的子级.

该方法有几个优点. 由于 PackedScene.instance() 函数速度相当快, 您可以创建新的敌人, 子弹, 效果等, 而无需每次都从磁盘再次加载它们. 请记住, 像往常一样, 图像, 网格等都是在场景实例之间共享的.

释放资源

资源(Resource) 不再使用时, 它将自动释放自己. 由于在大多数情况下, 资源包含在节点中, 因此当您释放节点时, 如果没有其他节点使用该节点拥有的所有资源, 则引擎也会释放它们.

创建自己的资源

像Godot中的任何Object一样, 用户也可以编写资源脚本. 资源脚本继承了object类属性和序列化文本或二进制数据( *.tres , *.res )之间自由转换的能力. 它们还从 Reference 类型继承引用计数内存管理.

与其他替代数据结构(如JSON, CSV或自定义TXT文件)相比, 它具有许多明显的优势. 用户只能将这些资源导入为 Dictionary (JSON)或要解析的 File. 将资源区分开来的是它们对 Object, Reference, 和 Resource 功能的继承:

  • 它们可以定义常量, 因此不需要其他数据字段或对象中的常量.

  • 它们可以定义方法, 包括属性的 setter/getter 方法. 这允许对基础数据进行抽象和封装. 如果资源脚本的结构需要更改, 则使用资源的游戏则不必更改.

  • 它们可以定义信号, 因此 Resources 可以触发对所管理数据更改的响应.

  • 它们具有已定义的属性, 因此用户知道其数据将100%存在.

  • 资源自动序列化和反序列化是一个Godot引擎的内置功能. 用户无需实现自定义逻辑即可导入/导出资源文件的数据.

  • 资源甚至可以递归地序列化子资源, 这意味着用户可以设计更复杂的数据结构.

  • 用户可以将资源保存为版本控制友好的文本文件(*.tres). 导出游戏后,Godot将资源文件序列化为二进制文件(*.res), 以提高速度和压缩率.

  • Godot 引擎的检查器开箱即用地渲染和编辑资源文件。这样,用户通常不需要实现自定义逻辑即可可视化或编辑其数据。为此,请在文件系统面板中双击资源文件,或在检查器中点击文件夹图标,然后在对话框中打开该文件。

  • 它们可以扩展除基本 Resource 之外的其他资源类型。

Godot 可以轻松地在检查器面板中创建自定义 Resource。

  1. 在检查器面板中创建一个普通的 Resource 对象。只要是扩展自 Resource 的类型,你的脚本就可以去扩展。

  2. 将检查器中的 script 属性设置为您的脚本。

现在,检查器将显示 Resource 脚本的自定义属性。如果编辑这些值并保存资源,则检查器也会序列化自定义属性!要从检查器中保存资源,请点击检查器的工具菜单(右上角),然后选择“保存”或“另存为…”。

如果脚本的语言支持脚本类,则可以简化该过程。仅为脚本定义名称会将其添加到“检查器”的创建对话框。这会将脚本自动添加到您创建的 Resource 对象中。

让我们看一些示例。

GDScriptC#

  1. # bot_stats.gd
  2. extends Resource
  3. export(int) var health
  4. export(Resource) var sub_resource
  5. export(Array, String) var strings
  6. # Make sure that every parameter has a default value.
  7. # Otherwise, there will be problems with creating and editing
  8. # your resource via the inspector.
  9. func _init(p_health = 0, p_sub_resource = null, p_strings = []):
  10. health = p_health
  11. sub_resource = p_sub_resource
  12. strings = p_strings
  13. # bot.gd
  14. extends KinematicBody
  15. export(Resource) var stats
  16. func _ready():
  17. # Uses an implicit, duck-typed interface for any 'health'-compatible resources.
  18. if stats:
  19. print(stats.health) # Prints '10'.
  1. // BotStats.cs
  2. using System;
  3. using Godot;
  4. namespace ExampleProject {
  5. public class BotStats : Resource
  6. {
  7. [Export]
  8. public int Health { get; set; }
  9. [Export]
  10. public Resource SubResource { get; set; }
  11. [Export]
  12. public String[] Strings { get; set; }
  13. // Make sure that every parameter has a default value.
  14. // Otherwise, there will be problems with creating and editing
  15. // your resource via the inspector.
  16. public BotStats(int health = 0, Resource subResource = null, String[] strings = null)
  17. {
  18. Health = health;
  19. SubResource = subResource;
  20. Strings = strings ?? new String[0];
  21. }
  22. }
  23. }
  24. // Bot.cs
  25. using System;
  26. using Godot;
  27. namespace ExampleProject {
  28. public class Bot : KinematicBody
  29. {
  30. [Export]
  31. public Resource Stats;
  32. public override void _Ready()
  33. {
  34. if (Stats != null && Stats is BotStats botStats) {
  35. GD.Print(botStats.Health); // Prints '10'.
  36. }
  37. }
  38. }
  39. }

备注

资源脚本类似于 Unity 的 ScriptableObject。检查器为自定义资源提供内置支持。如果需要的话,用户甚至可以设计自己的基于 Control 控件的工具脚本,并将它们与一个 EditorPlugin 结合起来,以为他们的数据创建自定义的可视化和编辑器。

虚幻 4 引擎的数据表 DataTable 和 CurveTable 也很容易使用资源脚本重新创建。数据表 DataTable 是映射到自定义结构的字符串,类似于将字符串映射到辅助自定义资源脚本的字典。

GDScriptC#

  1. # bot_stats_table.gd
  2. extends Resource
  3. const BotStats = preload("bot_stats.gd")
  4. var data = {
  5. "GodotBot": BotStats.new(10), # Creates instance with 10 health.
  6. "DifferentBot": BotStats.new(20) # A different one with 20 health.
  7. }
  8. func _init():
  9. print(data)
  1. using System;
  2. using Godot;
  3. public class BotStatsTable : Resource
  4. {
  5. private Godot.Dictionary<String, BotStats> _stats = new Godot.Dictionary<String, BotStats>();
  6. public BotStatsTable()
  7. {
  8. _stats["GodotBot"] = new BotStats(10); // Creates instance with 10 health.
  9. _stats["DifferentBot"] = new BotStats(20); // A different one with 20 health.
  10. GD.Print(_stats);
  11. }
  12. }

除了内联 Dictionary 值之外,还可以选择……

  1. 从电子表格导入值表并生成这些键值对,或者……

  2. 在编辑器中设计可视化,并创建一个简单的插件,可在当您打开这些类型的 Resource 时,将其添加到检查器中。

CurveTable 是相同的东西,除了映射到一个浮点值数组或一个 Curve/ Curve2D 资源对象之外。

警告

请注意,资源文件(*.tres/*.res)将在文件中存储它们使用的脚本的路径。加载后,它们将获取并加载此脚本作为其类型的扩展。这意味着尝试指定一个子类,即脚本的内部类(例如在 GDScript 中使用 class 关键字)将不起作用。Godot 将无法正确序列化脚本子类上的自定义属性。

在下面的示例中,Godot 将加载 Node 脚本,并看到它没有扩展 Resource,然后判断脚本由于类型不兼容而无法为 Resource 对象加载。

GDScriptC#

  1. extends Node
  2. class MyResource:
  3. extends Resource
  4. export var value = 5
  5. func _ready():
  6. var my_res = MyResource.new()
  7. # This will NOT serialize the 'value' property.
  8. ResourceSaver.save("res://my_res.tres", my_res)
  1. using System;
  2. using Godot;
  3. public class MyNode : Node
  4. {
  5. public class MyResource : Resource
  6. {
  7. [Export]
  8. public int Value { get; set; } = 5;
  9. }
  10. public override void _Ready()
  11. {
  12. var res = new MyResource();
  13. // This will NOT serialize the 'Value' property.
  14. ResourceSaver.Save("res://MyRes.tres", res);
  15. }
  16. }