Фоновая загрузка префаба
Запрос, опрос состояния и безопасный момент создания Entity.
Подготовка
Создайте префаб asset://Props/Crate.rprefab с корнем Entity3D и дочерним MeshInstance3D с BoxMesh. В главном уровне создайте Entity3D с именем Objects и обычную Entity со скриптом BackgroundPrefab.cs. В свойстве ParentForInstance назначьте Objects, соберите C#-проект и запустите уровень.
Скрипт сам вызывает Begin() из _Ready(). После загрузки в Objects появится экземпляр, Busy станет false, Progress — 1, LastError — Ok. Для видимого куба в уровне нужны камера и свет; загрузка ресурса их не создаёт.
BackgroundPrefab.cs
using Reckfell;
public partial class BackgroundPrefab : Entity
{
[Export(PropertyHint.File, "*.rprefab,*.rscene")]
public string ResourcePath { get; set; } = "asset://Props/Crate.rprefab";
[Export] public Entity ParentForInstance { get; set; } = null!;
public bool Busy { get; private set; }
public float Progress { get; private set; }
public Error LastError { get; private set; } = Error.Ok;
public Entity? LastInstance { get; private set; }
private string _requestedPath = "";
public override void _Ready() => Begin();
public Error Begin()
{
if (Busy)
return Error.Busy;
if (!IsInsideTree() || IsQueuedForDeletion() || !ReckfellObject.IsInstanceValid(ParentForInstance) ||
!ParentForInstance.IsInsideTree() || ParentForInstance.IsQueuedForDeletion())
return Error.Unconfigured;
if (!ResourcePath.StartsWith("asset://", System.StringComparison.Ordinal) ||
!ResourceLoader.Exists(ResourcePath, "PackedEntity"))
return Error.FileNotFound;
_requestedPath = ResourcePath;
Progress = 0;
LastInstance = null;
LastError = ResourceLoader.LoadThreadedRequest(_requestedPath, "PackedEntity");
Busy = LastError == Error.Ok;
return LastError;
}
public override void _Process(double delta)
{
if (!Busy)
return;
using var progress = new Reckfell.Collections.Array();
var status = ResourceLoader.LoadThreadedGetStatus(_requestedPath, progress);
if (progress.Count != 0)
Progress = Mathf.Clamp((float)progress[0].AsDouble(), 0, 1);
if (status == ResourceLoader.ThreadLoadStatus.InProgress)
return;
Busy = false;
if (status == ResourceLoader.ThreadLoadStatus.InvalidResource)
{
LastError = Error.InvalidData;
return;
}
Resource resource = ResourceLoader.LoadThreadedGet(_requestedPath);
if (status != ResourceLoader.ThreadLoadStatus.Loaded || resource is not PackedEntity prefab)
{
LastError = Error.CantOpen;
return;
}
if (!ReckfellObject.IsInstanceValid(ParentForInstance) || !ParentForInstance.IsInsideTree() ||
ParentForInstance.IsQueuedForDeletion())
{
LastError = Error.Unconfigured;
return;
}
LastInstance = prefab.Instantiate();
ParentForInstance.AddChild(LastInstance);
Progress = 1;
LastError = Error.Ok;
}
public override void _ExitTree()
{
if (!Busy)
return;
Busy = false;
// Collect the outstanding request; this may wait for its completion.
_ = ResourceLoader.LoadThreadedGet(_requestedPath);
LastError = Error.Unavailable;
Progress = 0;
}
}Почему три вызова
LoadThreadedRequest() начинает загрузку ресурса. LoadThreadedGetStatus() опрашивается один раз за кадр, а не в цикле ожидания. Массив progress, если заполнен, содержит долю 0…1; это не прогноз оставшегося времени.
Только после Loaded код получает результат через LoadThreadedGet(), создаёт экземпляр и добавляет его в дерево в главном потоке. Ранний Get() может заблокировать поток до завершения. Статус Failed тоже требует забрать результат запроса; он вернёт ошибочный результат, который нельзя использовать как префаб. После получения токен запроса исчерпан: последующий статус может быть InvalidResource.
Время жизни и повторный запуск
Begin() во время загрузки возвращает Busy и не запускает второй запрос. Путь фиксируется в _requestedPath: изменение экспортированного свойства не меняет уже начатую операцию. Повторный успешный Begin() создаёт ещё один экземпляр; предыдущий объект остаётся в игре.
Если родитель исчез до завершения, результат забирается, но объект не создаётся. При выходе самого загрузчика из дерева _ExitTree() забирает незавершённый запрос, не создавая Entity. Этот аварийный путь может ждать загрузку и задержать выход; API отмены здесь нет. Для обычного экрана загрузки держите загрузчик в постоянном корне до завершения, затем меняйте уровень.
Это загрузка одного ресурса с зависимостями, не готовая система стриминга мира. Создание сложной иерархии, _Ready() и подготовка графики всё ещё могут занять главный поток. Передача useSubThreads: true также не гарантирует ускорения: измеряйте конкретный ресурс.
Ошибки
| Результат | Причина |
|---|---|
Unconfigured | Загрузчик или назначенный родитель не находится в дереве |
FileNotFound | Неверный путь или ресурс не существует как PackedEntity |
Busy из Begin() | Сначала дождитесь текущей операции |
InvalidData | Нет действующего запроса для сохранённого пути |
CantOpen | Загрузка завершилась неудачно либо вернулся другой тип |
Unavailable | Загрузчик вышел из дерева до обработки результата |
Смена текущего уровня — отдельная операция: переходы между уровнями.