资源

节点和资源

在本教程中,我们将重点放在 Node 在Godot中的类,因为这是用来编写行为的代码,并且大多数引擎的特性都依赖于它。还有另一个数据类型同样重要: Resource .

结点 提供功能:绘制精灵、三维模型、模拟物理、安排用户界面等。 资源数据容器 . 它们自己不做任何事情:相反,节点使用资源中包含的数据。

Godot从磁盘中保存或加载的任何内容都是资源。无论是场景(一个.tscn或.scn文件)、图像、脚本…这是一些 Resource 示例: TextureScriptMeshAnimationAudioStreamFontTranslation .

当引擎从磁盘加载资源时, 它只加载一次 . 如果该资源的副本已在内存中,则再次尝试加载该资源时,每次都会返回相同的副本。因为资源只包含数据,所以不需要复制它们。

每个对象,无论是节点还是资源,都可以导出属性。有许多类型的属性,如字符串、整数、向量2等,这些类型中的任何一个都可以成为资源。这意味着节点和资源都可以包含资源作为属性:

../../_images/nodes_resources.png

外部与内置

有两种方法可以节省资源。它们可以是:

  1. 外部的 到一个场景,作为单个文件保存在磁盘上。

  2. Built-in ,保存在 * .tscn或 * .scn文件。

更具体地说,这里是 Texture 在一个 Sprite 节点:

../../_images/spriteprop.png

单击资源预览允许我们查看和编辑资源的属性。

../../_images/resourcerobi.png

path属性告诉我们资源来自何处。在本例中,它来自一个名为 robi.png . 当资源来自这样的文件时,它是一个外部资源。如果删除路径或此路径为空,则它将成为内置资源。

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

注解

即使您保存了一个内置资源,当您多次实例一个场景时,引擎也只会加载其中一个副本。

从代码加载资源

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

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

你也可以 preload 资源。不像 load ,此函数将从磁盘读取文件并在编译时加载。因此,不能使用变量路径调用preload:需要使用常量字符串。

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

正在加载场景

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

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

func _on_shoot():
        var bullet = preload("res://bullet.tscn").instance()
        add_child(bullet)
private PackedScene _bulletScene = (PackedScene)GD.Load("res://bullet.tscn");

public void OnShoot()
{
    Node bullet = _bulletScene.Instance();
    AddChild(bullet);
}

此方法创建场景层次结构中的节点,配置它们,并返回场景的根节点。然后可以将其添加为任何其他节点的子节点。

这种方法有几个优点。作为 PackedScene.instance() 功能很快,您可以创建新的敌人、子弹、效果等,而不必每次都从磁盘上重新加载它们。请记住,像往常一样,图像、网格等都在场景实例之间共享。

释放资源

当A Resource 不再使用,它将自动释放自己。因为在大多数情况下,资源都包含在节点中,所以当释放节点时,如果没有其他节点使用资源,引擎也会释放它拥有的所有资源。

创建自己的资源

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

与其他数据结构(如JSON、CSV或自定义TXT文件)相比,这具有许多明显的优势。用户只能将这些资产作为 Dictionary (json)或作为 File 解析。使资源与众不同的是它们的继承 ObjectReferenceResource 特征:

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

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

  • 它们可以定义信号,这样资源就可以触发对所管理数据更改的响应。

  • 他们定义了属性,所以用户100%知道他们的数据将存在。

  • 资源自动序列化和反序列化是内置的Godot引擎功能。用户不需要实现自定义逻辑来导入/导出资源文件的数据。

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

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

  • Godot引擎的检查器可以直接呈现和编辑资源文件。因此,用户通常不需要实现自定义逻辑来可视化或编辑数据。为此,双击文件系统基座中的资源文件,或单击检查器中的文件夹图标,然后在对话框中打开该文件。

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

警告

资源和字典都是通过引用传递的,但只有资源是引用计数的。这意味着,如果在对象之间传递字典,并且删除第一个对象,则所有其他对象对字典的引用都将无效。相反,资源将不会从内存中释放,直到 all 对象将被删除。

extends Node

class MyObject:
    extends Object
    var dict = {}

