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.


Tuesday, January 28, 2020

Simple Sprites

Simple Sprites


The hardware sprites available on the C64 are quite limited but if used well can be quite effective. Sprites are controlled but the VIC chip and by setting different registers in the VIC chip the sprites can be displayed and manipulated.

So what is a sprite? Well in simple terms its a graphical image which is 24 x 22 pixels in hi res mode and 12 x 22 pixels in multi colour mode. And yes 24 x 22 is high res, its all relative.

In hi res a sprite can be 2 colours and by two colours I mean one colour and transparent. In multi colour mode sprites have three colours and transparent but two of the colours are the same for all sprites and one can be unique to each sprite. Sounds limiting, yep it is.

Sprites can also be double width and double height but that doesn't mean there are more pixels to work with, the pixels are just double the width or height making the sprite look more chunky.

Due to a limitation in the amount of memory the VIC chip is able to address the location of all VIC data is set relative to VIC page, this is covered in the banking kernal and BASIC section. For the purpose of this section assume that kernal and BASIC are switched out and that the screen has been remapped to $C000. This is explained fully in the kernal section.

To control a sprite we must give the VIC certain information, this is done by setting values into the memory that can be read by the VIC chip.

The important values we need are:
  • X co-ordinate of the sprite
    • How far across the screen should the sprite be drawn, this point is the top left of the sprite graphic including any transparent pixels
  • Y co-ordinate of the sprite
    • How far down the screen should the sprite be drawn, this point is the top left of the sprite graphic including any transparent pixels
  • The sprites individual colour
    • Each sprite has one colour that can be set independently of all other sprites
  • Is the sprite double width
    • Simple toggle on or off
  • Is the sprite double heigh
    • Simple toggle on or off
  • Where is the graphic for the sprite
    • I'll go into greater detail on this below
  • Is the sprite enabled
    • Each sprite can be turned on or off individually


This doesn't, at first glance, look too complicated but its 1982 and the limitations of the chips and addressable memory are very restrictive. Back in the early days saving as much memory as possible wasn't just a good idea it was an absolute requirement.


So lets look at the X co-ordinate for the sprite. There are 320 visible pixels across the screen of but there is also the screen border. It is possible to place a sprite into the border but the only part that will be displayed is parts which over lap onto the viewable screen. The border is 24 pixels wide so the top left of the viewable screen starts at 24 not 0. There are then 320 pixels across the viewable screen so the last addressable pixel in the viewable area is 343. This is a problem. Its a problem because the memory location used to store the X co-ordinate is a single byte, values of 0 - 255, so we need to a second byte to hold hold the high value of X. As I mentioned above memory is was at a premium and as the max value X can't attain is 376 the developer realised that they didn't need a full byte they only needed on extra bit.


Theory bit to be completed..... Hayes needs the sample code





Negative Numbers

Negative numbers


Negative numbers are surprisingly complicated when working in assembler, firstly there is no minus sign inside the registers just 0s and 1s and when is a negative number actually a negative number? 

Well it comes down to context so lets look at some examples.

An 8bit register or memory location can hold numbers from 0 - 255 ($00 - $ff) and these numbers are all positive.

But let’s look at an example:

lda #0
sec
sbc #1

We set a = 0 and subtract 1 from it from it, so a now equals -1 but if you look at the value of a in a debugger you will see its set to $ff or 255! But also the negative is set, so does that mean 0 - 1 = -255?

Well no, when working with signed numbers computer use a system called 2's complement. With 2 complement we can represent numbers from -128 to +127. Bit 7 becomes a sign bit so numbers starting with a 1 are positive and numbers starting with a 0 are positive.

I don't want to go too deeply into this for the time being as its not really that relevant to games development on the platform, but if I get any feed back to suggest that this is a topic worth a deep dive I may reconsider. There is plenty of information on the internet on this subject and I really don't want to repeat what many other people have done already.



Branching and Comparisons

Branching and comparison operations


Branching is one of those topics which seems to confuse most people when it comes to very low level programming, which is probably because it is based on the condition of the status register rather than what you are wanting to compare.

