Jump to content
  • entries
  • comments
  • views

Cruisin' On Auto-Pilot - Part I



I mentioned in a previous post that the Christmas Carol game employed a sequencing or scripting engine I created to handle cut-scenes, the so-called "Auto-Pilot." I though I'd give some details on that for those interested in such things. I'll try to not get too technical, but anybody curious enough to wander into this blog and wanting further information can just contact me directly and I'll share what I can.

First, a bit of background. Back in the yonder days of 2008, when I was working on an Intellivision version of Pac-Man, I thought that apart from the normal AI of the ghosts I would need some sort of mechanism with which to script the Pac-Man cut-scenes. My idea was that it would leverage the normal game engine, but provide various commands to position and move the sprites without having to write special-purpose code or individual state machines. The cut-scenes were static animations, typically moving and animating the sprites in specific ways, so I wanted a way to define a "script" that said something like "start Pac-Man at the left-edge, move him to the center, turn up, and walk off the screen, etc." Something like that.

My self-prescribed requirements at the time were indeed very simple: provide a mechanism to script each object individually. Thus, the focus was on a per-object script, rather than an overall script for the entire scene. This is a decision which will come to haunt me later, but at the moment it made perfect sense: there is no "scenery" in the Pac-Man's cut-scenes, only moving sprites.

So I went and created such a scripting engine, which supported very simple commands such as MoveX, MoveY, SetPositionXY, SetDirection, etc. I used it to animate the ghosts in their pen, and was planning on testing it for the return of their eyes after getting eaten. I called it the Auto-Pilot. The full list of supported commands is below:

    @@Delay         EQU     NEXT_IDX
    @@Wait          EQU     NEXT_IDX
    @@GotoScript    EQU     NEXT_IDX
    @@GotoStep      EQU     NEXT_IDX
    @@Stop          EQU     NEXT_IDX
    @@MoveToTile    EQU     NEXT_IDX
    @@MoveToX       EQU     NEXT_IDX
    @@MoveToY       EQU     NEXT_IDX
    @@MoveToXY      EQU     NEXT_IDX
    @@MoveToTarget  EQU     NEXT_IDX
    @@MovePix       EQU     NEXT_IDX
    @@SetTarget     EQU     NEXT_IDX
    @@SetSpeed      EQU     NEXT_IDX
    @@SetDirToX     EQU     NEXT_IDX
    @@SetDirToY     EQU     NEXT_IDX
    @@SetDir        EQU     NEXT_IDX
    @@SetState      EQU     NEXT_IDX
    @@SetFlag       EQU     NEXT_IDX
    @@ClearFlag     EQU     NEXT_IDX
    @@FlipFlags     EQU     NEXT_IDX

Here's the very first script I made for the ghosts moving up and down inside the pen, courtesy of my source version-control time machine:

;; Title:       Auto-Pilot Scripts                                          ;;
;; By:          DZ-Jay                                                      ;;
;; Description: Data structures for Auto-Pilot scripts.                     ;;
;;                                                                          ;;
;; Copyright 2010, DZ-Jay, <dz@caribe.net>.                                 ;;
                ; +---------------------------+-------------------+-------------------+
                ; |     Command:              | Param1:           | Param2:           |
                ; +---------------------------+-------------------+-------------------+
@@Up:           DECLE   AUTO_CMD.SetSpeed,      GHST_CAGE_SPEED,    0
                DECLE   AUTO_CMD.GotoStep,      AUTO_STEP(4),       0
@@Down:         DECLE   AUTO_CMD.SetSpeed,      GHST_CAGE_SPEED,    0
                DECLE   AUTO_CMD.GotoStep,      AUTO_STEP(6),       0
@@Cycle:        DECLE   AUTO_CMD.SetDir,        SPR_DIR.Up,         0
                DECLE   AUTO_CMD.MoveToY,       $2E,                0
                DECLE   AUTO_CMD.SetDir,        SPR_DIR.Down,       0
                DECLE   AUTO_CMD.MoveToY,       $32,                0
                DECLE   AUTO_CMD.GotoStep,      AUTO_STEP(4),       0

