Ресурсы

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

До этого урока мы были сосредоточены на классе Node в Godot, так как именно его вы используете для написания поведения и большинство функций движка полагаются на него. Есть еще один тип данных, который так же важен: Resource.

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

Все, что Godot сохраняет на диск или загружает с него, является ресурсом. Будь то сцена (.tscn или .scn файл), изображение, скрипт... Вот несколько примеров Ресурсов : Текстура, Скрипт, Сетка, Анимация, Аудиопоток, шрифт, Перевод.

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

Каждый объект, будь то Узел или Ресурс, может экспортировать свойства. Существует множество типов свойств, таких как 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 = 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, пользователи также могут создавать скрипты Ресурсов. Скрипты ресурса наследуют возможность свободного перемещения между свойствами объекта и сериализованным текстом или двоичными данными (*.tres, *.res). Они также наследуют управление памятью подсчета ссылок из ссылочного типа.

Это дает много явных преимуществ по сравнению с альтернативными структурами данных, такими как JSON, CSV или пользовательские TXT-файлы. Пользователи могут импортировать эти ресурсы только в виде Dictionary (JSON) или как File для парсинга. Отличительной особенностью Ресурсов является наследование характеристик Object, Reference, и Resource:

  • Они могут определять константы, поэтому константы из других полей данных или объектов не нужны.

  • Они могут определять методы, включая методы setter/getter для свойств. Это позволяет абстрагироваться и инкапсулировать исходные данные. Если структура скрипта ресурса должна измениться, то игра, использующая ресурс, не должна.

  • Они могут определять сигналы, чтобы Ресурсы могли инициировать реакцию на изменения в данных, которыми они управляют.

  • Они имеют определенные свойства, поэтому пользователи знают на 100%, что их данные будут существовать.

  • Ресурсы автоматически сериализуются и десериализуются — это встроенная функция движка Godot. Поэтому пользователям не требуется самостоятельно реализовывать логику импорта/экспорта файлов ресурсов.

  • Кроме того, ресурсы могут рекурсивно сериализовывать вложенные ресурсы. Это значит, что пользователи могут проектировать ещё более сложные структуры данных.

  • Пользователи могут сохранять ресурсы как текстовые файлы (*.tres) для лучшей совместимости с системами контроля версий. При экспорте игры Godot сериализует ресурсы в бинарные файлы (*.res) для увеличения быстродействия и экономии места.

  • Инспектор Godot Engine осуществляет рендеринг и редактирование файлов ресурсов "из коробки". Таким образом, пользователям часто не требуется придумывать собственную логику для визуализации или редактирования своих данных. Для этого дважды щелкните по файл ресурса в файловой системе или на значок папки в Инспекторе и выберите файл в диалоговом окне.

  • Они могут расширять другие виды ресурсов, помимо простого базового ресурса.

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

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

Примечание

Скрипты Ресурсов похожи на Unity's ScriptableObjects. Инспектор обеспечивает встроенную поддержку пользовательских ресурсов. При желании пользователи могут даже создавать собственные скрипты инструментов на основе Control и комбинировать их с EditorPlugin для создания собственных визуализаций и редакторов для своих данных.

Таблицы данных и таблицы кривых 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);
    }
}

Вместо того, чтобы просто подставлять значения словаря, можно также, в качестве альтернативы...

  1. Импортируйте табличные значения из электронной версии таблицы и создайте эти пары ключ-значение, или....

  2. Разработайте визуализацию в редакторе и создайте простой плагин, который добавит его в Инспектор, когда вы откроете такие типы ресурсов.

CurveTables - это то же самое, за исключением сопоставления с массивом значений с плавающей запятой или ресурсным объектом Curve/Curve2D.

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

Помните, что ресурсные файлы (*.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 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);
    }
}