The Thin Silence is an cinematic narrative adventure game with both puzzle and exploration elements, told through the introspection and recollection of Ezra Westmark. Find creative ways to make your way out of the darkness, uncovering the journey that lead him there and the hope that leads him home.

Post news Report RSS Proof - Ready, Set, Action!

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...

Posted by on

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:

  • Not an external language (binding all the functions would take too much time)
  • Syntactically light and logical
  • Flexible and easily extensible in specific cases
  • Possibly allow player to retain control during the scene
  • Allow other game objects to continue updating during scene


  • 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.

    actionscript code:
        public function FirstCutscene()
        {
          super();
             
          addStep(panCamera, [320, 180, 0.001]);
          addStep(waitForTime, [4]);
          addStep(say, ["Proof"]);
          addStep(fadeToBlack);
          addStep(waitForTime, [4]);
          addStep(fadeFromBlack);
          addStep(waitForTime, [2]);
          addStep(crackBlock);
          addStep(waitForTime, [2]);
          addStep(fallBlock);
          addStep(waitForTime, [0.5]);
          addStep(changeLightBeam);
          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.

    actionscript code:
      /**
       * Add a new step to the cutscene list.
       * @param f Functionality
       * @param p Parameter Array
       *
       */
     
      protected function addStep(f:Function, p:Array=null):void
      {
        stepList.push(f);
        paramList.push(p);
      }


    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,

    actionscript code:
        protected function waitForKey(p:Array):void
        {
          if (Input.pressed(p[0])) 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!):

    actionscript code:
      /**
       * 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[0], -21, p[1] - p[3]/2, p[2] - p[4]/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:)
    Oh dear
    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.



    Thoughts


    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

    Post comment Comments
    Komposten
    Komposten - - 116 comments

    This must be one of the things I enjoy the most about ModDB; reading about how other people, experienced or not, go about in solving different problems (and explaining what the problems are, and why they arise).

    I'm personally not into AS, being a Java programmer, but it is still really wonderful to read posts such as these. (Though it might be annoying for non-programmers :P)

    You should definitely keep up the posting of this kind of posts!

    Reply Good karma Bad karma+1 vote
    e_Glyde
    e_Glyde - - 331 comments

    I like how you tell the people what you are doing?

    Reply Good karma Bad karma+1 vote
    Post a comment

    Your comment will be anonymous unless you join the community. Or sign in with your social account: