Sequence Script Strategy in Unity
I’ve recently completed a course to implement an interactive VR experience called Space Cadet. This is the resulting demonstration;
Interactive Ghost Train/Escape Room
I think of this sort of application as a fair ground Ghost Train. It takes you on a journey and while you can move around you’re mostly restricted to the narrative the game provides. I.e. with the exception of the final sequence the player only has limited movement, in this case they always remain seated. However, it is interactive, the player must complete a sequence of tasks similar in nature to an Escape Room. What does this mean for the design?
Developer vs. Designer
I’ve never liked this phrase, but it’s a convenient way of separating the problem. As with most front-end development the problem of integrating a graphical design with code is something that needs to be addressed. In this example I considered the ratio of effort to be much more Design than Coder. My rationale was this;
The Ghost Train aspect is mostly Designer led, they need the player to be taken on various mini journeys through the scenes and props. In a professional environment there will be feedback and changes. These will most likely be about the visuals, e.g. can we make it use this route, can we have another prop here, etc. Therefore I wanted to make it as easy as possible for a Designer to make these changes without involving a coder.
The Escape Room aspect is also mostly Designer led, they have an actor’s screen-play (to distinguish from code script) that needs to be carried out line by line, task by task. I.e. it’s a sequence of (mostly) ordered steps. Again, the likely feedback would be about changing-play, i.e. Design changes.
How to Implement a Ghost Train in Unity?
I’m not sure who suggested this (sorry I’ll quote you if I find out) but the use of the Cinemachine Dollycart and track is pretty much ideal for this sort of problem.
The Dolly-cart track provides a visual route through the props,in this case asteroids, where speed and pitch can all be controlled without any changes to code or more complicated animation sheets. What it can’t really do is bring props to life, e.g. in the above case a particular voice should play, the music changes, etc. These can be implemented with Timelines which are ideal in non-interactive sequences.
Yay, it fulfills the Designer-led requirement. But what about the interactive aspect?
How to implement an Escape Room in Unity?
After reviewing the tasks I realized that this particular Escape Room mainly consisted of the following tasks;
- Play a voice over
- Flash and Enable a button
- Wait for the button to be pressed
- Play a dolly track
- Play a timeline
One typical Unity approach to this is to create a state machine that knows where the player is and what should happen next. Inside the state machine there would be logic to say something like;
IF the player is in the MakeCoffee Scene
AND the coffee machine button has not been pressed
THEN Flash and Enable the coffee machine button.
My problem with this approach is that it requires different code for each state. Given my self-imposed Designer-led requirement then this doesn’t work for me. What I needed is a more Designer friendly, less-code approach.
What I realized was that I could treat each tasks as a sequential step that has either completed or not. This means that if I can represent the tasks as a generic step then a designer could simply build any configuration of an Escape Room scene by drag and drop. Sounds good, fortunately it’s pretty easy to code too. Win-win.
The generic step is very simple. To be a step you must implement StartStep(). Once your step has completed its task you should then call RaiseCompleted(). How simple is that? Even I could write code that uses that :)
But what does a real life example look like?
Make Coffee in Unity
One of the Escape Room activities was to carry out the following;
- Play a voice-over explaining that the player should make some coffee
- Flash the light of the coffee machine button
- Wait for the coffee button to be pressed
- Create some coffee
- Either wait for some coffee to be drunk or for a timeout
- — end of scene —
Let’s take a look at the slightly more specialized but still generic-to-task steps.
Playing a voice over is just a step with the additional need to know what voice over to play.
This is an example of a ‘fire-and-forget’ step. I.e. the action it carries out, playing the voice over, is still continuing when the step declares that it has finished. Buttons are different;
This step starts by allowing the user to interact with the button. It then waits until the button has raised an event to say it has been pressed. That’s its signal to declare that the step is complete. Hopefully you can see that the system is very easy to code/maintain/extend. All that was left was to create a library of these steps;
Okay so we have a toolbox of steps we can use. But we still need a state machine to run them, so whilst a Designer can choose a type of step they can’t alter the flow through them. The trick is, don’t use a state machine, or at least we only need a very…very…very simple one.
The dumbest State Machine
A bit harsh sounding but it must be pretty close to the simplest state machine you can write;
The ItemSequence is basically a Queue of steps, it’s not, because I allow steps to be become active again after they’ve run…makes testing a lot easier. Basically it just takes the first non-completed step, asks it to run and waits for the signal that it has finished running. Easy. Given it’s a behaviour we can add that directly to the hierarchy;
This means that we have away for the Designer to change the sequence, they can add, alter, remove and - with the help of re-order Editor, easily move the steps around.
However, it would be unmanageable to represent the whole of the app with a huge sequence of tasks, so the keen among you would have noticed that the ItemSequence is itself a Step. Therefore they can be grouped and nested. This means you can nest a small group of steps; such as playing a warp effect of Shaders, sounds and skybox change. But it also means you can nest huge ‘scenes’ of the Escape House without needing a separate scene manager;
Since the app is now just distilled to a series of steps that wait to be completed this means that it makes testing really easy. With a quick Inspect Button Editor you can start any step, which also means you can start any sequence. Not only that but by exposing the ‘HasRun’ property of a step you turn certain steps off while testing…believe me if you’ve heard, “Why not make a cup of coffee” for the 400th time you’ll thank me ;)
This is really just another example of utilizing the way Unity can associate scripts with the scene hierarchy. It’s a powerful tool. It’s also example of tailoring your design to not only meet your in-game requirements (the voice over has to play) but with more non-live needs (make it easy for a non-dev to change).