Lets have a chat about a prototype I’ve been working on for the last few weeks then we’ll take a high level tour of how it’s put together.
On a recent VR projects we developed a system where interacting with elements in the world could set off a sequence of actions. As an example, putting your hands underneath a tap would be the trigger for turning on water particles and start playing running water audio. Setting these interactions up could be quite fun and often logical at the same time. This seemed like a good jumping off point to make a puzzle game.
Without further ado, here’s what I made:
In the first example, the player must move the ball to the right and into the yellow cylinder to complete the level. By hooking up the “Every 1S” trigger and the “Move Right” action the ball is moved right every second.
The more complex second example relies on selection of blue cubes to move in the correct order in a timing sequence which eventually pushes the ball into the yellow cylinder. Here the player uses wait nodes to make sure all the blue cubes aren’t moved instantly at the start of the level.
A Little Deeper
In distilling the idea down, I came up with the following node types:
– Triggers: Start the execution flow when a specific condition is met. E.g.
- When the game starts
- When a timer expires
- When a button is pressed.
– Actions: Manipulate objects in the world. E.g.
- Move the player around
- Turn on/off lights
- Open/close a gate.
– Waits: Halt the execution flow for a period of time. Very useful in chaining together actions to execute over a period of time.
How it Works
Even though the prototype is still very simple, lets crack open the lid and look inside it.
From up high, a level contains the real world objects on the right and the node composer on the left. The composer placement is always the same across all levels but the real world objects can be anything that makes a fun puzzle.
Inside the code base and with MVC in mind, nodes are split into 3 distinct groups:
- Data: Small, data only classes.
As an example a “MoveRightAction” would contain a reference to an object.
- Executor: Takes a data class as input and performs some actions on that.
A “MoveRightActionExecutor” would find the object reference on a “MoveRightAction”, grab its transform and act upon it.
- Renderer: Creates a visual node on the composer, sets up the text and any special properties of a node.
Architecturally, this split is really useful and gives some great benefits:
- A level containing a group of nodes can simply be serialised to XML/JSON without having to pick data away from execution or rendering variables
- Executors are only ever useful once the “Play” button has been pressed. As they are separate, they are only created at that point and removed when the level is stopped.
- Modifying rendering code is not going to affect execution and vice versa.
- Reduced dependancies leading to easier to understand and maintain node code.
Judge, Jury and Executor
Nodes are all good and well but won’t be doing all that much unless there is a high level system that tells them when to execute. Enter the ComposerExecutor.
The ComposerExecutor knows about every trigger, action and link between them. Every frame, triggers are queried if their conditions are satisfied. E.g. If a button has been pressed. Then if this condition is true, the ComposerExecutor finds all the links that flow out of this trigger and executes the actions that they link to. This continues from trigger, through link to action until the player has completed the level or the “Stop” button is pressed.
Under the Hood Summary
One of the most important tasks in keeping a code base clean is splitting classes into smaller behaviours that have fewer concrete dependancies between them. This project lends itself perfectly to that. I’ve only given a very high level overview of the architecture in this prototype but I hope it gives a good idea of how it is put together and the benefits of doing it like this.
After working on something for a while, it’s useful to reveal it to public scrutiny outside the four walls of this office to see if the idea has legs. I’m on the fence as to whether working on this or leave it in the land of interesting prototypes.