Sega Megadrive – 6: Scary Monsters and Nice Sprites

Sprites! Wonderful little things. I have fond memories of getting one on screen on the Commodore 64 back when I was wearing size 9’s, after typing in several hundred lines of code from some tutorial in a microcomputer magazine.

Armed with a few VDP skills – loading patterns, organising artwork into VRAM, setting tile IDs – basic sprite work seems to be pretty easy. Sprite tiles are uploaded to VRAM in the same way as plane A/B patterns, and are again referred to using tile IDs. The structure to describe a sprite’s attributes is a little more complex, as are some of the rules for displaying and sorting sprites, but I’m confident I can wrap the basics up in just a few paragraphs. Sprites can be displayed at any X/Y screen coordinates; they’re not tied to cells like the other planes. They have their own plane, too.

Sprites – The basics

Sprites use the same pattern data as the A/B planes, and can be made up of more than one pattern, too. The VDP supports a grid of up to 4×4 patterns, and will manage their positioning for us to fit together, so it’s possible to have a sprite of up to 32×32 pixels in size. It’s feasible to support larger sizes, but they would have to be positioned manually. All patterns in the sprite must share one palette.

First we need a sprite for testing. I’ve found some great free sprites after some hunting around, and settled on this little monster – he’s a 24×24, 16 colour beast under the Creative Commons license (source in References section). After some tidying up the PNG file in The Gimp, I converted it to indexed mode, 16 colour (optimised palette):

After importing it into Bmp2Tile, set Sprite Output Mode, press the * key to select the whole image, and then export both the tiles and palette. The palette looks garbled in the preview, but it does export correctly; either I don’t understand how to make it show properly, or Windows 7 doesn’t correctly handle DJGPP/Allegro applications.

Loading sprite artwork

Sprites use the same VRAM pattern memory as the A/B planes, so for moving them to VRAM I’ll simply rename the LoadFont subroutine to something more generic like LoadTiles. Now that we’re dealing with more than one art asset, it might be worth figuring out how to organise it all into VRAM. We can modify the preprocessor tricks to do this all for us, and move these addresses to a separate file so they can be layed out neatly. Here’s a quick example of how I’ve arranged the Pixel Font and a few tiles for various sprites, of various sizes:

assetmap.asm:

; ************************************
; Art asset VRAM mapping
; ************************************
PixelFontVRAM:  equ 0x0000
Sprite1VRAM:    equ PixelFontVRAM+PixelFontSizeB
Sprite2VRAM:    equ Sprite1VRAM+Sprite1SizeB
Sprite3VRAM:    equ Sprite2VRAM+Sprite2SizeB

; ************************************
; Include all art assets
; ************************************
    include 'assets\fonts\pixelfont.asm'
    include 'assets\sprites\sprite1.asm'
    include 'assets\sprites\sprite2.asm'
    include 'assets\sprites\sprite3.asm'

; ************************************
; Include all palettes
; ************************************
    include 'assets\palettes\paletteset1.asm'

pixelfont.asm:

PixelFont: ; Font start address

    dc.l    $01111100
    dc.l    $11000110
    dc.l    $10111010
    dc.l    $10000010
    dc.l    $10111010
    dc.l    $10101010
    dc.l    $11101110
    dc.l    $00000000

; ...etc

PixelFontEnd                                 ; Font end address
PixelFontSizeB: equ (PixelFontEnd-PixelFont) ; Font size in bytes
PixelFontSizeW: equ (PixelFontSizeB/2)       ; Font size in words
PixelFontSizeL: equ (PixelFontSizeB/4)       ; Font size in longs
PixelFontSizeT: equ (PixelFontSizeB/32)      ; Font size in tiles
PixelFontTileID: equ (PixelFontVRAM/32)      ; ID of first tile

sprite1.asm:

Sprite1:

    dc.l    $11111111    ;  Tile: 1
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $11111111

    dc.l    $11111111    ;  Tile: 2
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $10000001
    dc.l    $11111111

Sprite1End                                 ; Sprite end address
Sprite1SizeB: equ (Sprite1End-Sprite1)     ; Sprite size in bytes
Sprite1SizeW: equ (Sprite1SizeB/2)         ; Sprite size in words
Sprite1SizeL: equ (Sprite1SizeB/4)         ; Sprite size in longs
Sprite1SizeT: equ (Sprite1SizeB/32)         ; Sprite size in tiles
Sprite1TileID: equ (Sprite1VRAM/32)         ; ID of first tile

I’ve also moved the palettes to their own file for consistency. Now asset files can be added and removed at will, and it should be simple to keep them organised correctly in VRAM. It’s not an all-round solution, if the game gets big we have neither the space nor the need to fit everything in VRAM at once, so I’ll need to come up with a more dynamic solution if and when the time comes. For now, this is perfect for my needs.

Drawing sprites

Sprites are drawn by filling in details in a sprite attribute table. The VDP’s memory contains an area specifically for this attribute data – at 0xE000 – set in register 5 during intialisation. Each entry is 8 bytes long, and looks a little something like this:

000000YY YYYYYYYY 0000HHVV 0NNNNNNN DPPFFTTT TTTTTTTT 000000XX XXXXXXXX

where:

Y   = Y coord (from -128 to screen height + 128)
H/V = Sprite grid dimensions, in tiles
N   = Index of next sprite attribute (a linked list next ptr)
D   = Draw priority
P   = Palette index
F   = Flip bits (vert. and horiz.)
T   = Index of first tile in sprite
X   = X coord (from -128 to screen width + 128)

