Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

   Assembly

     Introduction

     Keyboard Input

     Basic Graphics

       Part I

       Part II

     C & Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

TIGCC Assembly Lessons

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.

Sprite Image

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:

TI-89 AMS 2.05 sprite.89z TI-92+ AMS 2.05 sprite.9xz

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.

  1. Make the sketch program support diagonal movement.
  2. 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)
  3. 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.


 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit