Design Overview

I wanted to start with a basic overview of the game design, so using the reversed engineered code for CBM Prg Studio as mentioned in part 1, the basic flow of the game is as follows:

  • Initialise (Entry point $1000)
  • Menu_DisplayTitleScreen
  • Menu_DisplayHiScores
  • Menu_DemoMode
    • If ‘F1’ pressed
      • Menu_DisplayTitleScreen
  • If ‘Space’ pressed
    • Menu_SelectControls
    • Level_InitialiseGame
    • Level_GetReady
    • Level_GameLoop
    • If level complete
      • Level_Complete
      • Level_GetReady
    • If princess rescued
      • Quasi_RescueEsmerelda
      • Level_GetReady (Game continues)
    • If life lost
      • Level_LifeLost
      • If lives remaining
        • Level_GetReady
      • Else
        • Player_GameOver
        • Player_EnterHiScoreName (if hi-score)
        • Menu_DisplayTitleScreen

For the key presses in the title screen and demo mode, it sets up a simple IRQ:

        sei
        lda #<IRQ_MenuOptionSelect
        sta sysIntVectorLo
        lda #>IRQ_MenuOptionSelect
        sta sysIntVectorHi
        cli
IRQ_MenuOptionSelect
        lda sysKeyPress
        cmp #KEY_F1
        beq .DisplayInstructionsSelected
        cmp #KEY_SPACE
        beq .StartGameSelected

        jmp krnINTERRUPT
.DisplayInstructionsSelected
        jmp Menu_DisplayInstructions
.StartGameSelected
        jmp Menu_SelectControls

Level Builder

The levels are derived from a list of bits which define the type of level and the type of obstacles on the level:

;-------------------------------------------------------------------------------
; LEVEL DATA
;
; Level Types:
; $01 = Knight Pit
; $02 = Empty Pit
; $04 = Row of Bells
; $08 = Rope Pit
; $10 = Esmerelda's Tower
; $20 = Wall
;
; Obstacle Types:
; $01 = Rope
; $02 = Hi Fireball R-L
; $04 = Lo Fireball L-R
; $08 = Lo Fireball R-L
; $10 = Double Arrow HiLo R-L
; $20 = Double Arrow HiLo L-R
; $40 = Double Fireball Lo L+R
; $80 = Double Arrow HiLo L+r
;-------------------------------------------------------------------------------
tbl_LevelType           byte $20, $28, $22, $21, $21, $22, $28, $21
                        byte $21, $2c, $22, $22, $21, $21, $2c, $31
                        byte $21, $21, $2c, $28, $22, $21, $21, $22
                        byte $21, $22, $21, $2c, $22, $28, $21, $31
                        byte $21, $22, 208, $21, $21, $21, $21, $2c
                        byte $2c, $22, $21, $22, $28, $2c, $21, $31

tbl_LevelObstacleType   byte $08, $01, $00, $00, $02, $04, $01, $08
                        byte $10, $08, $20, $40, $20, $40, $80, $40
                        byte $20, $10, $08, $01, $20, $20, $40, $40
                        byte $20, $20, $40, $80, $40, $01, $40, $40
                        byte $20, $40, $01, $10, $20, $08, $10, $08
                        byte $80, $20, $10, $40, $01, $80, $40, $40

So for level 01 (index 0), tbl_LevelType will be $20 (Wall) and tbl_LevelObstacleType = $08 (Lo Fireball R-L)

These bits can be combined, so level 02 for instance is $28, so has a Wall ($20) and a Rope Pit ($08).

When building the level it has a list of Lo-Hi pointers to the drawing routine for the relevant Level Types:

;-------------------------------------------------------------------------------
; CHARACTER BLOCK & COLOUR DATA
;-------------------------------------------------------------------------------
tbl_LevelCharBlockLo    byte <Screen_BuildKnights, <Screen_BuildPits, <Screen_BuildRowOfBells
                        byte <Screen_BuildRopePit, <Screen_BuildEsmereldaTower, <Screen_BuildWall

