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.
Background
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:
DEFINE_ENUM(AUTO_CMD, 24) @@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 END_ENUM
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>. ;; ;;==========================================================================;; APS_CAGED PROC ; +---------------------------+-------------------+-------------------+ ; | 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 ENDP
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. ; -------------------------------------- GHOST_OBJ STRUCT 0 ; 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 ENDS
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
-
If the object AutoPilot flag is set then
-
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.
-dZ.
Edited by DZ-Jay
Fixed code formatting.
0 Comments
Recommended Comments
There are no comments to display.