Ресурсы
Узлы и Ресурсы
До этого урока мы были сосредоточены на классе Node в Godot, так как именно его вы используете для написания поведения и большинство функций движка полагаются на него. Есть еще один тип данных, который так же важен: Resource.
Узлы дают вам функциональность: они рисуют спрайты, 3D модели, моделируют физику, организовывают UI и т.д. Ресурсы являются контейнерами данных. Они ничего не делают сами по себе: вместо этого узлы используют данные, содержащиеся в ресурсах.
Всё, что Godot сохраняет или загружает с диска, является ресурсом. Будь то сцена (файл .tscn или .scn), изображение, скрипт... Вот несколько примеров Resource:
Когда движок загружает ресурс с диска, он загружает его только один раз. Если копия ресурса уже находится в памяти, при попытке загрузить его снова будет каждый раз возвращаться одна и та же копия. Поскольку ресурсы содержат только данные, нет необходимости их дублировать.
Каждый объект, будь то Узел или Ресурс, может экспортировать свойства. Существует множество типов свойств, таких как String, integer, Vector2 и т.д., и любой из этих типов может стать ресурсом. Это означает, что и узлы, и ресурсы могут содержать ресурсы словно они свойства:
Внешние против Встроенных(Build-it)
Существует два способа сохранения ресурсов. Они могут быть:
Внешний сохраненный в виде отдельных файлов.
Встроенный сохраненный в
.tscnили.scnфайле, к которому они прикреплены.
Если быть более точным, вот Texture2D в узле Sprite2D:
Нажав на предварительный просмотр ресурса, можно просмотреть его свойства.
Свойство Path говорит нам, откуда берется ресурс. В данном случае речь идет об PNG изображении, получившем название robi.png. Когда ресурс поступает из такого файла, он является внешним ресурсом. Если вы удалите этот путь или этот путь пуст, он станет встроенным ресурсом.
Переключение между встроенными и внешними ресурсами происходит при сохранении сцены. В приведенном выше примере, если вы удалите путь "res://robi.png" и сохраните, Godot сохранит изображение в файле сцены в формате .tscn.
Примечание
Даже если вы сохраните встроенный ресурс, при многократном копировании сцены, движок загрузит только одну ее копию.
Загрузка ресурсов из кода
Существует два способа загрузки ресурсов из кода. Во-первых, можно воспользоваться функцией load():
func _ready():
# Godot loads the Resource when it reads this very line.
var imported_resource = load("res://robi.png")
$sprite.texture = imported_resource
public override void _Ready()
{
// Godot loads the Resource when it executes this line.
var texture = GD.Load<Texture>("res://Robi.png");
var sprite = GetNode<Sprite2D>("sprite");
sprite.Texture = texture;
}
Вы также можете использовать preload ресурсы. В отличие от load, эта функция считывает файл с диска и загружает его во время компиляции. Поэтому preload нельзя вызвать с переменным путём: необходимо использовать константную строку.
func _ready():
# Godot loads the resource at compile-time
var imported_resource = preload("res://robi.png")
get_node("sprite").texture = imported_resource
// 'preload()' is unavailable in C Sharp.
Загрузка сцен
Сцены тоже являются ресурсами, но есть одна загвоздка. Сцены, сохранённые на диске, — это ресурсы типа PackedScene. Сцена упакована в Resource.
Чтобы получить экземпляр сцены, необходимо использовать метод PackedScene.instantiate().
func _on_shoot():
var bullet = preload("res://bullet.tscn").instantiate()
add_child(bullet)
private PackedScene _bulletScene = GD.Load<PackedScene>("res://Bullet.tscn");
private void OnShoot()
{
Node bullet = _bulletScene.Instantiate();
AddChild(bullet);
}
Этот метод создает узлы в иерархии сцены, настраивает их и возвращает корневой узел сцены.Вы можете добавить его в качестве ребенка любого другого узла.
Такой подход имеет несколько преимуществ. Поскольку функция PackedScene.instantiate() быстрая, вы можете создавать новых врагов, пули, эффекты и т. д., не загружая их каждый раз заново с диска. Помните, что, как всегда, изображения, сетки и т. д. являются общими для всех экземпляров сцены.
Очистка(освобождение) ресурсов
Когда ресурс Resource больше не используется, он автоматически освобождается. Поскольку в большинстве случаев ресурсы содержатся в узлах, при освобождении узла движок освобождает и все принадлежащие ему ресурсы, если ни один другой узел их не использует.
Создание собственных ресурсов
Как и любой объект в Godot, пользователи могут создавать скрипты для ресурсов. Скрипты ресурсов наследуют возможность свободного преобразования свойств объекта в сериализованные текстовые или двоичные данные (*.tres, *.res). Они также наследуют управление памятью с помощью подсчёта ссылок от типа RefCounted.
Это даёт множество очевидных преимуществ по сравнению с альтернативными структурами данных, такими как JSON, CSV или пользовательские TXT-файлы. Пользователи могут импортировать эти ресурсы только как Dictionary (JSON) или как FileAccess для анализа. Отличительной особенностью Resources является наследование функций Object, RefCounted и Resource:
Они могут определять константы, поэтому константы из других полей данных или объектов не нужны.
Они могут определять методы, включая методы setter/getter для свойств. Это позволяет абстрагироваться и инкапсулировать исходные данные. Если структура скрипта ресурса должна измениться, то игра, использующая ресурс, не должна.
Они могут определять сигналы, чтобы Ресурсы могли инициировать реакцию на изменения в данных, которыми они управляют.
Они имеют определенные свойства, поэтому пользователи знают на 100%, что их данные будут существовать.
Ресурсы автоматически сериализуются и десериализуются — это встроенная функция движка Godot. Поэтому пользователям не требуется самостоятельно реализовывать логику импорта/экспорта файлов ресурсов.
Кроме того, ресурсы могут рекурсивно сериализовывать вложенные ресурсы. Это значит, что пользователи могут проектировать ещё более сложные структуры данных.
Пользователи могут сохранять ресурсы как текстовые файлы (*.tres) для лучшей совместимости с системами контроля версий. При экспорте игры Godot сериализует ресурсы в бинарные файлы (*.res) для увеличения быстродействия и экономии места.
Инспектор Godot Engine осуществляет рендеринг и редактирование файлов ресурсов "из коробки". Таким образом, пользователям часто не требуется придумывать собственную логику для визуализации или редактирования своих данных. Для этого дважды щелкните по файл ресурса в файловой системе или на значок папки в Инспекторе и выберите файл в диалоговом окне.
Они могут расширять другие виды ресурсов, помимо простого базового ресурса.
Godot облегчает создание пользовательских ресурсов в Инспекторе.
Создайте новый объект Resource в инспекторе. Это может быть даже тип, производный от Resource, если ваш скрипт расширяет этот тип.
Установите в свойстве
scriptв Инспекторе Ваш собственный скрипт.
Теперь в Inspector будут отображаться пользовательские свойства вашего Resource script's. Если вы измените эти значения и сохраните ресурс, Inspector также сериализует пользовательские свойства! Чтобы сохранить ресурс из Inspector, нажмите значок сохранения в верхней части Inspector и выберите "Save" или "Save As...".
Если язык скрипта поддерживает script classes, это упрощает процесс. Указание имени скрипта само по себе добавит его в диалоговое окно создания в Инспекторе. Это автоматически добавит ваш скрипт в создаваемый вами объект Resource.
Давайте рассмотрим несколько примеров. Создайте ресурс Resource и назовите его bot_stats. Он должен появиться на вкладке "file" под полным именем bot_stats.tres. Без скрипта он бесполезен, поэтому давайте добавим немного данных и логики! Прикрепите к нему скрипт с именем bot_stats.gd (или просто создайте новый скрипт и перетащите его на него).
Примечание
Чтобы новый класс ресурса появился в Create Resource GUI, вам необходимо указать имя класса для GDScript или использовать атрибут [GlobalClass] в C#.
class_name BotStats
extends Resource
@export var health: int
@export var sub_resource: Resource
@export var strings: PackedStringArray
# 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
// BotStats.cs
using Godot;
namespace ExampleProject
{
[GlobalClass]
public partial class BotStats : Resource
{
[Export]
public int Health { get; set; }
[Export]
public Resource SubResource { get; set; }
[Export]
public string[] Strings { get; set; }
// Make sure you provide a parameterless constructor.
// In C#, a parameterless constructor is different from a
// constructor with all default values.
// Without a parameterless constructor, Godot will have problems
// creating and editing your resource via the inspector.
public BotStats() : this(0, null, null) {}
public BotStats(int health, Resource subResource, string[] strings)
{
Health = health;
SubResource = subResource;
Strings = strings ?? System.Array.Empty<string>();
}
}
}
Теперь создайте CharacterBody3D, назовите его Bot и добавьте в него следующий скрипт:
extends CharacterBody3D
@export var stats: Resource
func _ready():
# Uses an implicit, duck-typed interface for any 'health'-compatible resources.
if stats:
stats.health = 10
print(stats.health)
# Prints "10"
// Bot.cs
using Godot;
namespace ExampleProject
{
public partial class Bot : CharacterBody3D
{
[Export]
public Resource Stats;
public override void _Ready()
{
if (Stats is BotStats botStats)
{
GD.Print(botStats.Health); // Prints '10'.
}
}
}
}
Теперь выберите узел CharacterBody3D, который мы назвали bot, и перетащите ресурс bot_stats.tres в Инспектор. Он должен вывести значение 10! Очевидно, что эту настройку можно использовать для более продвинутых функций, но если вы действительно понимаете, как всё это работает, вам нужно будет разобраться со всем остальным, что связано с ресурсами.
Примечание
Скрипты Ресурсов похожи на Unity's ScriptableObjects. Инспектор обеспечивает встроенную поддержку пользовательских ресурсов. При желании пользователи могут даже создавать собственные скрипты инструментов на основе Control и комбинировать их с EditorPlugin для создания собственных визуализаций и редакторов для своих данных.
DataTables и CurveTables Unreal Engine также легко воссоздать с помощью скриптов ресурсов. Таблицы данных представляют собой строку, сопоставленную с пользовательской структурой, подобно словарю, сопоставляющему строку со вторичным пользовательским скриптом ресурсов.
# 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 Godot;
[GlobalClass]
public partial class BotStatsTable : Resource
{
private Godot.Collections.Dictionary<string, BotStats> _stats = new Godot.Collections.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);
}
}
Вместо встраивания Словарных значений можно также сделать следующее:
Импортируйте таблицу значений из электронной таблицы и сгенерируйте эти пары ключ-значение.
Разработайте визуализацию в редакторе и создайте плагин, который добавит ее в Инспектор при открытии этих типов ресурсов.
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(my_res, "res://my_res.tres")
using Godot;
public partial class MyNode : Node
{
[GlobalClass]
public partial 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, "res://MyRes.tres");
}
}