Since there was a fairly positive reaction to the last writeup I did, regarding our lighting, I thought I would write another piece corresponding to what I’m doing right now.
When we set out to make Proof, we knew that cutscenes would play a big part of the game (since it’s actually story centric, an unusual turn for us). This lead us to have to make some very difficult decisions regarding the implementation of scripted events. We came to a list of things we required:
How did we achieve these?
ActionScript supports functional programming! Our cutscenes are defined as a list of functions, here’s an example of a defined scene.
addStep(panCamera, [320, 180, 0.001]);
addStep(end, [true, true]);
So, what you might notice right away here is the slightly non-standard syntax. AS3 doesn’t allow me to define an arbitrary number of arguments for each function I’m calling. The system passes the array of arguments as one actual argument, and the functions have to be smart enough not to access non-existent indices. (This can be done with varargs, but the performance suffers apparently and it provides no more documentation than this does).
Underneath this is a simple Stack implemented using a Vector (built in AS3 type) that just plays the next step until it's told to move on.
* Add a new step to the cutscene list.
* @param f Functionality
* @param p Parameter Array
protected function addStep(f:Function, p:Array=null):void
One might suggest that the parameter array and the function reference should be paired and use one stack, it makes little difference here from my perspective.The functions themselves don’t actually get called in the way one might expect either,
if (Input.pressed(p)) nextStep = true;
Looking at this, you might be able to guess how it works - but I’ll explain anyway. The Cutscene handler calls the step once-per-frame until it decides to say nextStep = true, at which point it visits the next step. This has many benefits, one is obviously shown above (trivial to implement a wait) but this also means that the game’s update loop continues to run during every cutscene.This is not particularly self documenting sadly, so commenting becomes very important (including a TODO and a magic number, I'm not perfect okay!):
* Displays an image on screen, in front of everything else.
* @param p [image, x, y, width, height]
protected function showImage(p:Array):void
shownImage = room.addGraphic(p, -21, p - p/2, p - p/2);
//TODO: could get width and height from the image?
nextStep = true;
This implementation also means that my cutscenes can access any object and/or method in the current game making my life much simpler (i.e. not having to write many wrapper functions to achieve things the game does anyway, no message passing etc.). I wanted to avoid having to write functions to control the camera, when I could directly access the object and its methods.(Sidenote, this method leads to a large number of bugs where I forget to set nextStep. See below:)
That means that any really specific functionality can be modular and contained within other classes, which brings me to the other point about modularity. In the scene code I showed earlier, we called a function called crackBlock() this is not a global function, it is one specifically defined for this one scene. The system allows both aspects of modularity (subclassing and and encapsulating special cases), making code quite pleasant to look at.
Obviously, there is some annoying syntax involved (repeating addStep() isn’t ideal) but this was the best method I could think of when trying to work with our initial set of conditions. It’s an interesting design pattern, but it’s one that works for me - especially as someone who hates cutscene programming. Between this and FlashPunk’s built in tweening, it takes a lot of tedium out of scripted encounters.
In my early time as a programmer, I would have dived into an external (and possibly in-house) scripting language but as time goes on I realise, there is no need to put more work in than the game actually requires. It becomes much more important to be able to make the game not all the systems.I also found that when I was starting out, no-one ever outlined how one might implement a cutscene system. It’s fairly straight forward logically, but very easy to get bogged down in. Our (very) old release EverEternal WinterWorld 2 used a fully externalized Lua system, which was very powerful but took forever to create (and was very challenging as a 15 year old!). Before that, we made the original EverEternal WinterWorld that uses an INI based scripting system, that was a shocker - but once again as 12/13 year olds we didn’t know better.
So, there’s another insight into our process. I hope you guys are enjoying these, I don’t profess to be an expert on this or any matter and this is not the method you should use, just a method - but I wish I’d seen more content like this when I was younger so I’m trying to give back!If you want to know more about the game here are some links:
Our Website - Our Devlog - My Twitter - Ricky’s Twitter