Ресурсы

Узлы и Ресурсы

Up to this tutorial, we focused on the Node class in Godot as that’s the one you use to code behavior and most of the engine’s features rely on it. There is another datatype that is just as important: Resource.

Узлы дают вам функциональность: они рисуют спрайты, 3D модели, моделируют физику, организовывают UI и т.д. Ресурсы являются контейнерами данных. Они ничего не делают сами по себе: вместо этого узлы используют данные, содержащиеся в ресурсах.

Все, что Godot сохраняет на диск или загружает с него, является ресурсом. Будь то сцена (.tscn или .scn файл), изображение, скрипт… Вот несколько примеров Texture, Script, Mesh, Animation, AudioStream, Font, Translation.

Когда движок загружает ресурс с диска, это загружается только один раз. Если копия этого ресурса уже находится в памяти, при попытке загрузить ресурс снова будет каждый раз возвращаться одна и та же копия. Поскольку ресурсы содержат только данные, нет необходимости их дублировать.

Каждый объект, будь то Узел или Ресурс, может экспортировать свойства. Существует множество типов свойств, таких как String, integer, Vector2 и т.д., и любой из этих типов может стать ресурсом. Это означает, что и узлы, и ресурсы могут содержать ресурсы словно они свойства:

../../_images/nodes_resources.png

Внешние против Встроенных(Build-it)

Существует два способа сохранения ресурсов. Они могут быть:

  1. Внешний сохраненный в виде отдельных файлов.
  2. Встроенный сохраненный в файле *.tscn или в файле *.scn, к которому они прикреплены.

Если быть более точным, вот свойство Texture в узле Sprite:

../../_images/spriteprop.png

Нажатие на ссылку предварительного просмотра ресурса позволяет нам просматривать и редактировать свойства ресурса.

../../_images/resourcerobi.png

Свойство Path говорит нам, откуда берется ресурс. В данном случае речь идет об PNG изображении, получившем название 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. Сцена упакованая внутри ресурса.

To get an instance of the scene, you have to use the PackedScene.instance() method.

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

This method creates the nodes in the scene’s hierarchy, configures them, and returns the root node of the scene. You can then add it as a child of any other node.

The approach has several advantages. As the PackedScene.instance() function is fast, you can create new enemies, bullets, effects, etc. without having to load them again from disk each time. Remember that, as always, images, meshes, etc. are all shared between the scene instances.

Очистка(освобождение) ресурсов

Когда Resource перестает использоваться, он автоматически очищается. Поскольку, в большинстве случаев, Ресурсы содержатся в узлах, когда вы освобождаете узел, механизм освобождает все ресурсы, которыми он владеет, а также если ни один другой узел не использует их.

Создание собственных ресурсов

Like any Object in Godot, users can also script Resources. Resource scripts inherit the ability to freely translate between object properties and serialized text or binary data (/.tres, /.res). They also inherit the reference-counting memory management from the Reference type.

This comes with many distinct advantages over alternative data structures, such as JSON, CSV, or custom TXT files. Users can only import these assets as a Dictionary (JSON) or as a File to parse. What sets Resources apart is their inheritance of Object, Reference, and Resource features:

  • Они могут определять константы, поэтому константы из других полей данных или объектов не нужны.
  • Они могут определять методы, включая методы setter/getter для свойств. Это позволяет абстрагироваться и инкапсулировать исходные данные. Если структура скрипта ресурса должна измениться, то игра, использующая ресурс, не должна.
  • Они могут определять сигналы, чтобы Ресурсы могли инициировать реакцию на изменения в данных, которыми они управляют.
  • Они имеют определенные свойства, поэтому пользователи знают на 100%, что их данные будут существовать.
  • Ресурсы автоматически сериализуются и десериализуются — это встроенная функция движка Godot. Поэтому пользователям не требуется самостоятельно реализовывать логику импорта/экспорта файлов ресурсов.
  • Кроме того, ресурсы могут рекурсивно сериализовывать вложенные ресурсы. Это значит, что пользователи могут проектировать ещё более сложные структуры данных.
  • Пользователи могут сохранять ресурсы как текстовые файлы (*.tres) для лучшей совместимости с системами контроля версий. При экспорте игры Godot сериализует ресурсы в бинарные файлы (*.res) для увеличения быстродействия и экономии места.
  • Инспектор Godot Engine осуществляет рендеринг и редактирование файлов ресурсов «из коробки». Таким образом, пользователям часто не требуется придумывать собственную логику для визуализации или редактирования своих данных. Для этого дважды щелкните по файл ресурса в файловой системе или на значок папки в Инспекторе и выберите файл в диалоговом окне.
  • Они могут расширять другие виды ресурсов, помимо простого базового ресурса.

Предупреждение

Resources and Dictionaries are both passed by reference, but only Resources are reference-counted. This means that if a Dictionary is passed between objects and the first object is deleted, all other objects“ references to the Dictinoary will be invalidated. Conversely, Resources will not be freed from memory until all the objects are deleted.

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. Create a plain Resource object in the Inspector. This can even be a type that derives Resource, so long as your script is extending that type.
  2. Установите в свойстве script в Инспекторе Ваш собственный скрипт.

Инспектор теперь отобразит пользовательские свойства вашего скрипта ресурсов. Если отредактировать эти значения и сохранить ресурс, Инспектор также выполнит сериализацию пользовательских свойств! Для сохранения ресурса Инспектора щелкните на меню инструментов Инспектора (вверху справа) и выберите «Сохранить» или «Сохранить как…».

If the script’s language supports script classes, then it streamlines the process. Defining a name for your script alone will add it to the Inspector’s creation dialog. This will auto-add your script to the Resource object you create.

Давайте рассмотрим некоторые примеры.

# 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'.
            }
        }
    }
}

Примечание

Resource scripts are similar to Unity’s ScriptableObjects. The Inspector provides built-in support for custom resources. If desired though, users can even design their own Control-based tool scripts and combine them with an EditorPlugin to create custom visualizations and editors for their data.

Таблицы данных и таблицы кривых Unreal Engine 4 также легко воссоздаются с помощью скриптов Ресурсов. Таблицы данных - это строка, сопоставленная с пользовательской структурой, подобно Словарю, сопоставляющему строку со вторичным пользовательским сценарием ресурса.

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

Instead of just inlining the Dictionary values, one could also, alternatively…

  1. Import a table of values from a spreadsheet and generate these key-value pairs, or…
  2. Разработайте визуализацию в редакторе и создайте простой плагин, который добавит его в Инспектор, когда вы откроете такие типы ресурсов.

CurveTables are the same thing, except mapped to an Array of float values or a Curve/Curve2D resource object.

Предупреждение

Помните, что ресурсные файлы (*.tres/*.res) будут содержать путь к скрипту, который они используют. При загрузке они будут получать и загружать этот скрипт в качестве расширения своего типа. Это означает, что попытка присвоить подкласс, т.е. внутренний класс скрипта (например, с помощью ключевого слова class в GDScript“„) не сработает. 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 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);
    }
}