TIGCC Assembly Programming Lessons
Lesson 3: Basic Graphic Techniques
Step 4 - Intro to Sprites
Sprites, also know by many other names, are used to make displaying
graphics easier. Simple graphics like we are using on a calculator are
called rasterized. Basically, this means they are simply collections of
pixels on a 2D Cartesian coordinate scale (i.e. x,y). Because the pixels
are so small, we can combine groups of them to make shapes and images.
These shapes or images are commonly called sprites (or bitmaps, images,
pixmaps, and many other names). We can display these sprites on the
screen in various locations to make graphics that we can recognize. We
can even collect small groups of sprites to make large sprites. This was
often done on older gaming platforms like the Nintendo.
Now that we know a little about sprites, let's take a look at a simple
sprite.
Yes, this is a sprite. It's just a normal image we define pixel by pixel
(or in some cases converted from bitmaps or made with sprite editors).
Now, how do we define such data? Well, simple, we just put a 1 where our
black pixels go, and a 0 where the blank pixels are.
So, imagine it looking something like this:
0000001111000000
0000001111000000
0000111111110000
0000111111110000
0011111111111100
0011111111111100
1111001111001111
1111001111001111
1111111111111111
1111111111111111
0011001111001100
0011001111001100
1100000000000011
1100000000000011
0011000000001100
0011000000001100
Now, let's take out the zero's, and leave the ones.
1111
1111
11111111
11111111
111111111111
111111111111
1111 1111 1111
1111 1111 1111
1111111111111111
1111111111111111
11 1111 11
11 1111 11
11 11
11 11
11 11
11 11
Starting to look more familiar? I think you can probably tell that it
looks pretty much like the image.
Now that we have a general idea how sprites are made, let's take a look
at a program using a sprite.
Step 5 - Sprite Programming 101
Open a new TIGCC project called sprite. Create the main.c C source file
and an assembly file name sprite. The main.c source is still available
in lesson 1. Edit the assembly file so
it looks like this:
.include "os.h"
.equ AMS_jumptable,0xC8
.equ KEY_ESC,264
.equ USE_TI89,1
.ifne USE_TI89
.equ KEY_LEFT,338
.equ KEY_RIGHT,344
.equ KEY_UP,337
.equ KEY_DOWN,340
.equ WIDTH,160
.equ HEIGHT,100
.else
.equ KEY_LEFT,337
.equ KEY_RIGHT,340
.equ KEY_DOWN,344
.equ KEY_UP,338
.equ WIDTH,240
.equ HEIGHT,128
.endif
.text
.xdef asm_main
|-----------------------------------------------------------------------------------------
| asm_main - our program starts here
|
| short int ax,ay,new_ax,new_ay;
.equ ax,-2
.equ ay,-4
.equ new_ax,-6
.equ new_ay,-8
asm_main:
movem.l %d3/%a5,-(%sp) | save registers
link %fp,#-8 | allocate stack frame
movea.l AMS_jumptable,%a5
move.l 4*ClrScr(%a5),%a0 | ClrScr();
jsr (%a0)
move.w #WIDTH/2-8,(%fp,ax) | (ax,ay) = (xCenter,yCenter)
move.w #HEIGHT/2-8,(%fp,ay)
move.w (%fp,ax),(%fp,new_ax) | (new_ax,new_ay) = (ax,ay)
move.w (%fp,ay),(%fp,new_ay)
move.w (%fp,ax),%d0 | Sprite16(ax, ay, 16, alien, LCD_MEM, SPRT_XOR);
move.w (%fp,ay),%d1
moveq #16,%d2
clr.w %d3
lea alien(%pc),%a0
movea.w #LCD_MEM,%a1
bsr Sprite16
while:
movea.l 4*ngetchx(%a5),%a0 | %d0 = ngetchx();
jsr (%a0)
cmpi.w #KEY_ESC,%d0 | if ESC, goto end_while
beq end_while
cmpi.w #KEY_LEFT,%d0 | if not left, goto check_right
bne check_right
cmpi.w #2,(%fp,ax) | if we can't go left
bcs while | goto while
subq.w #2,(%fp,new_ax) | adjust new_ax position
bra redraw | goto redraw
check_right:
cmpi.w #KEY_RIGHT,%d0 | if not right, goto check_up
bne check_up
cmpi.w #WIDTH-16,(%fp,ax) | if we can't move right
bcc while | goto while
addq.w #2,(%fp,new_ax) | adjust new_ax position
bra redraw | goto redraw
check_up:
cmpi.w #KEY_UP,%d0 | if not up, goto check_down
bne check_down
cmpi.w #2,(%fp,ay) | if we can't move up
bcs while | goto while
subq.w #2,(%fp,new_ay) | adjust new_ay position
bra redraw | goto redraw
check_down:
cmpi.w #KEY_DOWN,%d0 | if not down, goto while
bne while
cmpi.w #HEIGHT-16,(%fp,ay) | if we can't move down
bcc while | goto while
addq.w #2,(%fp,new_ay) | adjust new_ay position
redraw:
move.w (%fp,ax),%d0 | Sprite16(ax, ay, 16, alien, LCD_MEM, SPRT_XOR);
move.w (%fp,ay),%d1
moveq #16,%d2
clr.w %d3
lea alien(%pc),%a0
movea.w #LCD_MEM,%a1
bsr Sprite16
move.w (%fp,new_ax),(%fp,ax) | (ax,ay) = (new_ax,new_ay)
move.w (%fp,new_ay),(%fp,ay)
move.w (%fp,ax),%d0 | Sprite16(ax, ay, 16, alien, LCD_MEM, SPRT_XOR);
move.w (%fp,ay),%d1
moveq #16,%d2
clr.w %d3
lea alien(%pc),%a0
movea.w #LCD_MEM,%a1
bsr Sprite16
bra while | goto while
end_while:
unlk %fp | deallocate stack frame
movem.l (%sp)+,%d3/%a5 | restore registers
rts
|-----------------------------------------------------------------------------------------
| Data Section
|
.data
alien:
.word 0x03C0,0x03C0,0x0FF0,0x0FF0,0x3FFC,0x3FFC,0xF3CF,0xF3CF
.word 0xFFFF,0xFFFF,0x33CC,0x33CC,0xC003,0xC003,0x300C,0x300C
Like in the last example, you will need to compile the correct version
for your calculator. If you are using the TI-92+/V200, adjust the
USE_TI89 constant to 0.
Build the program and run it. It will look something like this:
Step 5a - Program Analysis
This program simply moves the sprite around the screen, based on which
arrow keys you press. I'm sure you can think of ideas where this would
prove useful. A lot of the code is very simple, so we won't go over
everything. There are just a few points I want to highlight. Let's start
just above asm_main.
| short int ax,ay,new_ax,new_ay;
.equ ax,-2
.equ ay,-4
.equ new_ax,-6
.equ new_ay,-8
We have used added some additional equ directives before asm_main so we
could name our variables on the stack frame. This is just a convenience
so we don't have to remember which offset corresponds to which variable.
move.w #WIDTH/2-8,(%fp,ax) | (ax,ay) = (xCenter,yCenter)
move.w #HEIGHT/2-8,(%fp,ay)
move.w (%fp,ax),(%fp,new_ax) | (new_ax,new_ay) = (ax,ay)
move.w (%fp,ay),(%fp,new_ay)
Moving down into asm_main slightly, we see the initial assignments of
our variables. (ax,ay) is the current position of the sprite. The
(new_ax,new_ay) is where we are about to move the sprite. We start with
these two position being the same.
move.w (%fp,ax),%d0 | Sprite16(ax, ay, 16, alien, LCD_MEM, SPRT_XOR);
move.w (%fp,ay),%d1
moveq #16,%d2
clr.w %d3
lea alien(%pc),%a0
movea.w #LCD_MEM,%a1
bsr Sprite16
Here we get into the Sprite16 function. Sprite16 is one of the functions
from the TIGCC library we use to display sprites. It takes an (x,y)
coordinate, the height of the sprite (in pixels), a pointer to the
sprite data, a pointer to the LCD memory, and the method for combining
the sprite with the data already in the LCD memory.
In our case, we have a 16 pixel by 16 pixel sprite. Sprite16 is for
sprites of width 16 pixels. There also exists Sprite8 and Sprite32 for
sprites of 8 and 32 pixel widths. We'll take a look at the Sprite
definition later. For now, let's examine the call.
For starters, you can see there are a lot of function arguments, which
means we need to know the calling convention of the Sprite16 function.
If you look inside the header file sprites.h, you will see Sprite16 has
the __ATTR_LIB_C__ attribute. This means it uses the C calling
conventions. C calling conventions specify we pass the parameters in the
registers. If you look in the TIGCC docs in the Sprite16 function, you
see there are four short values (the x and y coordinates, the height,
and the mode), and two pointers (the sprite and the LCD memory address).
Data gets put into data registers, and pointers get put into address
registers. Fill them up left to right and we have our calling
conventions.
Most of the code is incredibly similar to what we had in the sketch
program from the last example, so I'm going to skip down now to the
redraw label.
redraw:
move.w (%fp,ax),%d0 | Sprite16(ax, ay, 16, alien, LCD_MEM, SPRT_XOR);
move.w (%fp,ay),%d1
moveq #16,%d2
clr.w %d3
lea alien(%pc),%a0
movea.w #LCD_MEM,%a1
bsr Sprite16
move.w (%fp,new_ax),(%fp,ax) | (ax,ay) = (new_ax,new_ay)
move.w (%fp,new_ay),(%fp,ay)
move.w (%fp,ax),%d0 | Sprite16(ax, ay, 16, alien, LCD_MEM, SPRT_XOR);
move.w (%fp,ay),%d1
moveq #16,%d2
clr.w %d3
lea alien(%pc),%a0
movea.w #LCD_MEM,%a1
bsr Sprite16
bra while | goto while
Okay, in the redraw function, we simply erase our sprite, then draw it
back at the new position. We have seen code like this before, so let's
talk about XOR logic for a moment.
XOR (eXclusive OR) is a bitwise operation over two numbers. The XOR of
a number and itself is 0, and the XOR of a number and 0 is that number.
This basic property of XOR logic allows us to use the same code to draw
and erase simple sprites. Here is more information on
XOR logic and binary math in general.
alien:
.word 0x03C0,0x03C0,0x0FF0,0x0FF0,0x3FFC,0x3FFC,0xF3CF,0xF3CF
.word 0xFFFF,0xFFFF,0x33CC,0x33CC,0xC003,0xC003,0x300C,0x300C
The only thing left in the program to cover is our sprite definition.
Remember earlier that we said we had a 16x16 pixel sprite. If you look
back at the top of this lesson, you will see the binary definition in
0's and 1's where we first showed the sprite image. This is how we
define the sprite.
So, we have 16 16 pixel segments. To define this data, we can use the
.word directive. .word defines word-sized (16-bit) data. You can see
here we have 16 words. If you convert the binary from earlier to hex,
you will see where we got our data.
Step 6 - Conclusions
We have learned a lot about graphics today using these two techniques.
You can do a lot with these simple ideas.
To do well, one must practice these skills. So, here's some ideas if you
want to try and practice your skills.
-
Make the sketch program support diagonal movement.
-
Add a current position monitor to the sketch program. (i.e. display
the current position of the pen at the bottom of the screen, or use
a cursor)
-
Add multiple copies of the sprite and practice moving them in sync.
I hope this has been helpful.
Lesson 3: Basic Graphic Techniques
Questions or Comments? Feel free to
contact us.
|