The status register is an 8bit register and sets bits on or off according the result of the running commands.

For branch comparison we need only look at 4 of these:

C - Carry flag set if the result of an arithmetic operation exceeds 8bit
Z - Zero flag set if the result of an operation  is zero
V - Over flow set if an overflow condition is set
N - Negative set if a value result in a negative value, I'll go more into how numbers can be negative later.

The flag registers are set when the CPU runs an instruction and, depending on the instruction and the result, the appropriate flag or flags are set. I'm not going to go into which instructions set which flag, many other people have already produced tables of these operations and the one I use on a daily basis is http://www.obelisk.me.uk/6502/reference.html#SBC

Branch instructions test the status of these flags and act accordingly and can be thought of in pairs. The first instruct will test if the flag is set, the other will check if its clear.


  • Carry flag
    • BCS - Branch if carry set
    • BCC - Branch if carry clear
  • Zero flag
    • BEQ - Branch if zero is set
    • BNE - Branch if zero is clear
  • Negative flag
    • BMI - Branch if minus is set 
    • BPL - Branch if minus clear
  • Overflow flag
    • BVS - Branch if overflow is set
    • BVC - Branch if overflow is clear
     

The most common use of branching instructions is after a comparison operation, your basic IF THEN type of construct.

for example

IF a = 5 THEN GOTO LABLE

There are comparison functions for the A, X and Y registers but for these examples I'll stick to using A Conf consistency.

lda #5 // set the value in A
cmp #5 // compare a to 5
beq someLable // branch

This looks pretty straight forward, we load a value into the A register, compare it to 5 and if they are equal we branch.

However, that's not actually what is being done here.

5 is loaded into the A register.

The compare instruction then subtracts its value from the accumulate, in this example 5 - 5  = 0, so the compare instruct sets to zero bit in the status register.

The branch instruct then looks at the status register, checks the status is the zero flag and then if the zero flag is set it performs the branch.

At no point does the branch instruction have any knowledge of what the value in A is, its just looking at the zero flag.

In the above example we can test if A is not equal to 5 by replacing the beq instruction with the bne.

Using the code above the following flags are set


  • IF A >= value 
    • Carry flag is set
  • IF A < value
    • Negative flag is set
  • If A = value
    • Zero flag is set

You will see that there is no single condition for <= but this can still tested it just need to be done in two stages by testing the negative flag then the  zero flag.

lda #5
cmp #10.     
bmi lessThanOrEqual.                       // if the negative flag set its <
beq lessThanOrEqual                        // it the zero flag is set it =

Its not only the CMP instructions that can set the flags though, when a value is loaded into a register the zero and negative flags will be set accordingly.

lda #0
beq label

The above will branch because the lda #0 instruction will set the zero flag, again its because the instruction sets the flag that we can branch, not because of the content of the A register. I know I'm really pushing the point here but it’s important to get into that mindset when working with assembler code.

Other instructions that will set flag are the INX/DEX instructions which will set the negative and zero flags when appropriate, this is useful if you have a loop that counts down for example you don't need to CPX:



ldx #10
loop:
// some code
dex
bne loop

Here to loop counter X is decreased and the code loops until X is zero, we don't need to perform a CPX #0 because the zero flag is set by the DEX instruction. This is why its important to think in terms of flags.







Monday, January 27, 2020

Interrupts Explained

Interrupts  Explained


Why do you need to worry about interrupts when writing a simple C64 game? Interrupts are  only needed for for fancy raster bars and demos right?

Well no, raster interrupts perform two very important jobs. 

The first is they give us strict timing control, without some kind of timing interrupt, games would run as fast as they can all the time. Which would mean that they would constantly run faster and slower depending on what's on the screen and how much processing the game uses.  

