Переходы между уровнями
Автозагрузка, смена сцены, ожидание завершения и повторные запросы.
Уровни и автозагрузка
Нужны два сохранённых документа, например asset://Levels/Level01.rscene и asset://Levels/Level02.rscene. Каждый уровень должен содержать собственную камеру и освещение. Назначьте первый главным документом проекта.
Создайте Scripts/LevelRouter.cs. В Project Settings → Globals → Autoload добавьте этот скрипт с именем LevelRouter. Автозагрузка создаёт объект вне текущей сцены: он не удалится при переходе на другой уровень.
LevelRouter.cs
using System.Threading.Tasks;
using Reckfell;
public partial class LevelRouter : Entity
{
public bool IsChanging { get; private set; }
public async Task<Error> OpenLevel(string path)
{
if (IsChanging)
return Error.Busy;
if (!IsInsideTree())
return Error.Unconfigured;
if (!ResourceLoader.Exists(path, "PackedEntity"))
return Error.FileNotFound;
IsChanging = true;
EntityTree tree = GetTree();
try
{
Error error = tree.ChangeSceneToFile(path);
if (error != Error.Ok)
return error;
await ToSignal(tree, EntityTree.SignalName.SceneChanged);
return Error.Ok;
}
finally
{
IsChanging = false;
}
}
}Все переходы проекта вызывайте через этот объект. Пока один переход не завершён, повторный возвращает Busy. finally снимает блокировку и при успешной смене, и при возвращённой ошибке загрузки.
Кнопка перехода
В первом уровне добавьте CanvasLayer, внутри него Control с размещением Full Rect, затем дочерний Button. Для кнопки задайте Text: Следующий уровень, Position: (24, 24), Size: (240, 48); привязки оставьте к верхнему левому углу. Эта структура размещает кнопку поверх 3D-мира.
Прикрепите к кнопке OpenLevelButton.cs. После сборки назначьте Level Path: asset://Levels/Level02.rscene.
using Reckfell;
public partial class OpenLevelButton : Button
{
[Export] public string LevelPath { get; set; } = "asset://Levels/Level02.rscene";
public override void _Ready()
{
Pressed += Open;
}
private async void Open()
{
var router = GetEntityOrNull<LevelRouter>("/root/LevelRouter");
if (router == null)
{
ReckfellApi.PushError("Add LevelRouter to Autoload.");
return;
}
Error error = await router.OpenLevel(LevelPath);
if (error != Error.Ok && error != Error.Busy)
ReckfellApi.PushError($"Level change failed: {error}");
}
}Нажмите F5 и кнопку: первый уровень заменится вторым. Код после await не обращается к кнопке — она принадлежала старому уровню и к этому моменту уже удалена.
Что происходит при смене
| Момент | Состояние дерева |
|---|---|
ChangeSceneToFile вернул Ok | Старая сцена уже исключена из дерева; CurrentScene временно равна null |
| Конец кадра | Старое дерево удаляется, новое добавляется и выполняет _Ready() |
Сигнал SceneChanged | Новый CurrentScene доступен; OpenLevel() завершается |
Не получайте новое дерево сразу после ChangeSceneToFile: дождитесь SceneChanged или настройте объекты в их _Ready(). Ссылки на объекты старого уровня становятся недействительными. Сам LevelRouter остаётся в /root/LevelRouter.
Данные между уровнями храните в отдельной автозагрузке. Для восстановления после закрытия игры нужна запись на диск; автозагрузка сохраняет состояние только в рамках текущего запуска.
Ошибки и ограничения
| Симптом | Проверка |
|---|---|
Add LevelRouter to Autoload | Имя автозагрузки должно быть ровно LevelRouter |
FileNotFound | Проверьте путь asset://, регистр и наличие сохранённого документа |
CantOpen / CantCreate | Файл не загружается как PackedEntity или не создаёт экземпляр; смотрите первую ошибку Output |
| Новый уровень чёрный | Проверьте его камеру, Current, свет и положение объектов |
| После перехода возникла ошибка доступа | Код использует объект уже удалённого уровня |
ChangeSceneToFile загружает ресурс синхронно; на крупных уровнях возможна пауза. Замена сцены не реализует подгрузку частей открытого мира. Для перезапуска текущего документа есть EntityTree.ReloadCurrentScene() с тем же отложенным завершением смены.
Справочник: EntityTree, ResourceLoader, Button.