Page Header

Nodebox: Visual Scripting for UGC (now) at your fingertips

I hate visual scripting*

First thing I wanted to make in S&box was a scripting API, so that people could make minigames on Sandbox-like gamemodes and stuff like that.
I literally have 5k hours of playtime on a Garry's Mod sandbox server(s) just scripting random thingamajigs:
  • (Expression 2) Whole ass PvP/Competitive gamemodes like Control Points / Supraball-something / etc
  • (Expression 2) "Shader" library that draws fractals, Raymarches stuff or just trace-scans a photo (with water reflections, texture sampling, world-wrap, debanding and supersampling)
  • ...
  • (Starfall) A couple 2D multiplayer games on a screen, where people can gather around and play them
  • (Starfall) A cat png that chases you, flips you off, drops a bunch of metal pipes on top of you and runs away
  • ...
This is what got me into coding/game development in the first place.
Developer experience was horrible..?, but I didn't know any better.
Now I have a LuaVM Godot extension, a bachelor's thesis on a coding-learning sandbox game/platform of some sort and way too many thoughts on scripting APIs.
Lua is still great, but I learned to hate it too. (fuck you Roberto Ierusalimschy, with your ~= and 1-indexing)

Visual scripting, yeah, visual scripting,
It's cumbersome :) (most of the time)
It's great for shaders/materials (very gud) and for small event logic (only sometimes)

Although, Blueprints in Unreal Engine are fine, they have variables, functions and libraries. It's the nicest visual scripting system you can try. (and materials are great too)

* - but,
"normal" people - non-nerd players that play your game - don't want to learn a programming language, they just want to cobble some stuff together and make RGB neon lights for their car.
This is what Wiremod in Gmod is - a very basic way to make funky contraptions come to life.
I still have no idea how people got into it, since you have zero-to-none help understanding it in-game, only tutorial videos on Youtube (most of which are already 8+ years old).

Wiremod is great, but it was so fiddly that I never want to comeback to it ever again.
I hate the fact that almost all wiremod contraptions are only understood by the person who made them.
 ???? A hunter?x???prop with 100 gates ?? and a mess of ????? wires ????

This was my stance on visual scripting before, but now...

"Play" Resonite.

Without a VR headset it's kinda weird, but at the very least, look up a Youtube video on it.

The visual scripting there is fucking ASS, but the framework is insane
(buuut it handles nicely in VR, which makes it one-of-a-kind)

The gist of it is:
  • All nodes exist in 3D space and everyone can see them
  • You can collaborate on scripts together, or just help someone understand stuff
  • Nodes can be hidden away (into GameObjects)
But, the most mind-bending things are:
  • You can upload anything from your PC and it just works
    • Models, Images/Textures, Sound Effects, Text Files, etc
  • You can control any property you want:
    • Set the model material, change the shader, move stuff around, parent/unparent, etc
      • Yes, that means every avatar is "constructed" in-game, maybe even by multiple people, one person is importing stuff, someone else is configuring materials, and another dood is plucking boogers while making node logic so the avatar farts when it walks
  • You have a personal inventory, where you can save any creation (and it has folders)
There are a lot of worlds where you can "symlink" folders shared by others to your inventory
I've seen everything:
  • Multi-tools
  • A 2D plane that's snaps nodes onto itself for ease of scripting (yes, it's a tool made for visual scripting in the same visual scripting engine)
  • Working minigames (sudoku/uno/...)
  • Equipable Half-Life HUD
  • and a fully-working Tardis...
Here's an Imgur album with more clips/screenshots if you are interested
(Nope, I lost all the clips where we were spawning random shit, like Sudoku, Tardis and others)

And, yes, this does look complex, but it's because we were balls deep into making a button that screams "YIPPIE" with it's pitch driven by it's scale, the sound effect for which was recorded in-game via an in-game microphone tool from my inventory...

But, the visual scripting itself is very bloated imo, backwards-compatibility is a bitch, and it makes the whole scripting system neigh-incomprehensible to a beginner.
Secondly, it's lacking libraries or even just the ability to collapse a group of nodes into a single node.

Foundations

