Sprites in Assembly II - IRQ Animation

Now that we successfully displayed Sprite on the screen we naturally want to move it around and animate it. If you read through my tutorial on Sprite animation in BASIC (here) you would see that it is fairly straightforward. We just need to change some Sprite registers in Video Memory.

To make it a bit more interesting let’s introduce Interrupts and write a custom Interrupt handler to update and draw the sprite.

Graphics

In the setup article we recreated the Amiga pointer, let’s stay with the same theme and animate the Amiga bouncing ball. Original demo on Amiga was done using cycling color palettes and used only two colors, we will create a smaller ball but with more colors and shading and will animate it using eight separate frames.

Below is a graphic I made in Paint.Net.



Each ball is 32 x 32 pixels in size and I translated it into 256 color representations using the default palette. So each frame takes 32 x 32 bytes = 1024 bytes or all together 8K for eight frames, which of course is very wasteful but at this time we are focusing on displaying Sprites and not on how to optimize color palettes.

Notable Memory Locations

We are already familiar with VERA registers. Other important addresses that we will use are defined in the following piece of code:

; VERA Registers
VERA_LOW = $9F20
VERA_MID = $9F21
VERA_HIGH = $9F22
VERA_DATA0 = $9F23
VERA_CTRL = $9F25

; Memory Locations
IRQ_VECTOR = $0314
OLD_IRQ_HANDLER = $06
WORK_REGISTER = $02

; VRAM Locations
SPRITE_GRAPHICS = $4000
SPRITE1 = $FC08

IRQ_VECTOR is address where the address of the current (default) interrupt handler is located

OLD_IRQ_HANDLER is pointing to Zero Page location where we will store the address of the Original Interrupt handler

WORK_REGISTER is zero page location where we will do some calculations in our program

SPRITE_GRAPHICS is location in Video RAM where we will store our Sprite data

SPRITE1 is hardcoded address where 8 bytes of Sprite 1 registers start


Why Using Interrupts

We could, of course, write a program to cycle through frames of animation and display them on screen using a simple loop. It would run too fast so we could just add an empty “busy” loop and be done with it.

However, when developing games in Assembly, sooner or later we have to tackle interrupts. They are critical tools and the sooner we get used to them the better. Some of most common reasons to use them are to maintain the constant speed of the game and to prevent screen tearing - an ugly effect when the display content is changed during the the update so we have certain object displayed at two different time frames and therefore “teared” if the movement occurred between those two timeframes.

One most common interrupt is triggered by VERA each frame. It means that VERA will send a signal to the CPU around 60 times per second and when CPU receives the signal it will look at address $0314, read the address at $0314 (low byte) and $0315 (high byte) and continue executing at that address. Please note that we can reconfigure the line at which interrupt is triggered and we can even change those settings on the fly to create very desirable functionality we might look at at a later time. To take advantage of this we can replace the default interrupt handler (code that is executed each time the IRQ is triggered) or insert our own code and then continue with the default handler. We will implement the latter.




The code is actually very simple. It is however very important that we block any interrupts during the process of rerouting the interrupt handling process. We do that with Assembly instruction SEI which sets the I flag in the Status register of the CPU. During the time when this flag is set no IRQ requests will be processed. (note that there are other kinds of interrupts on the system that we can’t disable). We turned it on again (clear the I flag) with instruction CLI.

Let’s look at the code:


; Insert new IRQ Vector
sei
lda IRQ_VECTOR
sta OLD_IRQ_HANDLER
lda #<BounceSprite
sta IRQ_VECTOR
lda IRQ_VECTOR+1
sta OLD_IRQ_VECTOR+1
lda #>BounceSprite
sta IRQ_VECTOR+1
cli


I believe the code is pretty self explanatory. We simply move two byte addresses to a new location and insert address of our interrupt handler to $0314 where CPU will look for. All we have to next is to call the old IRQ handler at the end of our IRQ routine by simple indirect jump instruction:

jmp (OLD_IRQ_HANDLER)

Configuring Sprite

As seen before, creating a sprite requires two steps. The only difference now is that we need to transfer a bit more data from CPU RAM to Video RAM. After data is the VRAM we turn it on and exit back to BASIC. That is all that is there in the main program. The actual bouncing and rotation is happening in our new Interrupt handler.

Interrupt Handler

The BounceSprite interrupt handler can be split into four logical parts:
  • Update X and Y position
  • Check if we hit Left, Right, Bottom or Top edge of the screen and if did change direction
  • Animate the sprite
  • Update Sprite registers with new location and pointer to graphics

