Node Script

Node based scripting is steadily becoming an industry standard in everything from gameplay to shaders, presented here is a version i made for a game project during my time at TGA.

The goal was to create a node based script system that level designers could use to quicky implement, test and tweak ideas. They already had experience using unreal blueprints, so it became my main reference during development. I also worked closely with level designer to get a better idea about potential needs and issues. To read more regarding the script system in use you can visit Bastian Randou's portfolio (Link) where he speaks from his perspective.

Lets start with a simple script that rotates an entity around a point:

Every script needs entry points from which the execution flows, here it's the red node 'Update', which executes whenever an entity that uses this script is updated. Execution flows from left to right on so called flow nodes (nodes with white pentagon shaped pins), and from right to left on local variable requests (circular pins).

The script system was developed around an entity component system that is heavily used for almost everything so i designed the scripts to run in the context of these entities (think C++ class to object). In the image you can see the 'This Entity' node which simply gets the ID associated with the entity running the script.

And here is the result (left):

The script system was designed with flexibility/simplicity in mind rather than performance, a good example of this is how scripts read and store long term variables.

Variables are simply created and associated with an entity when needed and there are several types of methods:

Variables can be predefined either in the script editor, or externally. They can also be created on demand by the script itself. The contextual variable is a special variable that attaches a prefix generated from its callstack to its identification, to enable certain scenarios of nested functions.

Functions are another important aspect of languages, function nodes are automatically generated from special scripts that have function input and output nodes, here's a good example of a function as well as contextual variables:

The point of this specific function is to start a countdown when executed, when the countdown is done it should execute from this point, this is implemented using the special 'ExecuteNextUpdate' node which when called executes the next update if it isn't reached. This delay function can be used in all sorts of useful scenarios and this is where contextual variables come in, because variables are associated with an entity you often cannot simply use a variable named "Timer" because a script could directly or indirectly call multiple delay functions all which would use the same variable to keep track of countdown, a contextual variable circumvents this issue by being context sensitive, allowing this sort of interaction.

The node based scripting system has been extremely useful to our development allowing level designers to not only implement everything from puzzles to simple animations, but systems such as the soundtrack/ambience manager:

Here's the script system in action on a puzzle:

It is also worth noting that the script editor ran within the engine, and due to the relatively safe nature of the implementation scripts could be changed while in use, giving instant feedback on changes if needed. This was not without issues of course but overall it was a good feature.

The script system and node editor was implemented in C++, and i used ImGui for the rendering of the node editor which helped out a bit initially.

One of my favorite things in game development in finding ways of improving pipelines, improving flexibility, increasing iteration effiency, etc. The node based system was extremely fun to work on because it pretty much checks all the boxes, as well as being an interesting intellectual challange it feels really great seeing others use your tools to be creative and create more stuff than you can keep track of.

Even as i write this my head can't stop thinking about how to improve the next version!