Nodes
I knew that I want nodes to be completely separate from any Components/GameObjects.
Someone will eventually want to make a 2D node editor, a fucking NodeGraph-to-Lua transpiler or something else completely.
By making a `Node` it's separate thing and then implementing a wrapper on top of it (`Node3d`), I ensure that any weird visual scripting fantasies can be realized.
My first visual scripting fantasy was to make a `Node` just has more `Node`s inside of it, wouldn't it be neat?
Also, Nodes don't handle execution at all, they just sit there until a wrapper tells them to recalculate outputs.
("Pulse"/"Signal" pins aren't there yet, still thinking of a better way to implement them in the `Node` base class)

`Node`s store both their input and output, which sounds like overkill, but I like that you can read both (if you hover on a pin f.e.), even if it doesn't have anything connected and isn't being processed right now.
`Node` also needs to be able to add/remove it's pins, because sometimes you want that (Array/List/Dictionary initializers, sequence, etc).
`Node`s need descriptions/docs, and, also, groups and aliases, so you can propertly search for them.

Also, you need to a way to get to wrappers from them, but a single `Node` can even have multiple different wrappers at the same time. So, GetMeta<T> / SetMeta<T> were added too.

Wires
Same.
They should be wrappable, be as abstract as possible, and just pass outputs into inputs when told so.
Same metadata trick too.

Also, Nodes and Wires need to reference each other, but because everything is "in memory" and should have wrappers around them in general, I decided to shit out a ton of WeakReference<T>s, not sure if it's working properly, but seems to be fine.

The "Evaluation Engine"
Nope, it's just some goofy GameObjectSystem, that is separate for each group(node3d, wire3d) of wrappers.
There's definetely a better way, but I have NO idea what's the best way to actually evaluate them yet.
And it's almost the least complex thing in here, so, nah

Ideas for the Future

I have multiple scripting, visual scripting and VR enthusiasts behind my back.
And they don't stop yapping.


This shit needs execution flow pins ASAP
(Non-Pure nodes in Blueprint, Discrete nodes in Resonite)
Basic human need to have a button make sound when it's pressed.

Collapsing Nodes into one
(Macros in Blueprint, Fuck-all in Resonite)
Simplest way to reuse code.
Also allows people to make "kindergarten libraries", just stuff some collapsed nodes into a dupe, or save them locally, I dunno.
Collapsed Nodes are just collapsed nodes though, so if you uncollapse a node, edit it and collapse it back together, none of the others change.

"Contexts"
This might be like the second coming of Crist, but for 3D visual scripting, maybe.
A place where you can create context-wide functions and event handlers.
Normal libraries will be registered and edited there too.
Will also be the place where you allow other players to mess with your stuff.

Other feature ideas include:
  • Node Search
  • Tooltips for nodes
  • In-editor Node3d WorldPanel rendering, if even possible
  • SceneTree Explorer (to grab properties of anything, from anywhere)
  • Multiplayer support
  • A 2D Grid to snap nodes to
  • Packing/Unpacking nodes into GameObjects (same as Pack/Unpack in Protoflux)
  • Custom panels for some nodes, so that they are more compact (+, -, *, sin, ...)
  • VR Support, lol
Or, some funsies from the development

S&box Reflection or "How To Tear Your Balls Off"

* If you are familiar with the default C# reflection

I knew I really wanted nodes like Add<T> and not AddFloat, AddDouble. Which lead me to the `TypeLibrary` global.
I didn't expect to have any reflection when I thought of starting this project, but Facepunch actually gave me something that kinda works.
C# generics can be fucky by themselves, but with the `TypeLibrary` it all multiplies tenfold.

Ok, what type do you think this returns?
TypeLibrary.GetType<Drive<float>>().TargetType
That's Correct!
TypeLibrary.GetType<Drive<float>>().TargetType == typeof(Drive<>) // true
TypeLibrary.GetType<Drive<float>>() == TypeLibrary.GetType(typeof(Drive<>)) // and this is, also, true
Yeah, it doesn't work with generics, it's in the docs, thankfully.
Now, if you want to create an instance with generic arguments, you can only do
TypeLibrary.GetType(typeof(Drive<>)).CreateGeneric<Node>([typeof(float)], ...);
It came out fine in the end, but this cost me a couple headaches to implement around.

Next, what do you think this returns?
TypeLibrary.GetType<Drive<float>>().GenericArguments
Yeah, maybe, now, you understand why I needed the generic type's TypeDescription.
TypeLibrary.GetType<Drive<float>>().GenericArguments // [ typeof(T) ] (name of the generic argument)
typeof(Drive<float>).GetGenericArguments() // [ typeof(System.Single) ] (actual generic argument, from a closed generic type)
This one actually forces me to write overrides like this shit into every node
public override Type[] Generics => [typeof(T)];

I still have a lot of fucking around and finding out yet to do though
If you have any suggestions, message me on discord or something (@manonox)

I have yet to see a more disorganized math library

Disclaimer: this ain't Facepunch's fault
I almost lost my voice after laughing in a discord vc for a solid 10 minutes, after seeing the MathX static class.

WE FUCKING HAVE THEM ALL, LADIES AND GENTLEMEN
Math, MathF and the elusive... MathX

Like, let's make MathA, MathB, MathC, MathD, ...
I despise C# sometimes. They forgot to add some cool methods to MathF, so they added them in Math..?
Also, I don't know if Microcock can add static class extensions or they would break stuff, but this is just insanity.

I have a plan to implement everything as extension methods on float/double/int and vectors, just so I don't have to come near this mess.

Some vector types(including Rotation, Angles and Color) are missing a ton of methods, especially Vector2Int and Vector3Int.

Also, how the fuck does C# not have a Pow method for ints?
// Awful
public static int Pow(this int x, int exponent) => (int)Math.Pow(x, exponent);

// Awful, Not whitelisted
public static int Pow(this int x, int exponent) => (int)System.Numerics.BigInteger.Pow(x, exponent);
Or, maybe, I'm just a whiny bitch, idk

I almost bounced back to hating frontend

Razor is cool, Xml/Html is.. something, SCSS is great
But the whole time, Intellisense was screaming at the top of it's lungs that I have some ambiguities between classes or whatever the fuck.
Facepunch, please fix it ASAP, I beg of you, I once had half my .razor file irreversibly nuked by Intellisense replacing html/xml stuff with a using directive (you can't undo, if you don't notice it immediately).

In-engine "Highlight Selected" thingy centers the rects at the top left of your screen for (Center,Center)-aligned WorldPanels.
That would be fine, if I could just set the alignment to (Left,Top), but it breaks labels for some reason.
(And the render bounds get fucked too)
(See #7389)

And gradients don't work with transparency, boowomp :(

Also, we definitely need a VSCode extension that shows you which CSS properties and values are usable in S&box. (and or their caveats) Won't come from Facepunch themselves, but I hope some frontend engineer get's mad enough. Or, maybe, it's only me.

Weak-ass IEnumerable<T>

A short one, how come are these not in C# by default?
public static IEnumerable<(int Index, T Item)> Enumerate<T>(this IEnumerable<T> ie)
{
    var max = ie.Count();
    return Enumerable.Range(0, max).Zip(ie);
}

public static void ForEach<T>(this IEnumerable<T> ie, Action<T> action)
{
    foreach (T item in ie) action(item);
}
I've literally ignored sleep for the past week, that's normal, it's how I operate.
Gonna take things a bit slower now.

First, I want to fix some dumb things:
  • `Node` Name/Description/etc. implementations
    • The less code you have to write to make a new node type, the better
  • Reimplement most nodes with generic type constraints (via "where"), things like Add<T>, Clamp<T>, etc
  • Lack of comparison, bitwise and vector nodes, more wiremod feature-parity
  • A utility screen panel that shows you that you have some panel focused
    • People get stuck in `Constant<T>` TextEntries, it's hilarious
  • And a general API pass, because it's ass

I might lose the Drive<T> to work on this project at any time.
I'm 100% sure I'll fix the stuff mentioned above, though.
If enough people show interest, I'll definitely get to QoL/actual features.

Ah yes, the library is here:
https://sbox.game/manonox/nodebox

And the source is here:
https://github.com/manonox/nodebox

Feel free to shoot feature requests for nodes you need, I will get to them, If they are simple enough.

Smash that review button,
Leave a subscribe,
Like this channel,
or whatever the fuck


                                         , peace

Comments