The sprite window’s coordinate system has a 128 pixel border, assumingly to allow sprites to be partially or fully hidden off screen, so for the sprite to be visible in the top-left corner coords of 128,128 must be set. The X and Y coordinates (of the top-left corner of the sprite) are defined in 10 bits each, although I’m unsure as to why they are at opposite ends of the structure. The 4 bits for the grid dimensions define how large the sprite will be, in tiles. It accepts all combinations from 1×1 to 4×4, and the positioning of subsequent tiles will be handled by the VDP for us automatically, as well as flipping for the entire sprite as a whole. I’m unsure what range the draw priority accepts, I’ve left it as zero for now since it’s out of the scope of this article. There are two bits for the H and V flipping (I won’t be using those yet), and the index of the first pattern tile in the sprite.

This leaves us with the N – the index of the next sprite attribute struct. The VDP will draw subsequent sprites by jumping through these next pointers, until it hits 0. This is also used for the drawing order – strangely, the VDP draws front-to-back, unlike most graphics APIs I’ve worked with on other platforms where the drawing is done back-to-front, which seems to make logical sense to me. So with this in mind, expect the first sprite in the linked list to be drawn on top, and the second to be drawn underneath it.

Here’s an example:

SpriteDesc1:
dc.w 0x0080        ; Y coord (+ 128)
dc.b %00001111     ; Width (bits 0-1) and height (bits 2-3)
dc.b 0x00          ; Index of next sprite (linked list)
dc.b 0x00          ; H/V flipping (bits 3/4), palette index (bits 5-6), priority (bit 7)
dc.b Sprite1TileID ; Index of first tile
dc.w 0x0080        ; X coord (+ 128)

Prefixing a value with % allows it to be specified in raw binary, useful for defining the width/height bits. Here I’ve defined a sprite made of a 4×4 grid of tiles. The struct then needs moving to the VDP, using a quick subroutine:

LoadSpriteTables:
   ; a0 - Sprite data address
   ; d0 - Number of sprites
   move.l    #vdp_write_sprite_table, vdp_control

   subq.b    #0x1, d0                ; 2 sprites attributes
   @AttrCopy:
   move.l    (a0)+, vdp_data
   move.l    (a0)+, vdp_data
   dbra    d0, @AttrCopy

   rts

…which is simply used with:

lea     SpriteDesc1, a0     ; Sprite table data
move.w  #0x1, d0            ; 1 sprite
jsr     LoadSpriteTables

Providing the 16 tiles have been loaded into VRAM, as well as the correct palette, we should have a big bad monster:

Moving sprites

Since sprites can be positioned at any X/Y coord, part of the point of them is to be able to move them about at runtime, so we’ll need subroutines to modify the X and Y coords. Not too difficult, just write to the correct addresses in the sprite attribute table:

SetSpritePosX:
   ; Set sprite X position
   ; d0 (b) - Sprite ID
   ; d1 (w) - X coord
   clr.l    d3                          ; Clear d3
   move.b    d0, d3                     ; Move sprite ID to d3

   mulu.w    #0x8, d3                   ; Sprite array offset
   add.b    #0x6, d3                    ; X coord offset
   swap    d3                           ; Move to upper word
   add.l    #vdp_write_sprite_table, d3 ; Add to sprite attr table

   move.l    d3, vdp_control            ; Set dest address
   move.w    d1, vdp_data               ; Move X pos to data port

   rts

SetSpritePosY:
   ; Set sprite Y position
   ; d0 (b) - Sprite ID
   ; d1 (w) - Y coord
   clr.l    d3                          ; Clear d3
   move.b    d0, d3                     ; Move sprite ID to d3

   mulu.w    #0x8, d3                   ; Sprite array offset
   swap    d3                           ; Move to upper word
   add.l    #vdp_write_sprite_table, d3 ; Add to sprite attr table

   move.l    d3, vdp_control            ; Set dest address
   move.w    d1, vdp_data               ; Move Y pos to data port

   rts

Used with:

move.w  #0x0,  d0      ; Sprite ID
move.w  #0xB0, d1      ; X coord
jsr     SetSpritePosX  ; Set X pos
move.w  #0xB0, d1      ; Y coord
jsr     SetSpritePosY  ; Set Y pos

Just for good measure, I’ve added another monster friend to demonstrate drawing two sprites, making sure to set the next sprite ID in the linked list, and terminating the second sprite with a 0:

SpriteDescs:
dc.w 0x0000        ; Y coord (+ 128)
dc.b %00001111     ; Width (bits 0-1) and height (bits 2-3) in tiles
dc.b 0x01          ; Index of next sprite (linked list)
dc.b 0x00          ; H/V flipping (bits 3/4), palette index (bits 5-6), priority (bit 7)
dc.b Sprite1TileID ; Index of first tile
dc.w 0x0000        ; X coord (+ 128)

dc.w 0x0000        ; Y coord (+ 128)
dc.b %00001111     ; Width (bits 0-1) and height (bits 2-3) in tiles
dc.b 0x00          ; Index of next sprite (linked list)
dc.b 0x20          ; H/V flipping (bits 3/4), palette index (bits 5-6), priority (bit 7)
dc.b Sprite2TileID ; Index of first tile
dc.w 0x0000        ; X coord (+ 128)

Here’s the finished result:

Check those badasses out.

There’s plenty more I could expand on – draw priorities and sorting, limitations of sprite drawing, subroutines to add and remove sprites at runtime – all in good time.

Matt.

Source code

Assemble with:

asm68k.exe /p spritetest.asm,spritetest.bin

References

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s