Skip to main content
Game logic in s&box is written in C#. You attach behavior to GameObjects by creating Components — C# classes that inherit from Component. When you save a .cs file, s&box recompiles and hot-reloads your code automatically.

Components

A Component is the primary unit of game logic. Create one by inheriting from Component and overriding lifecycle methods.
public class MyComponent : Component
{
    protected override void OnStart()
    {
        Log.Info( "Component started!" );
    }

    protected override void OnUpdate()
    {
        // Runs every frame
    }
}
Add it to a GameObject in the scene editor, or in code:
var go = new GameObject();
var c = go.AddComponent<MyComponent>();

How game code is structured

Your project has a Code/ folder for runtime game code and an Editor/ folder for editor-only code. Libraries you install also live in Libraries/ and are compiled together with your game code. The Scene object is your entry point to everything that exists at runtime. You can find GameObjects and Components through it:
// Find all active cameras in the scene
foreach ( var cam in Scene.GetAll<CameraComponent>() )
{
    Log.Info( cam.GameObject.Name );
}

API whitelist

s&box restricts which .NET APIs game code can use. This prevents malicious code from running on players’ machines when they download and play community games. When you use a blocked API, the compiler emits a SB1000 Whitelist Error. Editor code and library code are not restricted.
If you are building a standalone game, you can opt out of the whitelist — but you won’t be able to publish to the s&box platform while it’s disabled.
Many standard .NET APIs have s&box equivalents:
Not allowedUse instead
Console.LogLog.Info( message )
System.IO.*The s&box Filesystem API
If you encounter a false positive — an API that seems harmless but is blocked — report it on the issue tracker with the symbol as it appears in the error.
Security vulnerabilities in the whitelist system should be reported privately as described at facepunch.com/security. Do not report them publicly.

Common patterns cheat sheet

Debugging

TaskCode
Log to consoleLog.Info( $"Hello {username}" );
Draw text on screenDebugOverlay.ScreenText( new Vector2( 50, 50 ), "Hello" );
Assert a conditionAssert.NotNull( obj, "Object was null!" )

Transforms

TaskCode
Get world positionvar p = go.WorldPosition;
Set world positiongo.WorldPosition = new Vector3( 10, 0, 0 );
Get local positionvar p = go.LocalPosition;

GameObjects

TaskCode
Find by nameScene.Directory.FindByName( "Cube" ).First();
Find by GUIDScene.Directory.FindByGuid( guid );
Createvar go = new GameObject();
Destroygo.Destroy()
Disablego.Enabled = false;
Duplicatevar newGo = go.Clone();
Add a taggo.Tags.Add( "player" );
Iterate childrenforeach( var child in go.Children )
Check if validif ( go.IsValid() )

Components

TaskCode
Add componentvar c = go.AddComponent<ModelRenderer>();
Remove componentc.Destroy()
Disable componentc.Enabled = false;
Get owner GameObjectvar go = c.GameObject;
Get a componentvar c = go.GetComponent<ModelRenderer>();
Get or addvar c = go.GetOrAddComponent<ModelRenderer>();
Iterate allforeach ( var c in go.Components.GetAll() )
Check if validif ( c.IsValid() )
Get all active in sceneforeach ( var c in Scene.GetAll<CameraComponent>() )

Code generation

s&box includes a [CodeGenerator] attribute that lets you wrap methods and properties to intercept calls. You decorate another attribute with [CodeGenerator] to specify what it wraps and which callback to invoke. This is how the scene system implements Broadcast RPCs. You can use the same mechanism to build your own systems.

Wrapping a method

[CodeGenerator( CodeGeneratorFlags.WrapMethod | CodeGeneratorFlags.Instance, "OnRPCInvoked" )]
public class RPC : Attribute {}

public class MyObject
{
    [RPC]
    public void SendMessage( string message )
    {
        Log.Info( message );
    }

    internal void OnRPCInvoked( WrappedMethod m, params object[] args )
    {
        if ( IsServer )
        {
            // Send a networked message with the method name and args to all clients.
        }

        // Call the original method.
        m.Resume();
    }
}

Wrapping a property

[CodeGenerator( CodeGeneratorFlags.WrapPropertySet | CodeGeneratorFlags.Instance, "OnNetVarSet" )]
[CodeGenerator( CodeGeneratorFlags.WrapPropertyGet | CodeGeneratorFlags.Instance, "OnNetVarGet" )]
public class NetVar : Attribute {}

public class MyObject
{
    [NetVar] public string Name { get; set; }

    internal T OnWrapGet<T>( WrappedPropertyGet<T> p )
    {
        if ( MyNetVarTable.TryGetValue( p.PropertyName, out var netValue ) )
        {
            return (T)netValue;
        }
        return p.Value;
    }

    internal void OnWrapSet<T>( WrappedPropertySet<T> p )
    {
        if ( IsServer )
        {
            MyNetVarTable[p.PropertyName] = p.Value;
            // Send a networked message setting this property for all clients.
        }
        p.Setter( p.Value );
    }
}

CodeGeneratorFlags reference

The flags determine what gets wrapped and whether it applies to instance members, static members, or both. You can combine multiple flags on a single attribute and stack multiple [CodeGenerator] attributes on one attribute class.
FlagDescription
WrapMethodIntercept method calls
WrapPropertyGetIntercept property reads
WrapPropertySetIntercept property writes
InstanceApply to instance members
StaticApply to static members
The callbackName you pass to [CodeGenerator] can be an instance method or a static method. Use a . in the name to reference a static method on another class, e.g. "MyStaticClass.OnWrap".