Scripting Carol
Later on, still back in the halcyon days of 2010, when Pac-Man begat Christmas Carol, this simple scripting engine would serve as the basis for the Ghost Of Christmas Presents' movements during the initial phase of development, when he just moved in patterns instead of finding his own path using AI. It was straightforward and effective, and rather powerful. As you may imagine, altering the script was a matter of tweaking the command parameters, and you could "attach" one of these scripts to any of the game engine sprite objects simply by telling the framework something like "enable Auto-Pilot on sprite X."

Eventually, the Auto-Pilot grew into a more feature-rich scripting engine when it was used to great effect for the animated cut-scenes of the final Christmas Carol game. It was wrapped in a larger framework I called the "Stage Intro Sequencer," which handled all the infrastructure for the cut-scenes; things like initialization, clean-up, drawing the background scene, revealing the stage title and number, launching the Auto-Pilot scripts for each object in use during the cut-scene, etc.

Technical Details
The Auto-Pilot itself is exceedingly simple, its design is elegant and streamlined, but it is very versatile. The engine is just a state machine with a dispatcher: it has a table of routines for each script command available; as it transitions from one state to the next, advancing through the script, it calls the appropriate routine for the current step. When the command routine completes, depending on the type of command, it either returns control to the game or calls the Auto-Pilot dispatcher to jump to the next step.

An important detail to note is that the Auto-Pilot is not directly called by the game engine, at least not in the way it would if it were just one more game feature. As mentioned before, for better or worse (and I think for the worse, as I shall comment later on), the Auto-Pilot was designed to attach itself to a game object (logical sprite). Therefore, the execution of the script is performed from the perspective -- and within the context -- of the object.

In other words, all the commands used in an Auto-Pilot script can be understood to manipulate a particular game object, and not just the general game-state or background scenery. I worked-around this limitation, as I shall discuss later, but it was always more of a hack than part of the grand unified vision.

This is the main reason I called it the "Auto-Pilot," since it was intended to be a means to create scripted objects which move under autonomous control.

The Sprite Object Record
In the P-Machinery game engine (the framework I devised for Pac-Man and later used in Christmas Carol), each logical game object is defined as a data structure called the Sprite Object Record. This record includes a number of fields that describe the state and attributes of each object; things like its position, direction of movement, current state, displacement speed, various attribute flags, etc. Among the fields is a pointer to an Auto-Pilot script.

P-Machinery itself does not prescribe the structure or its fields; it just treats each Sprite Object Record as an atomic unit, passing it around various game-specific routines that use it to process the game state. It does reserve some basic fields for itself, like the ones to handle object state and attribute flags; but even those afford a degree of flexibility to the game program which can, for instance, define any number of states relevant to game-specific objects.

Below is a full list of all fields used in a Christmas Carol sprite object.

; --------------------------------------
; Defines a data record for the Ghost
; sprite object.
; --------------------------------------
                ; Address Pointers
@@GRAM          EQU     GRAM_BLOCK.Elf                  ; Pointer to GRAM block
@@System        EQU     .SYSMEM                         ; \_ Keep pointers to the RAM where this record is stored.
@@Scratch       EQU     .SCRMEM                         ; /

@@ObjType       EQU     _stype.Sprite
@@ObjIdx        EQU     .anim_obj_idx

                ; 16-bit System RAM
@@AnimSeq       SYSTEM  1                               ; Pointer to animation sequence
@@SpeedRate     SYSTEM  1                               ; MOB speed rate
@@TargetFunc    SYSTEM  1                               ; Pointer to auto-targeting routine

                ; 8-bit Scratch RAM
@@sX            SCRATCH 1                               ; \_ MOB screen coordinates (sX,sY)
@@sY            SCRATCH 1                               ; /
@@tX            SCRATCH 1                               ; \_ Target virtual tile
@@tY            SCRATCH 1                               ; /
@@State         SCRATCH 1                               ; Current Sprite State
@@Flags         SCRATCH 1                               ; Status flags
@@DirCurr       SCRATCH 1                               ; MOB current direction vector
@@DirNext       SCRATCH 1                               ; MOB next direction vector
@@RateFrac      SCRATCH 1                               ; Left-over MOB speed fraction
@@ScriptSeq     SCRATCH 2                               ; Pointer to sprite auto-pilot script pointer
@@Mob           SCRATCH 1                               ; Assigned MOB number

