Pobtastic / Rampage / Monster Sprite

Created Mon, 04 Mar 2024 16:54:27 +0000 Modified Wed, 31 Jul 2024 14:47:47 +0000

What Does It Do?

This isn’t all that complex, or probably even all that unique - there aren’t that many multiplayer games for the ZX Spectrum (or maybe just that I’ve not disassembled many!) Still, it gave me a bit of an “oh yeah, of course” moment when I saw how it’s been done.

Rampage uses three large main characters; George, Lizzie and Ralph. The sprites are organised with a “Sprite Information” table and each sprite has an ID. So, when a monster sprite is needed, the game calls on this routine:

Subroutine: Draw Monster Sprite

; Draw Monster Sprite
;
; ------------------------
; | Input                |
; ------------------------
; |A |Sprite ID          |
; |BC|Screen co-ordinates|
; ------------------------
;
; This is a wrapper around PrintSprite which simply takes the given sprite ID
;       and applies a "modifier" if the monster is either Lizzy or Ralph. This
;       then alters the sprite ID to be the correct value for the active
;       monster; bit 6 for Lizzy, bit 7 for Ralph.
@label=DrawMonsterSprite
c$D9BB LD L,A        ; #REGl=#REGa.
 $D9BC LD A,($D251)  ; #REGa=*MonsterSpriteModifier.
 $D9BF OR L          ; Set the bits from #REGl.
 $D9C0 JP $D6C9      ; Jump to PrintSprite.

How Does It Work?

Keep in mind hexadecimal is used here (Skoolkit syntax is e.g. $4000 which is equivalent to address 16384). Also, the bits are referencd from bit 7 to bit 0 (left to right).

Each monster is processed in a loop and in this loop the following config is set:

g $D251 Active Monster Sprite Modifier
@ $D251 label=MonsterSpriteModifier
D $D251 When monster frames are drawn, this is used with an OR to set a bit
.       which changes the sprite to the appropriate character.
.       See DrawMonsterSprite.
. -----------------------------
. | Byte | Bits     | Monster |
. -----------------------------
. | $00  | 00000000 | George  |
. | $40  | 01000000 | Lizzy   |
. | $80  | 10000000 | Ralph   |
. -----------------------------
B $D251,$01

The modifier, as it says above, is used with a logical OR command to alter the given sprite ID, so it reflects the same frame but for the other characters.

The logical OR, at its very simplest, works like this:

Input A Input B OR Result
0 0 0
0 1 1
1 0 1
1 1 1

So if either input bit is set, then the resulting bit is also set.

So, if the frame sprite ID is $01 then the following applies:

Hexadecimal Decimal Character Sprite
$01 1 George George Sprite ID 01
$41 65 Lizzie Lizzie Sprite ID 41
$81 129 Ralph Ralph Sprite ID 81

I’ve included the decimal notation here for reference but obviously it’s much easier to look at in hexadecimal.

  • For George, the sprite ID is unchanged as his modifier has no set bits.
  • For Lizzie, bit 6 is set - so the sprite ID becomes $41.
  • And for Ralph, bit 7 is set - so the sprite ID becomes $81.

Flow Diagram

block-beta
  columns 9
  George:9
  GeorgeBitsLabel["Bits"]
  block:GeorgeModifierBits:8
    gm7["Bit 7"]
    gm6["Bit 6"]
    gm5["Bit 5"]
    gm4["Bit 4"]
    gm3["Bit 3"]
    gm2["Bit 2"]
    gm1["Bit 1"]
    gm0["Bit 0"]
  end
  GeorgeModifierLabel["Modifier"]
  block:GeorgeModifiers:8
    gmod7["0"]
    gmod6["0"]
    gmod5["0"]
    gmod4["0"]
    gmod3["0"]
    gmod2["0"]
    gmod1["0"]
    gmod0["0"]
  end
  space:9
  GeorgeOutputLabel["Output"]
  block:GeorgeBits:8
    gbit7["0"]
    gbit6["0"]
    gbit5["0"]
    gbit4["0"]
    gbit3["0"]
    gbit2["0"]
    gbit1["0"]
    gbit0["0"]
  end
  gmod7--"Logical OR"-->gbit7
  gmod6--"Logical OR"-->gbit6
  gmod5--"Logical OR"-->gbit5
  gmod4--"Logical OR"-->gbit4
  gmod3--"Logical OR"-->gbit3
  gmod2--"Logical OR"-->gbit2
  gmod1--"Logical OR"-->gbit1
  gmod0--"Logical OR"-->gbit0
  space:9
  Lizzie:9
  LizzieBitsLabel["Bits"]
  block:LizzieModifierBits:8
    lm7["Bit 7"]
    lm6["Bit 6"]
    lm5["Bit 5"]
    lm4["Bit 4"]
    lm3["Bit 3"]
    lm2["Bit 2"]
    lm1["Bit 1"]
    lm0["Bit 0"]
  end
  LizzieModifierLabel["Modifier"]
  block:LizzieModifiers:8
    lmod7["0"]
    lmod6["1"]
    lmod5["0"]
    lmod4["0"]
    lmod3["0"]
    lmod2["0"]
    lmod1["0"]
    lmod0["0"]
  end
  space:9
  LizzieOutputLabel["Output"]
  block:LizzieBits:8
    lbit7["0"]
    lbit6["1"]
    lbit5["0"]
    lbit4["0"]
    lbit3["0"]
    lbit2["0"]
    lbit1["0"]
    lbit0["0"]
  end
  lmod7--"Logical OR"-->lbit7
  lmod6--"Logical OR"-->lbit6
  lmod5--"Logical OR"-->lbit5
  lmod4--"Logical OR"-->lbit4
  lmod3--"Logical OR"-->lbit3
  lmod2--"Logical OR"-->lbit2
  lmod1--"Logical OR"-->lbit1
  lmod0--"Logical OR"-->lbit0
  space:9
  Ralph:9
  RalphBitsLabel["Bits"]
  block:RalphModifierBits:8
    rm7["Bit 7"]
    rm6["Bit 6"]
    rm5["Bit 5"]
    rm4["Bit 4"]
    rm3["Bit 3"]
    rm2["Bit 2"]
    rm1["Bit 1"]
    rm0["Bit 0"]
  end
  RalphModifierLabel["Modifier"]
  block:RalphModifiers:8
    rmod7["1"]
    rmod6["0"]
    rmod5["0"]
    rmod4["0"]
    rmod3["0"]
    rmod2["0"]
    rmod1["0"]
    rmod0["0"]
  end
  space:9
  RalphOutputLabel["Output"]
  block:RalphBits:8
    rbit7["1"]
    rbit6["0"]
    rbit5["0"]
    rbit4["0"]
    rbit3["0"]
    rbit2["0"]
    rbit1["0"]
    rbit0["0"]
  end
  rmod7--"Logical OR"-->rbit7
  rmod6--"Logical OR"-->rbit6
  rmod5--"Logical OR"-->rbit5
  rmod4--"Logical OR"-->rbit4
  rmod3--"Logical OR"-->rbit3
  rmod2--"Logical OR"-->rbit2
  rmod1--"Logical OR"-->rbit1
  rmod0--"Logical OR"-->rbit0

Rampage Disassembly