func _ready():
    var obj1 = MyObject.new()
    var obj2 = MyObject.new()
    obj1.dict.greeting = "hello"
    obj2.dict = obj1.dict             # 'obj2.dict' now references 'obj1's Dictionary.
    obj1.free()                       # 'obj1' is freed and the Dictionary too!
    print(obj2.dict.greeting)         # Error! 'greeting' index accessed on null instance!

    # To avoid this, we must manually duplicate the Dictionary.
    obj1 = MyObject.new()
    obj1.dict.greeting = "hello"
    obj2.dict = obj1.dict.duplicate() # Now we are passing a copy, not a reference.
    obj1.free()                       # obj2's Dictionary still exists.
    print(obj2.dict.greeting)         # Prints 'hello'.

Godot使在检查器中创建自定义资源变得容易。

  1. 在检查器中创建一个普通资源对象。这甚至可以是派生资源的类型,只要脚本扩展该类型。

  2. 设置 script 属性作为脚本。

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

如果脚本的语言支持 script classes ,然后简化流程。单独为脚本定义一个名称将把它添加到检查器的创建对话框中。这将自动将脚本添加到您创建的资源对象中。

让我们看一些例子。

# bot_stats.gd
extends Resource
export(int) var health
export(Resource) var sub_resource
export(Array, String) var strings

func _init(p_health = 0, p_sub_resource = null, p_strings = []):
    health = p_health
    sub_resource = p_sub_resource
    strings = p_strings

# bot.gd
extends KinematicBody

export(Resource) var stats

func _ready():
    # Uses an implicit, duck-typed interface for any 'health'-compatible resources.
    if stats:
        print(stats.health) # Prints '10'.
// BotStats.cs
using System;
using Godot;

namespace ExampleProject {
    public class BotStats : Resource
    {
        [Export]
        public int Health { get; set; }

        [Export]
        public Resource SubResource { get; set; }

        [Export]
        public String[] Strings { get; set; }

        public BotStats(int health = 0, Resource subResource = null, String[] strings = null)
        {
            Health = health;
            SubResource = subResource;
            Strings = strings ?? new String[0];
        }
    }
}

// Bot.cs
using System;
using Godot;

namespace ExampleProject {
    public class Bot : KinematicBody
    {
        [Export]
        public Resource Stats;

        public override void _Ready()
        {
            if (Stats != null && Stats is BotStats botStats) {
                GD.Print(botStats.Health); // Prints '10'.
            }
        }
    }
}

注解

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

Unreal Engine4的数据表和曲线表也很容易用资源脚本重新创建。数据表是映射到自定义结构的字符串,类似于将字符串映射到辅助自定义资源脚本的字典。

# bot_stats_table.gd
extends Resource

const BotStats = preload("bot_stats.gd")

var data = {
    "GodotBot": BotStats.new(10), # Creates instance with 10 health.
    "DifferentBot": BotStats.new(20) # A different one with 20 health.
}

func _init():
    print(data)
using System;
using Godot;

public class BotStatsTable : Resource
{
    private Godot.Dictionary<String, BotStats> _stats = new Godot.Dictionary<String, BotStats>();

    public BotStatsTable()
    {
        _stats["GodotBot"] = new BotStats(10); // Creates instance with 10 health.
        _stats["DifferentBot"] = new BotStats(20); // A different one with 20 health.
        GD.Print(_stats);
    }
}

除了输入字典值之外,还可以选择…

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

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

除了映射到浮点值数组或 Curve/Curve2D 资源对象。

警告

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

在下面的示例中,Godot将加载 Node 脚本,注意不要扩展 Resource ,然后确定无法为资源对象加载脚本,因为类型不兼容。

extends Node

class MyResource:
    extends Resource
    export var value = 5

func _ready():
    var my_res = MyResource.new()

    # This will NOT serialize the 'value' property.
    ResourceSaver.save("res://my_res.tres", my_res)
using Godot;

public class MyNode : Node
{
    public class MyResource : Resource
    {
        [Export]
        public int Value { get; set; } = 5;
    }

    public override void _Ready()
    {
        var res = new MyResource();

        // This will NOT serialize the 'Value' property.
        ResourceSaver.Save("res://MyRes.tres", res);
    }
}