As you can see above, the second from last field, @@ScriptSeq, is intended to hold a pointer to an Auto-Pilot script. Another relevant field for our discussion is @@Flags, which is an 8-bit register holding the state of various sprite attributes. The full specification of the bit-field is below:

;     +---+---+---+---+---+---+---+---+
;     | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
;     +---+---+---+---+---+---+---+---+
;       ^   ^   ^   ^   ^   ^   ^   ^
;       |   |   |   |   |   |   |   '-----| Flip adjustment
;       |   |   |   |   |   |   |
;       |   |   |   |   |   |   '---------| Stealth
;       |   |   |   |   |   |
;       |   |   |   |   |   '-------------| New Tile
;       |   |   |   |   |
;       |   |   |   |   '-----------------| Animate
;       |   |   |   |
;       |   |   |   '---------------------| Active
;       |   |   |
;       |   |   '-------------------------| Flip X
;       |   |
;       |   '-----------------------------| Hit
;       |
;       '---------------------------------| Auto-Pilot

Hopefully it is obvious from the above list that, apart from generic fields like Active, Animate, and AutoPilot, the rest are employed for very game-specific attributes. Christmas Carol was the first application of the P-Machinery framework, and both grew organically together; and as the complexity increased, some compromises were made along the way that fused various pieces together. This is the main reason why P-Machinery was later released without a sprite engine: because a lot of it was interdependent on game-specific functionality which was not adequately generalized.

On the one hand, Christmas Carol was completed and released to great success; on the other, the generalized, all-purpose game development framework that P-Machinery was intended to be, never reached its full potential. This is a problem I intend to fully rectify with P-Machinery 2.0, aptly named P-Machinery AGE (Advanced Game Engine).

Engaging The Auto-Pilot
During game-play (or at any point in the game program), engaging an object "on auto-pilot" involves just assigning a script to its @@ScriptSeq field and setting its Active and Auto-Pilot flags. The rest is handled automatically by the framework. As the P-Machinery engine periodically handles the motion routines of each active object, it test the Auto-Pilot flag. If set, it will call the Auto-Pilot dispatcher; otherwise, it will invoke the motion routine as normal.

The basic algorithm is like this:

  • If the object Active flag is set then
    • If the object AutoPilot flag is set then
      • Call the Auto-Pilot dispatcher for the object
    • Else
      • Call the normal motion routine for the object
  • Else

    • Do nothing


In pseudo-code, it'll look like this:

procedure MoveGhost {
    // Only handle the Ghost if it is active
    if (GhostObj.Flags(Active) == True) {

        // Determine whether we move the Ghost
        // or let the Auto-Pilot handle it.
        if (GhostObj.Flags(AutoPilot) == True) {
            Call AutoPilotDispatcher(GhostObj)
        } else {
            Call MoveEnemy(GhostObj)


Thus, each object can engage its own Auto-Pilot script, which works even during normal game-play. In fact, Christmas Carol is full of such little scripts; they handle many of the scripted animations and motions which do not depend on game-state or the normal path-finding logic. An example of this is when the Ghost stops periodically in the middle of game-play to "look around." Another one is when the elf dies, which invokes a complex "death" animation sequence.

Coming Up Next...
In the next installment in this series we'll delve deeper into the guts of the Auto-Pilot engine to see what makes it tick. We'll follow that with a technical description of the available commands and what they do. Eventually, we'll present some sample scripts from Christmas Carol and hopefully all this background will help to illustrate how an insane amount of versatility and complexity is encapsulated in such a simple interface, which affords an exceedingly large amount of creative expression.

Finally, we'll conclude with some lessons learned from the practical application of the original Auto-Pilot engine to the Christmas Carol game, and what design constraints, limitations, or principles we would change or improve upon for its next incarnation in P-Machinery 2.0.



Edited by DZ-Jay
Fixed code formatting.


Recommended Comments

There are no comments to display.

Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...