Ресурси
Вузли та ресурси
В цьому уроці ми зосередилися на класі Node в Godot, оскільки саме його ви використовуєте для кодування поведінки, і більшість функцій движка покладаються саме на нього. Є ще один тип даних, який так само важливий: Resource.
Вузли надають вам функціональність: вони малюють спрайти, тривимірні моделі, імітують фізику, впорядковують інтерфейс користувача тощо. Ресурси - це контейнери даних. Вони нічого не роблять самостійно: натомість, вузли використовують дані, що містяться в ресурсах.
Все, що Godot зберігає або завантажує з диска, є ресурсом. Будь то сцена (файл .tscn або .scn), зображення, сценарій... Ось деякі Resource наприклади:
Коли двигун завантажує ресурс з диска, він завантажує його лише один раз. Якщо копія цього ресурсу вже є в пам'яті, при спробі знову завантажити ресурс, кожен раз буде повертатися та сама копія. Оскільки ресурси містять лише дані, не потрібно дублювати їх.
Кожен об'єкт, чи то Вузол, чи Ресурс, може експортувати властивості. Існує багато типів властивостей, таких як String, integer, Vector2 тощо, і будь-який з цих типів може стати ресурсом. Це означає, що і вузли, і ресурси можуть містити ресурси типу властивостей:
Зовнішні та вбудовані
Існує два способи зберігання ресурсів:
Зовнішнє, збережене на диску у вигляді окремих файлів.
Вбудований, збережений всередині
.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;
}
Ви також можете попередньо завантажити ресурси. На відміну від 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() є швидкою, ви можете створювати нових ворогів, кулі, ефекти тощо, не завантажуючи їх щоразу з диска. Пам’ятайте, що, як завжди, зображення, сітки тощо є спільними для екземплярів сцени.
Звільнення ресурсів
Коли Ресурс більше не використовується, він автоматично звільниться. Оскільки в більшості випадків ресурси містяться у вузлах, коли ви звільняєте вузол, система також звільняє всі ресурси, якими володіє, якщо жоден інший вузол їх не використовує.
Створення власних ресурсів
Як і будь-який об’єкт у Godot, користувачі також можуть створювати сценарії ресурсів. Сценарії ресурсів успадковують можливість вільного перекладу між властивостями об’єкта та серіалізованим текстом або двійковими даними (*.tres, *.res). Вони також успадковують керування пам’яттю для підрахунку посилань від типу RefCounted.
Це має багато явних переваг перед альтернативними структурами даних, такими як файли JSON, CSV або спеціальні TXT. Користувачі можуть імпортувати ці ресурси лише як Dictionary (JSON) або як FileAccess для аналізу. Що відрізняє Resources від інших, так це їх успадкування функцій Object, RefCounted і Resource:
Вони можуть визначати константи, тому константи з інших полів даних, або об'єктів, не потрібні.
Вони можуть визначати методи, включаючи методи встановлення/отримання (setter/getter) для властивостей. Це дозволяє абстрагувати та інкапсулювати основні дані. Якщо структура скрипту Resource потребує змін, то гра, що використовує Resource, не потребує їх.
Вони можуть визначати сигнали, тому Ресурси можуть реагувати на зміни в даних, якими вони керують.
Вони мають визначені властивості, тому користувачі впевнені на 100%, що їх дані будуть існувати.
Автосеріалізація та десеріалізація ресурсів є вбудованою функцією Godot Engine. Користувачам не потрібно застосовувати власну логіку для імпорту/експорту даних файлу ресурсу.
Ресурси можуть навіть використовувати вкладені ресурси рекурсивно, тобто користувачі можуть створювати ще складніші структури даних.
Користувачі можуть зберігати Ресурси як текстові файли (*.tres). Після експорту гри Godot серіалізує файли ресурсів у вигляді бінарних файлів (*.res) для збільшення швидкості та стиснення.
Інспектор Godot Engine виводить і редагує файли ресурсів з коробки. Таким чином, користувачам часто не потрібно застосовувати власну логіку для візуалізації, чи редагування, своїх даних. Для цього двічі клацніть файл ресурсу на панелі Файлова система, або натисніть значок папки в інспекторі та відкрийте файл у діалоговому вікні.
Вони можуть розширювати інші типи ресурсів, окрім базового ресурсу.
Godot дозволяє легко створювати власні ресурси в Інспекторі.
Create a new Resource object in the Inspector. This can even be a type that derives Resource, so long as your script is extending that type.
Встановіть властивість
scriptв Інспекторі для вашого скрипту.
The Inspector will now display your Resource script's custom properties. If one edits those values and saves the resource, the Inspector serializes the custom properties too! To save a resource from the Inspector, click the save icon at the top of the Inspector, and select "Save" or "Save As...".
Якщо мова сценарію підтримує класи сценаріїв, це спрощує процес. Визначення назви лише для вашого сценарію додасть його до діалогового вікна створення інспектора. Це автоматично додасть ваш сценарій до об’єкта Resource, який ви створюєте.
Давайте розглянемо кілька прикладів. Створіть Ресурс і назвіть його bot_stats. Він має з’явитися на вкладці вашого файлу з повною назвою bot_stats.tres. Без сценарію це марно, тому давайте додамо трохи даних і логіки! Додайте до нього сценарій під назвою bot_stats.gd (або просто створіть новий сценарій, а потім перетягніть його туди).
Примітка
To make the new resource class appear in the Create Resource GUI you need to provide a class name for GDScript, or use the [GlobalClass] attribute in C#.
@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! Очевидно, що це налаштування можна використовувати для більш розширених функцій, ніж це, але якщо ви дійсно розумієте, як це все працювало, вам слід зрозуміти все інше, пов’язане з ресурсами.
Примітка
Скрипти ресурсів схожі на ScriptableObjects в Unity. Інспектор забезпечує вбудовану підтримку користувацьких ресурсів. За бажанням користувачі можуть навіть розробити власні скрипти інструментів на основі контролю (Control) та комбінувати їх з EditorPlugin, щоб створити власні візуалізації та редактори для своїх даних.
DataTables і CurveTables Unreal Engine також легко відтворити за допомогою сценаріїв Resource. 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 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, а потім визначає, що скрипт не вдалося завантажити для об'єкта ресурсу, оскільки типи несумісні.
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");
}
}