The second reason is we need to make sure all screen updates are as smooth as possible, with CRT screen the picture is drawn from the top left to the bottom right. Its quite like a type writer when the scan line gets all the way to the right, it drops one line and returns to the left before resuming the screen update. The problem comes when the program draws something to the screen if the CRT has passed the point on the screen where the graphic is to appear. in this situation the graphics wont be drawn until the next screen refresh (50 times/second PAL 60 times NTSC) This doesn't sound important but this is why graphics flicker on screen. To stop this flicker it is important to draw all the graphics before the raster reaches them, the easiest way to do this is to start drawing the next frame as soon as the raster reaches the bottom of the viewable screen, this is where the interrupts come into play; we trigger the interrupt at the end of the last line of the screen and wait at that point.

Raster interrupts are generated by the VIC chip when the CRT gets to a certain line down the screen.

The best way to think of interrupts, for someone who is used to modern languages, is as an event. When an event is triggered the code associated with that event is run and that’s exactly how an interrupt works but it’s the hardware that causes the event not a user.

There are two sets to getting interrupts up and working, the initial config code and the interrupt (or event) handler code.

Let’s take a look at the setup code:

interrupts: {

            init: {

                sei                                                      // turn off maskable interupts

                lda #$7f                                             // kill CIA interupts
                sta $dc0d
                sta $dd0d

                lda $dc0d                                          // read interupts to clear them
                lda $dd0d

                lda #$01                                            // get the vic II to do raster interupts
                sta $d01a


                lda #$1b                                           // set the inturpt line
                sta $d011

                lda #$EA
                sta $d012

                lda #<rasterHandler                          //this is how we set up
                sta $fffe                                           //the address of our interrupt code
                lda #>rasterHandler
                sta $ffff
                
                cli                                                       // turn on the interupts

                rts
            }

Ok so most of this code is boiler plate code and doesn’t really need to be understood in detail;

The sei command disables all interrupts, if we get an interrupt whilst setting up the interrupts it will probably crash the computer so temporally turn them off.

The CIA interrupts are not used usually during a game, they are used to monitor the keyboard inputs and let the kernal buffer them which in-game we don’t want so we turn them off and clear any pending interrupts.

The two green lines tell the VIC chip that you want it to generate interrupts at a given point, these lines just enable that function.

The yellow lines are the first important ones and the ones that may change program to program. They tell the VIC chip which line to generate the interrupt on, this is important for timing. Usually you want the interrupt to fire after all the screen updates are complete but leave as much time as possible before the next screen refresh. I usually set mine to the bottom of the visible screen area ie at the start of the bottom border.

Next the blue code tells the computer what address to call when the interrupt is triggered, think of it as a jsr rasterHandler. When the interrupt fires this subroutine is called.

Now the interrupt is set up you need to give it some code to run.

            rasterHandler: {                      

                        dec rasterCounter
                        
                        lsr $d019                                             // acknowledge the interupt 
                        rti

             }

This is about the simplest code you will ever use, all it does is decrease memory location called rasterCounter. The variable rasterCounter should be defined in zero page see later.

We also have to acknowledge the interrupt, that is to tell the VIC that we saw the interrupt and to reset it for the next cycle. If the interrupt is not acknowledged then the VIC chip will constantly spam the interrupt which means that only the code in the interrupt will run.


Ok so now you have an interrupt that every 1/50th of a second will decrease a variable, not a lot of use right? Well …..


*=2 virtual
rasterCounter:
            .byte 0
*=$1000
Main: {
            Jsr interrupts.init

!gameLoop:

            Lda #1
            Sta rasterCounter
!waitForInterrupt:
            Lda rasterCount
            Bne !waitForInterupt-

// game code here

            Jmp !gameLoop-

}

// interrupt code from above goes here.

Right so when you run this program logically the section is an infinite loop and should never break out, you set rasterCounter to 1 then keep checking to see if its zero which it never will be right? Well every 50th of a second the interrupt event happens and decrease the counter so after a 50th of a second the rasterCounter isn’t 1 any more and the loop ends.

If you want to make it 25th of a second set the initial raster count to 2 etc. this way. You can create a pause as long as you want provided its in 1/50 of a second.

Now you will notice in the interrupt code I didn’t save any of the registers, well that’s because I didn’t use any of them, you only need to save the registers you change in the interrupt handler.

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...