Sandbox Logo
27 January 2024

Action Graph

S&box has visual scripting now
James King
In Hammer, we have EntityIO. If you press a button, it opens a door. Entities have inputs and outputs. There's relays and counters. You make a bunch of entities and wire them all together to create complex logic.

That's great, but we don't have entities anymore. We need a solution that can work in the scene system and in Hammer. 

We've already got a Shader Graph tool, can we re-use that tech? Can we build something that integrates well with everything?

Using the Shader Graph UI as a foundation, I've built a visual scripting system perfect for throwing together simple behaviours.

Action Graphs can live in properties of anything you can open in the Inspector, or in standalone .action assets enabling you to create your own custom nodes.

Programming is so much fun, but can be daunting for beginners. I want S&box to be a fantastic gateway into coding, just like GMod was for my generation. To that end, my evil plan is for Action Graph to lure them in by being as fun as possible to use. After a while they might realize they've actually been programming the whole time, and then give a language like C# a try.

Along with that, it would be fantastic if Action Graph could be expressive enough for even experienced programmers to want to use, even if only for simpler tasks. I think visual scripting has some huge advantages if we really lean into it being visual.
Any property with a Delegate type can store an Action Graph.

public class ExampleComponent : Component { [Property] public Action DoSomething { get; set; } }

Action is the simplest, but you can have parameters too.

[Property] public Action<int, GameObject, string, object[]> DoSomethingElse { get; set; }

You can invoke a graph like any other method.

protected override void OnStart() { DoSomething(); DoSomethingElse( 123, GameObject.Parent, "Hello", new object[10] ); }

Finally, you can make your own nodes in C# as static methods.

// Increments the value by 1.
[ActionGraphNode( "example.addone" ), Pure]
[Title( "Add One" ), Group( "Examples" ), Icon( "exposure_plus_1" )]
public static int AddOne( int value )
return value + 1;

// Waits for one second.
[ActionGraphNode( "example.waitone" )]
[Title( "Wait One" ), Group( "Examples" ), Icon( "hourglass_bottom" )]
public static async Task WaitOne()
await Task.Delay( TimeSpan.FromSeconds( 1d ) );
If you've got access, I'd love to hear what you think after giving it a try. There's certainly some bugs and rough edges, but all feedback is welcome.

Here's some docs to get you started:

Please direct your bug reports and feature requests here:

Have fun!