tbl_LevelCharBlockHi    byte >Screen_BuildKnights, >Screen_BuildPits, >Screen_BuildRowOfBells
                        byte >Screen_BuildRopePit, >Screen_BuildEsmereldaTower, >Screen_BuildWall

The screen builder will get the tbl_levelType for the current level and setup the zero page pointers to call the correct screen drawing routine, very nice:

        lda tbl_LevelCharBlockLo,Y
        sta zpLow
        lda tbl_LevelCharBlockHi,Y
        sta zpHigh
        jmp (zpLow)

For most levels it builds the Wall first and has char block drawing routines to replace certain parts of the screen e.g. the pits (some empty, some with knights), and these blocks are defined in memory and once the rows (ldy), height (ldx) and char pointers have been setup, the block drawing routine is called.

For example to draw an empty pit (note wall is already drawn):

        ldx #4
        ldy #3
        lda #20
        stx charRows
        sty charWidth
        sta colourOffset
        ldx #<tbl_EmptyPitChars
        ldy #>tbl_EmptyPitChars
        lda #<scn_TemporaryScreen+$0199
        sta zpScnPtrLo
        lda #>scn_TemporaryScreen+$0199
        sta zpScnPtrHi
        jsr Screen_DrawCharBlocks

It uses a temporary off screen memory buffer to build the screen, as this eliminates any flicker and also used when scrolling the intro. It actually builds the screen as it goes, so the intro builds level 15, shows it, builds the next level (9), scrolls it and so on:

;-------------------------------------------------------------------------------
; SELECT SCREENS TO SHOW DURING INTRO SEQUENCE
;-------------------------------------------------------------------------------
Screen_IntroScrollSelect
ldx #9
stx currentLevel
jsr Screen_BuildScreen
jsr Screen_IntroScroll
ldx #8
stx currentLevel
jsr Screen_BuildScreen
jsr Screen_IntroScroll
ldx #0
stx currentLevel
jsr Screen_BuildScreen
jsr Screen_IntroScroll
rts

This screenshot below from the debugger shows the off-screen buffer of the next screen to be scrolled, ignore the colours as these can’t be buffered:

Off-screen buffer at $8400

Scrolling

As I mentioned earlier the scrolling is jerky and that’s because it scrolls at 1 character at a time, then feeds the next column from the off-screen buffer, and not to be confused with double buffer scrolling, we get onto that later.

Sprites

Nothing exciting to mention here, Quasi is sprite 0 and the other sprites are used for the obstacles like arrows, fireballs and even the rope. The animated rope is made up of two sprites across 16 frames (8 swinging left, 8 right) so a large chunk, but done quite well I think. Only on the Esmeralda level are all 8 sprites used.

Quasi is a little blocky and I’ve seen in others games where they have used two sprites to create a hi-res outline and a multi-colour body, so would like to attempt this in another chapter.

Collision

The collision detection makes use of the hardware collision registers, so if a sprite-sprite collision occurs it checks the relevant bits to see if Quasi was involved and generally that means he dies.

For sprite-character collision it varies between level types, but checks if a collision has occurred and because the character objects are static can quite easily cross-reference the x position of Quasi to see what he has collided with.

Enemies

The enemies are defined by the obstacle type for each level, and at the start of each level loads the relevant sprites into place. The ‘Enemy_Select’ routine decides which type of objects need to be updated per frame, like moving an arrow, and made quite flexible by using the tbl_LevelObstacleType table, so different levels can reuse a type of enemy/obstacle.

A simple IRQ runs to update the enemies:

;-------------------------------------------------------------------------------
; IRQ TO UPDATE ENEMY AND OBSTACLES DURING GAME
;-------------------------------------------------------------------------------
IRQ_EnemyUpdate
        jsr Enemy_Select
        jsr Level_DisplayHeart
        jsr Enemy_MoveKnight
        jsr Level_UpdateRope
        jmp krnINTERRUPT

That’s a very high level view of the game and shows the dynamic drawing mechanism of displaying the levels which I think was very clever back in 1983, it could have quite easily handled hundreds of different levels using this technique, but I imagine other games must have used this approach.

Now the basics are out of the way, let’s move on to making some changes.

Leave a Reply

Your email address will not be published. Required fields are marked *