Welcome

Thursday, January 30, 2020

Sample Sprite Code


Sample Sprite Code


As I stated earlier on the interrupt and simple sprites post, I prefer to draw all my sprites in simple games as soon as the raster interrupt has triggered. This avoids any flicker and it also means that I can use a single routine to draw all the sprites, which makes the code simpler to debug as all the screen updates are in one place.

The first step is to setup a table in memory to hold all the values that will be needed to draw the sprites, I usually prefer to store these in zero page because of access speed though its not necessary.

The first thing to do is define the table:

spriteXLow:
    .fill 8,0
spriteXHigh:
    .fill 8,0
spriteY:
    .fill 8,0
spritePointer:
   .fill 8,0
spriteColour:
    .fill 8,0
spriteStatus:
    .fill 8,0

This code uses the Kick Assembler directive .fill which simply creates 8 bytes of zero's for each label. This can be replaced with .byte 0,0,0,0,0,0,0,0 or any similar commands supported by other assemblers.

One thing to note here is that the high byte for the X co-ordinate is stored as a byte not, as discussed previously, a shared byte. This is because whilst setting these variables it is easier and quicker to set the X coordinate as a 16bit number and let the sprite draw code take care of the VIC limitations.

Another advantage of using this table is the fact that it can be used to store the working information about every sprite, you do not need to keep separate variables for each sprite, they just live here.

I also use a a spriteStatus byte for each sprite which can be used to hold various in-game attributes for the sprite. In a later post I'll look at using this for setting individual sprite into double width, double height and mixing multi colour and hi res sprites, butfor now I'll keep the routine simple.

So lets look at an example:

Sprite 0 is 280 pixels across the screen and 100 pixels down the screen, it should be coloured red and use sprite 64.

To set the sprites attributes we store the relative attributes into the table, sprite 0 is the first column of the table so I'll load the values into the label + 0, the reason I use + 0 is to demonstrate that this is an entry into a table in column zero, an aid memoir so to speak (idea blatantly stolen from Shallan50k)

One reason I always use hex when writing assembler is that it makes 16bit numbers easier to split into bytes, which will help with the X co-ordinate.

The X co-ordinate is 280 which is hex is $0118 so the low byte is $18 and the hight byte is $01

lda #$18
sta spriteXLow + 0

lda #$01
sta spriteXHigh + 0

Now to set the Y, this is easier as there is only one byte. 100 in hex is $64

lda #$64
sta spriteY + 0

spritePointer is the sprite number which I covered in the previous post, this it just the sprite number.

lda #$40
sta spritePointer + 0

Where possible it is better to use a descriptive label when setting a value, the Kick Assembler has builtin constants for colours so I'll use one here.

lda #RED
sta spriteColour + 0

For the time being I will leave the status flag alone, I will expand on what can be done with status flags in a later post.

The values for the sprites can be updated anywhere in the game code.

The code to produce the sprites consist of two routines, the first one is only called once and set-up some VIC settings which don't, for now, need to be changed every time sprites are drawn.


.const spriteEnable      = $d015
 .const spriteMulticolour = $d01c
.const spriteDoubleWide  = $d01d
.const spriteDoubleHeight = $d017

initSprites:{




// enable sprites

lda #$ff
sta spriteEnable

// set all sprites to multi colour

lda #$00
sta spriteMulticolour

// set sprites to single height an width

lda #$00
sta spriteDoubleWide
sta spriteDoubleHeight

rts


}

The initSprite subroutine should be called at the beginning of the program and simply sets all sprite to hi res, normal size and enables them all. It's always a good idea to define all constants and use the labels in a program, it makes debugging so much easier. A typo in a label will be flagged by the assembler whereas a mistype in an address in hex will usually result in a bug.

Once the sprite information is loaded into the sprite table the drawSprites routine can be called once per raster frame and all the hardware sprites are drawn to the screen.

highBit:
.byte 0

bitSet:

.byte $01, $02, $04, $08, $10, $20, $40, $80

.const sprite0X = $d000

.const spriteColour0 = $d027
// The value of screenRam may change depending on which
// blocks of memory have been switched out
// refer to the memory bank section
.const screenRam = $c000
.const spriteBaseAddress = screenRam + $3f8


drawSprites:{




// clear the highBit

// This is a variable to consolidate all of the X high bits

lda #$00

sta highBit

// loop through all of the sprites

// using the X register an index

ldx #$07

nextSprite:


// As esplained previously the X and Y registers in the VIC chip

// are interlaced so for every sprite you need to double the
// offset to the VIC register
// to do that I'll use the Y register and double it

txa
asl
tay

// set X lower byte
// using the x register to index into the sprite table
// and the y index to point to the VIC
lda spriteXLow,x
sta sprite0X,y


// now set the pesky high hit

// rather than keep changing the VIC register for every sprite
// ive created a variable call highBit which gets updated every sprite
// but only written to the VIC at the end

lda spriteXHigh,x

beq dontSetBit

// The bitSet will be explained in detail below


lda bitSet,x

ora highBit
sta highBit

dontSetBit:



// set sprite colour

lda zeroPage.spriteColour,x
sta spriteColour0,x

// set Y co-ordinate : dont get the Y and the Y co-ordinate confused
// the Y register has nothing to do with the Y co-ordinate
lda zeroPage.spriteY,x
sta sprite0X + 1,y


// set this sprite colour

lda spriteColour, x
sta spriteColour0, x

// set this sprite colour

lda spritePointer, x
sta spriteBaseAddress, x


// and loop back to the top for hw next sprite

dex
bpl nextSprite

// now we can set the highBit for the X sprite as its a single byte for all sprites

// the A register will all ready hold the correct value
lda highBit
    sta spriteXMSB


}



Once the drawSprite routine has been called the values in the sprite table can be updated for the next frame without affecting the current on-screen sprites.

*The bitSet table:

The bitSet table contains 8 values, each value corresponds to a single bit, as below.

000001
000010
000100
001000
010000
100000
%01000000
%10000000

By indexing into this table it is possible to select a single bit and apply that to a register or another variable.

For example, to set the high bit for sprite 5 we need to set bit 5 in the MBS register, to do that we take the 6th element in the bitSet table (6th because we start counting at 0). The 6th element in the table is 100000 which when ORed with the MSB register turns on the bit for sprite 5 and only for sprite 5.


No comments:

Post a Comment

Setting up Mega65 Connect for LAN

The latest Mega65 Core (0.96) now supports remote access from the  M65Connect using Jtag and now ethernet. This guide will explain how to se...