EntityTree: change and reload a level
CurrentScene, deferred replacement and SceneChanged; a complete button handler without accessing a freed level.
When a level has actually changed
GetTree() is available to an Entity in the active tree. EntityTree.Root is the main Viewport; CurrentScene is the current level root below it. Other Root children, such as autoloads, may coexist. Assigning CurrentScene alone neither attaches nor frees objects.
ChangeSceneToFile returns the starting operation's Error. On success the old root detaches immediately and CurrentScene is temporarily null; old-root deletion and new-root attachment finish later. SceneChanged is emitted after the new level enters and completes _Ready. Awaiting one ProcessFrame does not substitute for that signal contract.
A transition button
Prepare Assets/BadgeStyle.rdata, Actors/Badge.rprefab and Levels/FormatShowcase.rscene from the format example. Add both C# files below. In a separate level create a Button, give it visible text, attach LevelSwitchButton, build and run with F6. Pressing it replaces the level with FormatShowcase and its two instances.
LevelTransitions.cs
using System;
using System.Threading.Tasks;
using Reckfell;
public static class LevelTransitions
{
private static bool _pending;
public static async Task<Error> ChangeAsync(EntityTree tree, string path)
{
if (_pending) return Error.Busy;
if (!path.StartsWith("asset://", StringComparison.Ordinal) ||
!path.EndsWith(".rscene", StringComparison.OrdinalIgnoreCase)) return Error.InvalidParameter;
if (!ResourceLoader.Exists(path, "PackedEntity")) return Error.FileNotFound;
_pending = true;
try
{
Error error = tree.ChangeSceneToFile(path);
if (error != Error.Ok) return error;
await tree.ToSignal(tree, EntityTree.SignalName.SceneChanged);
return Error.Ok;
}
finally
{
_pending = false;
}
}
}LevelSwitchButton.cs
using System;
using Reckfell;
public partial class LevelSwitchButton : Button
{
[Export(PropertyHint.File, "*.rscene")]
public string NextLevelPath { get; set; } = "asset://Levels/FormatShowcase.rscene";
public override void _EnterTree() => Pressed += OnPressed;
public override void _ExitTree() => Pressed -= OnPressed;
private async void OnPressed()
{
try
{
Error error = await LevelTransitions.ChangeAsync(GetTree(), NextLevelPath);
if (error != Error.Ok) ReckfellApi.PrintErr("Level change: " + error);
}
catch (Exception error)
{
ReckfellApi.PrintErr(error.ToString());
}
}
}Call the helper on the main thread and route all transitions needing its overlap guard through it. Requiring .rscene is helper policy: the native API also accepts a .rprefab containing PackedEntity. A path's existence does not prove valid content, so the Error check after ChangeSceneToFile remains necessary.
After await the handler does not access the button; it may have been freed with the old level. To update UI afterward, find it in the new CurrentScene or use a controller Entity living outside the replaced root.
The helper does not implement a loading screen, background streaming or cancellation. Do not concurrently replace levels through another controller or manually free the pending new root: that breaks its SceneChanged wait.
Reload and manual management
ReloadCurrentScene loads CurrentScene.SourceAssetPath again. It restores file state rather than restarting with currently modified fields. A level created through new Entity and ChangeSceneToEntity may have no source file: retain the PackedEntity/path separately if it needs restarting.
ChangeSceneToEntity accepts a root not yet inside the active tree. Adding it through Root.AddChild and merely assigning CurrentScene does not automatically delete the previous root.
UnloadCurrentScene frees the current root immediately. Do not keep accessing its objects afterward. Use coordinated replacement for ordinary transitions, and retain long-lived data outside the replaced tree.
Diagnose a failed transition
| Result | Next action |
|---|---|
| Helper InvalidParameter | Check asset:// and .rscene; resolve a UID to a valid path first |
| FileNotFound | Check the level file and project/export inclusion |
| Busy | Wait for the previous transition to finish |
| Native load failure | Inspect resource diagnostics; refusal before replacement retains the old level |
| Temporarily null CurrentScene | Wait for SceneChanged rather than accessing old references |
The fixture verifies the actual transition gap, new-root readiness, old-root deletion, overlap refusal, reload and an already-created Entity target. This does not establish export or streaming readiness.