s&box
RustCounter-Strike 2s&boxDeadlock
Wiki
Get s&box on Steam
Browse
OverviewGetting StartedCreating GamesEngine & ToolsDocumentationCommunity GamesResources
s&boxDocumentationEditorUndo System
SOURCE 2 · C#
Build and play games with s&box.

Facepunch's game engine and creation platform — make games in C#, or jump straight into community games.

Get s&box on Steam

s&box Wiki

  • Overview
  • Getting Started
  • Creating Games
  • Engine & Tools
  • Documentation
  • Community Games
  • Resources

Tools & Community

  • Getting Started
  • Creating Games
  • Engine & Tools
  • Resources

Platforms

  • RustBattle
  • CSBattle
  • Rust Game Wiki
  • CS2 Skins Wiki
  • Deadlock Wiki

About

  • Official Site
  • Developer Wiki
  • GitHub
  • Get on Steam
NOT AFFILIATED WITH FACEPUNCH STUDIOS · © 2026 s&box Wiki (community)
Docs/Editor
Editor

Undo System

The scope based system works by creating a snapshot of a change set when the scope is entered and another one when the scope is disposed of. The system will automatically take care of restoring the state on undo/redo.

A basic blank scope can be created as follows:

// In Game & Editor Code
var undoScope = Scene.Editor?.UndoScope( "Your Action Name");

// In Editor Code
var undoScope = SceneEditorSession.Active.UndoScope( "Your Action Name" );
// Push() will turn the scope into a disposable
using ( SceneEditorSession.Active.UndoScope( "Your Action Name" ).Push() )
{
  // Actions that modify the scene
}

var undoScope = SceneEditorSession.Active.UndoScope( "Your Action Name" );
using ( undoScope.Push() )
{
  // Actions that modify the scene
}

{
  using var undoScope = SceneEditorSession.Active.UndoScope( "Your Action Name" ).Push();
  // Actions that modify the scene
}

However, these scopes will not capture anything yet.

You will have to tell the scope what objects you are about to modify. To ensure the best performance you should keep the set of captured objects as small as possible.

GameObjects

To capture GameObject changes use undoScope.WithGameObjectChanges(). You also have to specify what part of the GameObject(s) you would like to capture.

  • GameObjectUndoFlags.Properties Is always enabled and captures basic properties of the object (Parent, Transform, Name…)
  • GameObjectUndoFlags.Components Captures the components list of the object
  • GameObjectUndoFlags.Children Captures all children of the object, this can become very expensive for complex scene hierarchies, so use it only if you have to.
  • GameObjectUndoFlags.All Shortcut to capture everything
using var undoScope = SceneEditorSession.Active.UndoScope( "Your Action Name" )
  .WithGameObjectChanges( gameObject, GameObjectUndoFlags.Properties | GameObjectUndoFlags.Components)
  .Push();

To capture GameObject creation you can use WithGameObjectCreations().

using ( SceneEditorSession.Active.UndoScope( "Create Empty" ).WithGameObjectCreations().Push() )
{
	var go = new GameObject( true, "Object" );
}

Similarly you can capture objects that are about to be destroyed.

using ( SceneEditorSession.Active.UndoScope( "Delete Object(s)" ).WithGameObjectDestructions( selectedGos ).Push() )
{
	foreach ( var go in selectedGos )
	{
		if ( !go.IsDeletable() )
			return;

		go.Destroy();
	}
}

Components

Components offer similar functionality. Components are always captured as a whole so there are no flags that need to be specified.

using ( SceneEditorSession.Active.UndoScope( "Drop Material" ).WithComponentChanges( c as Component ).Push() )
{
	c.SetMaterial( material, trace.Triangle );
}

Capture Creation

using ( SceneEditorSession.Active.UndoScope( "Add Component(s)" ).WithComponentCreations().Push() )
{
    var component = go.Components.Create( componentType );
    createdComponents.Add( component );
}

Capture Destruction

using ( SceneEditorSession.Active.UndoScope( $"Cut Component" ).WithComponentDestructions( component ).Push() )
{
	component.CopyToClipboard();
	component.Destroy();
}

Selections

Selections are always captured and restored on undo/redo

Chaining

As you may have already noticed you can chain the different functions together to capture a variety of changes and events.

var undoScope = SceneEditorSession.Active.UndoScope( "Extract Faces" )
                  .WithComponentChanges( components )
                  .WithGameObjectDestructions( gameObjects )
                  .WithGameObjectCreations();

using ( undoScope.Push() )

More Examples

Actions that span multiple frames

If you have an action that spans multiple frames (e.g. dragging something around) you can use the following pattern to create an undo.

public class BoxColliderTool : EditorTool<BoxCollider>
{
	private IDisposable _componentUndoScope;

	public override void OnUpdate()
	{
		var boxCollider = GetSelectedComponent<BoxCollider>();
		if ( boxCollider == null )
			return;

		var currentBox = BBox.FromPositionAndSize( boxCollider.Center, boxCollider.Scale );

		using ( Gizmo.Scope( "Box Collider Editor", boxCollider.WorldTransform ) )
		{
			if ( Gizmo.Control.BoundingBox( "Bounds", currentBox, out var newBox ) )
			{
				if ( _componentUndoScope == null )
				{
                   // Create the scope if it does not exist yet
					_componentUndoScope = SceneEditorSession.Active.UndoScope( "Resize Box Collider" )
                                            .WithComponentChanges( boxCollider )
                                            .Push();
				}
				boxCollider.Center = newBox.Center;
				boxCollider.Scale = newBox.Size;
			}
   
            // Dispose the scope when the mouse is released
			if ( Gizmo.WasLeftMouseReleased )
			{
				_componentUndoScope?.Dispose();
				_componentUndoScope = null;
			}
		}
	}
}

Group GameObjects Action

var undoScope = SceneEditorSession.Active.UndoScope( "Group Objects" )
                  .WithGameObjectChanges( selection, GameObjectUndoFlags.Properties )
                  .WithGameObjectCreations();

using ( undoScope.Push() )
{
	var go = new GameObject();
	go.WorldTransform = first.WorldTransform;
	go.MakeNameUnique();

	first.AddSibling( go, false );

	for ( var i = 0; i < selection.Length; i++ )
	{
		selection[i].SetParent( go, true );
	}

	EditorScene.Selection.Clear();
	EditorScene.Selection.Add( go );
}

Source: Facepunch/sbox-docs (CC-BY-4.0) · updated 2025-06-15. Read it rendered on the official docs.

More in Editor

Asset Previews
Component Editor Tools
Custom Editors
Editor
Editor Apps
Editor Events
Editor Project
Editor Shortcuts
← All documentation

Community wiki — not affiliated with Facepunch Studios. For the latest and most authoritative information, see the official developer wiki.