Update X and Y position

We update X and Y by adding the DeltaX and DeltaY. In our code we are moving our sprite by one pixel each frame. Since the screen is wider and taller 256 pixels we have to use two bytes for those operations.


; Update X
lda PosX
clc
adc DeltaX
sta PosX
lda PosX+1
adc DeltaX+1
sta PosX+1

This is very standard code but there is one interesting feature of this routine. It is used for moving in all four directions therefore we are using signed numbers for DeltaX and DeltaY. So when the ball is moving up DeltaY must be -1. If you remember how binary numbers work that means that both high and low byte must be 255 or $FF in hex or %11111111 in binary.


Check Edges

For the top and left edge we compare current Y and X positions against 0. Right edge is 607 and the bottom edge is 447. How did we come to these numbers?

First we have to remember that we are setting top left pixel of the sprite so when calculating the edge we have to use following formula:

Edge = Screen Width - 1 - Sprite Width

In our case the screen is 640 pixels wide, but since the first pixel starts at 0 we know that right most pixel is 639 and since sprite is 32 pixels wide we know that the right edge of the ball hit the edge when the leftmost pixel is at 640-1-32=607. In hex that is $025F.

We use the same formula for the Y direction.

Checking the right edge is therefore:

check_right:
lda PosX+1
cmp #$02
bne check_left
lda PosX
cmp #$5F
bne check_left

; hit the right edge, DeltaX <= -1
lda #255
sta DeltaX
sta DeltaX+1
jmp check_bottom


In a similar way we do other three edges.

Animate sprite

Animating sprite in most cases means changing its appearance which obviously is done by changing the pointer to the graphics VERA needs to display. In our case we have eight frames of graphics to represent one shift of squares on the ball so that the full cycle is complete and after the eighth frame we can start with the first one again. Of course we count from 0 to 7 again and we store the current frame in a variable Frame.

The code is very straightforward:

; Update Frame
    inc Frame
    lda Frame
    cmp #8
    bne :+
    lda #0
:   sta Frame

If we wanted to rotate it in the opposite direction we would use dec instruction and could even remove the cmp instruction and use bmi instruction.

Next step is to calculate the address of the sprite data. Time to do some math. First of all let’s figure out where our data is. We uploaded it to VRAM from the location $4000 on. We also know that each sprite uses 1024 bytes so that means that

Frame 0 starts at $4000
Frame 1 starts at $4400
Frame 2 starts at $4800
and so on… 


We also know that Sprite registers only accept bits 5-16 so we have to round our address to 32 so above pointers become:

Frame 0 starts at pointer $0200
Frame 1 starts at pointer $0220
Frame 2 starts at pointer $0240
and so on…

As we see each next frame we have increase our pointer by 32 ($20 hex), so the formula for calculating the pointer becomes:

Pointer = $0200 + Frame * 32

There are several ways to multiply with 32.

The fastest would be using lookup tables. We would simply need 8 bytes for High byte and 8 bytes for low bytes.

I chose a bit slower method where we multiply by 2 using bit shifting and simply repeat it 5 times (2*2*2*2*2=32). I didn't even use a loop, so it is not the fastest and not the shortest but I would call it the laziest method. :-)

; Multiply by 32
sta WORK_REGISTER
stz WORK_REGISTER+1
asl WORK_REGISTER
rol WORK_REGISTER+1
asl WORK_REGISTER
rol WORK_REGISTER+1
asl WORK_REGISTER
rol WORK_REGISTER+1
asl WORK_REGISTER
rol WORK_REGISTER+1
asl WORK_REGISTER
rol WORK_REGISTER+1

; add starting address ($4000 translates to $0200)
lda WORK_REGISTER+1
clc
adc #$02
sta WORK_REGISTER+1

We are of course dealing with two byte values and we shouldn’t forget to add the starting address at the end.

Update Vera

The last step is to update Sprite settings in VERA registers which is straightforward now that we have all the data ready: new X and Y positions and new pointer to graphics.

Just don’t forget, you are not supposed to exit the interrupt handler with rts instruction but to jump to the original handler we saved the location for. 


Full source code is available here.
And binary can also be downloaded here.

Part I - Sprites in Assembly - Setup

Part III - Sprites in Assembly - Collision Detection

Comments

Popular posts from this blog

Commander X16 Premium Keyboard

Default Palette

Hello VERA (BASIC vs C vs Assembly)