menu_bookDocumentation
Sandbox: CleanupSystem — full scene cleanup and per-player cleanup with Ownable
Sandbox CleanupSystem — Scene Cleanup and Per-Player Cleanup
CleanupSystem is a GameObjectSystem that handles cleaning up spawned objects. It distinguishes between "baseline" objects (part of the original scene) and "spawned" objects (added at runtime).Full scene cleanup
CSHARP
// Removes all spawned objects and restores destroyed baseline objects
// Players and their belongings are preserved
public void Cleanup()
{
// 1. Destroy all objects tagged "removable" that aren't player-owned
// 2. Restore any baseline objects that were destroyed
// 3. Fire ICleanupEvents.OnCleanup
}Per-player cleanup
CSHARP
// Admin-only: clean up a specific player's objects
[Rpc.Host]
public static void CleanupPlayer( Connection caller )
{
Assert.True( Networking.IsHost );
var removable = Game.ActiveScene.GetAllComponents<Ownable>()
.Where( o => o.Owner == caller );
var count = 0;
foreach ( var ownable in removable.ToArray() )
{
ownable.GameObject.Destroy();
count++;
}
Notices.SendNotice( caller, "cleaning_services", Color.Green, $"Cleaned up {count} objects" );
}Baseline preservation for save/load
CSHARP
// Call before Game.ChangeScene() when loading a save
// Captures the current scene's baseline so it can be restored after load
public static void PreserveBaselineForSaveLoad() { ... }IsPlayerObject check
The cleanup system uses a recursive parent walk to determine if a GameObject belongs to a player:
CSHARP
private static bool IsPlayerObject( GameObject go )
{
if ( go.Components.Get<Player>( true ) is not null ) return true;
if ( go.Components.Get<PlayerData>( true ) is not null ) return true;
var parent = go.Parent;
while ( parent is not null && parent != go.Scene )
{
if ( parent.Components.Get<Player>( true ) is not null ) return true;
parent = parent.Parent;
}
return false;
}ICleanupEvents
CSHARP
public interface ICleanupEvents : ISceneEvent<ICleanupEvents>
{
void OnCleanup() { }
}Implement on a GameObjectSystem or Component to receive cleanup notifications.
Key points
- Objects must have the "removable" tag to be cleaned up by the full cleanup
- Per-player cleanup uses Ownable components — objects without Ownable are not cleaned up per-player
- CleanupPlayer requires Rpc.Caller.HasPermission("admin")
- PreserveBaselineForSaveLoad must be called before Game.ChangeScene() to avoid losing baseline tracking
Was this helpful?