Videopac G7000 BIOS
Last changed: 2003-07-22

A more complex example: joystick, sprites

In this section I explain a program which demonstrates the use of the joystick routines and how to display sprites. You can control a dot with the joystick. The dot changes into an arrow and moves if you move the joystick. By pressing fire you can double the size of the dot/arrow. This program is useful if you want to test a joystick, for example after some contact cleaning.

The routines the program uses

This program uses three new routines. The first one called waitvsync waits for the next VSYNC. I use it, because I only want to check for joystick movement once per frame. The routine getjoystick tests the position of one joystick. The number which joystick to test has to be put into R1. The outputs of getjoystick are 1/0/0FFh in register R2 for X axis and R3 for Y axis. All numbers are in 2's complement, so 0FFh is really a -1. If the button is pressed, F0 is set. This output is direct usable for changing positions, simply add R2/R3 to the X/Y position.

If your sprite has a front side which always points into the direction it moves, you can use decodejoystick. It takes the offset data in R2/R3 and converts them into a direction in R1. This direction can be used to change the shape of the sprite.

The program

Before I start to explain the program in details, some general remarks. All VDC accesses are done via tables. The program uses some variables in internal RAM, they are prefixed with iram_. All position data is first changed in internal RAM and then transferred into the sprite registers. There are no checks to make sure that the position is still on the screen. I don't explain every line of code here, only the main loop.

Calling getjoystick and decodejoystick

This starts at the beginning of the main loop, I simply call getjoystick, test for fire, call decodejoystick and initialise my table pointer.


loop
        call    waitvsync       ; execute only once per frame
        mov     r1,#0           ; joystick 0
        call    getjoystick     ; get offsets

        ; test, if fire
        mov     r1,#iram_colctrl
        mov     a,#col_spr_white | spr_double
        jf0     firepressed     ; fire ?
        mov     a,#col_spr_white
firepressed
        mov     @r1,a           ; store color/control

        call    decodejoystick  ; get direction from offsets
        call    extramenable    ; enable extram
        mov     r0,#07Fh        ; start of table

Test, if I have to change the shape

Before I can test if the shape has changed, I have to test for the neutral position, because decodejoystick maps neutral to right. I map the neutral position to 8 if the X and Y offsets are both 0. Then I can test if the shape I need is already set.


        ; test, if joystick is in neutral position
        mov     a,r2            ; x-offset
        jnz     shapetest       ; left/right

        mov     a,r3            ; y-offset
        jnz     shapetest       ; up/down

        mov     r1,#8           ; shape: neutral

shapetest
        ; test, if shape has change since last frame
        mov     a,r1            ; we need r1 as pointer
        mov     r7,a            ; so put contents into r7
        mov     r1,#iram_shape  ; last shape
        mov     a,@r1           ; get it
        xrl     a,r7            ; compare
        jz      setpos          ; no need to set shape,
                                ; skip that part

Set the new shape

Now I set the new shape, which number I have to use is stored in R7. This part is skipped completely, if the shape is already set correctly. In a real program the part which copies the data should be put into a separate page together with the data, because the 8048 can only access data in ROM which is in the same page as the code. To maximise the space usable for shapes, the only code in the same page should be the one which copies the data. But this is only a small example, everything fits into one page, so this is not really necessary. Look at the program from the chapter about collision checks to see how to handle this more correctly. When creating shape data you should know that the displayed data is mirrored by the VDC, bit 0 is displayed left.


        ; set new value of iram_shape
        mov     a,r7            ; the number of the shape
        mov     @r1,a           ; put into iram_shape

        ; init table to copy shape data
        mov     a,#8            ; copy 8 bytes
        movx    @r0,a
        dec     r0
        mov     a,#vdc_spr0_shape
        movx    @r0,a
        dec     r0

        ; now copy the data
        mov     a,r7            ; number of shape
        rl      a
        rl      a
        rl      a               ; 3*rl = a*8
        add     a,#spritedata & 0FFh
        mov     r1,a            ; start of shape data
        mov     r7,#8           ; 8 bytes
copyspriteloop
        mov     a,r1
        movp    a,@a            ; get byte
        movx    @r0,a           ; store in table
        dec     r0
        inc     r1
        djnz    r7,copyspriteloop

Adjusting the positions in internal RAM

Now I add the offsets to the old position. The Y position is no problem. But the X position is 9 bit wide, so I have to use the carry flag. Another problem is that the X offset is only 8 bit wide. I am dealing with signed numbers in 2's complement, so to expand the X offset to 9 bits I have to reuse bit 7 as bit 8. Because the X offset is only +1/0/-1, bits 1-7 are always the same, so I can reuse bit 1 as bit 8.


setpos
        ; adjust sprite positions in iram
        ; y is simple
        mov     r1,#iram_y      ; y position
        mov     a,@r1           ; get it
        add     a,r3            ; add offset
        mov     @r1,a           ; store y position

        ; x is a 9 bit add using carry, we need to
        ; expand r2 to 9 bit also
        mov     r1,#iram_xl     ; low byte of x
        mov     a,@r1           ; get it
        add     a,r2            ; add offset, sets
                                ; carry if necessary
        mov     @r1,a           ; store low byte of x
        mov     r1,#iram_xh     ; high bit of x
        mov     a,@r1           ; get it
        addc    a,#0            ; add the carry
        mov     r7,a            ; we need this later
        mov     a,r2            ; get offset
        rr      a               ; reuse bit 1 of offset
                                ; as bit 8. we need it,
                                ; because we are dealing with
                                ; 2s complement if R2=-1=01ffh
        add     a,r7            ; add result from above to
                                ; bit 8 of offset
        anl     a,#001h         ; we only need 1 bit
        mov     @r1,a           ; store it as high bit

Copying the sprite position into the VDC

The only thing that is left to do is to put the sprite position into the VDC. For this I use the table routine in the VSYNC. First I have to prepare the table. The Y position can be put directly into the table. The X position is 9 bit wide, but this time the lowest bit is separated from the rest. In internal RAM the highest bit is separated from the rest. So I have to re-split the X position and combine the lowest bit with the color/control value stored at the beginning of the main loop when testing for fire.


        ; prepare table for sprite positions
        mov     a,#3            ; copy 3 bytes
        movx    @r0,a
        dec     r0
        mov     a,#vdc_spr0_ctrl
        movx    @r0,a
        dec     r0

        ; set sprite positions from iram using table
        mov     r1,#iram_y      ; y position
        mov     a,@r1           ; get it
        movx    @r0,a           ; put it into table
        dec     r0

        ; x position: recombine xh and xl and split
        ; it into 8-1/0
        mov     r1,#iram_xh     ; highest bit of x position
        mov     a,@r1
        rrc     a               ; highest bit of sprite_x
                                ; into carry
        mov     r1,#iram_xl     ; low byte of x position
        mov     a,@r1
        rrc     a               ; lowest bit into carry,
                                ; highest bit into 7
        movx    @r0,a           ; put bit 8-1 of sprite_x into
                                ; sprite control 1
        dec     r0
        mov     a,#0            ; we only need carry (lowest
                                ; bit of sprite_x)
        rlc     a               ; lowest bit of sprite_x
                                ; into bit 0
        mov     r7,a            ; store in r7
        mov     r1,#iram_colctrl; color/control
        mov     a,@r1           ; get it
        orl     a,r7            ; put it together
        movx    @r0,a           ; put it into sprite control 2
                                ; via table
        dec     r0

        call    tableend        ; thats all

        jmp     loop