资源

节点和资源

在本教程之前, 我们重点研究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() 函数:

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 = GetNode<Sprite>("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() 函数速度相当快, 您可以创建新的敌人, 子弹, 效果等, 而无需每次都从磁盘再次加载它们. 请记住, 像往常一样, 图像, 网格等都是在场景实例之间共享的.

释放资源

资源(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 使在属性检查器面板中创建自定义 资源(Resources) 变得很容易.

  1. 在属性检查器面板中创建一个普通的 资源(Resources) 对象. 只要您的脚本扩展了该类型, 它甚至可以是派生 资源(Resource) 的类型.

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

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

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

让我们看一些示例.

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

# Make sure that every parameter has a default value.
# Otherwise, there will be problems with creating and editing
# your resource via the inspector.
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; }

        // Make sure that every parameter has a default value.
        // Otherwise, there will be problems with creating and editing
        // your resource via the inspector.
        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'.
            }
        }
    }
}

注解

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

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

# 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);
    }
}

不仅可以内联 字典(Dictionary) 值, 还可以选择...

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

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

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

警告

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

在下面的示例中,Godot将加载 Node 脚本, 并看到它没有扩展 Resource, 然后判断脚本由于类型不兼容而无法为 资源(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 System;
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);
    }
}