The GTE Programming Tutorial

This document is intended to be used by intermediate programmers as a tutorial on using GTE for their own use. Functions such as initializing the engine, setting up sprites and tiles, and common operations will be exampled. To build upon previous examples, by the end of the tutorial, a simple, but functional game will have been created. So, if you want to move into the world of fast game programming for the GS, continue on...

This tutorial is written in C, but anyone with experience writing IIgs assembly code should have no trouble picking it up. We begin by starting with the simple program skeleton below.

/**
 * GTE Tutorial skeleton code
 */
#include "gte.h"

int main(int argc, char *argv[])
{
   return 0;
}
And that's it to start with. If this compiles successfully, that means the GTE header files are successfully installed.


Step 1: Initializing the engine

Initialize GTE is very similar to the process of starting any of the IIgs Tool Sets. A call to GTEStartUp is made with the appropriate parameters. The parameters tell the engine what mode you intend to run and the size of the on-screen playing field. the function prototype for the function call is

int GTEStartUp(unsigned int mode, unsigned int width, unsigned int height);
The mode arguement is broken down into three sections -- the tile size, background #0 and background #1. For now we will only set the tile sizes and let the background options remain at their default values. The function also returns a status value upon completion. It will be equal to NO_ERROR if successful, or OUT_OF_MEMORY if the engine could not allocate enough memory for its data structures. This must be checked!

The tiles sizes can be chosen as one of TILES_4X4, TILES_8X8, or TILES_16x16. The next two parameters define the width and height of the playing field in tiles!. It is important to remember the size of the field is proportional to the size of the tiles used, thus a 10 by 10 playing field is much smaller using 4x4 tiles than using 16x16.

Finally, when you're program is finished, you must make a call to GTEShutdown(). This cleans up the internal data structures and deallocates any memory used by the engine during its use.

Putting this knowledge to use, we can now extend our skeleton code a bit to the following:

/**
 * GTE Tutorial skeleton code
 */
#include "gte.h"

#define TILE_WIDTH  16
#define TILE_HEIGHT 9

int main(int argc, char *argv[])
{
  /* Make a 256x144 playing field */
  if (GTEStartUp(TILES_16X16, TILE_WIDTH, TILE_HEIGHT) == NO_ERROR) { 

    /* Do something here ... */

  }

  GTEShutDown();
  return 0;
}

Step 2: Setting up the screen

The first thing to realize, is that GTE is primarily focused around that little region defined during GTEStartup() and has relatively few functions that deal with the graphics screen as a whole. That said, there is still the need to set things up on the whole screen before getting into that game. The set of functions that are useful in this context are:

These functions should be faily self explanatory, we will use the SetPallete() and ClearPhysicalScreen() functions in our example to set up a greyscale pallete and clear the screen to color 0.

The GTEClearPhysicalScreen() funtion takes a simgle integer parameter and fills in the entire screen with that value. So, to fill the screen with zeros is as simple as issuing GTEClearPhysicalScreen(0x0000).

The SetPallete() function takes two parameters, a pallete number and a pointer to an array of 16 integers where each integer contains a color in RGB format. So let's extend our previous code to do this. We will also use a function call GTEWaitKeypress() to pause so we can see our beautiful, black screen.

#include "GTE.h"

int greyscale[] = {0x000,0x111,0x222,0x333,0x444,0x555,0x666,0x777,
                   0x888,0x999,0xAAA,0xBBB,0xCCC,0xDDD,0xEEE,0xFFF};

void main(void)
{
   GTEInit();

   if ( GTEStartup( TILES_16X16, 16, 9 ) == 0 ) {  /* make a 256x144 playing field */
      GTESetPallete( 0, greyscale );              /* set the palette */
      GTEClearPhysicalScreen( 0x0000 );          /* clear the screen to black */
      GTEWaitKeypress();                       /* wait for the user */
   }

   GTEShutdown()
}

Step 3: Positioning the playing field

One nicity is that the playing field can be positioned anywhere on the screen that it will fit using the GTESetScreenOrigin() fuctions. This takes 2 parameters, an x and y corrdinate and attempts to move the playing field. If it cannot be moved, then no action is performed. Since we will do some more initializing later on, let's make a separate function to take care of all of our setup. Also, we'll define our screen size using defines for increased flexibility.

One point to mention is that most functions that take a width or an x coordinate deal with a relative size of bytes. Since the graphics screen is 160 bytes wide, the legal range for values is 0 - 159. There are a few exceptions, but they will be dealt with on a case-by-case basis.

#include "GTE.h"

#define TILE_WIDTH 16
#define TILE_HEIGHT 9

#define TILE_SIZE 16

int greyscale[] = {0x000,0x111,0x222,0x333,0x444,0x555,0x666,0x777,
                   0x888,0x999,0xAAA,0xBBB,0xCCC,0xDDD,0xEEE,0xFFF};

void setup_stuff(void)
{
   GTESetPallete( 0, greyscale );              /* set the palette */
   GTEClearPhysicalScreen( 0x0000 );          /* clear the screen to black */

   /* We'll center the playing field since that looks nice */

   GTESetScreenOrigin( 160 - ( TILE_WIDTH*TILE_SIZE / 4 ),
                        100 - ( TILE_HEIGHT*TILE_SIZE / 2 ));
}

void main(void)
{
   GTEInit();

   if ( GTEStartup( TILES_16X16, TILE_WIDTH, TILE_HEIGHT ) == 0 ) {  
                                             /* make a 256x144 playing field */
      setup_stuff();
      GTEWaitKeypress();                       /* wait for the user */
   }

   GTEShutdown();
}
And we're done! Now we have all the elements in place to start seeing some sction on the screen; so on to....

Step 4: Creating sprites

Well, nothing too exciting has happened yet, since we can't see any of our changes really doing anything. So let's have some fun moving some sprites around. Working with sprites is a four step process.

  1. Loading the picture file containing the sprites.
  2. Compiling sprites from the picture
  3. Adding a sprite to Object Attribute Memory
  4. Updating the playing field
Loading pictures is a straightforward process. We will use the function GTELoadSHR(). As the name suggests, this can only be used to load raw super hi-res picture files which are uncompressed screen dumps. The function returns a handle to the loaded picture, or NULL if it fails. Thus the code to load a picture would be the following.
Handle picHandle;

picHandle = GTELoadC1Pic("mysprites.shr");
if (picHandle == NULL) {
   printf("Oops, I can't load this picture!\n");
   return;
}

/* Else do stuff */

We'll, use this code in a function we'll call LoadSprites() in our framework that will load a bunch of sprites from disk.

So we have to code to load in the picture we'll use, so the next step is to compile to sprites into GTE's internal format. The function to use, GTECreateSprite() has several options which we'll ignore for now. Instead we'll use the defaults which will give us a sprite that is solid and will be clipped to te edges of the screen.

The first thing to do when compiling sprites is to set which color is transparent in the sprite. Usually this color is black (color 0), but it can be set to any value between 0 and 15 with the GTESetBackgroundColor() call. So let's write out our new function. We will set it up to take several arrays to specify lists of sprites.

int LoadSprites(int numSprites, int x[], int y[], int w[], int h[], 
                SpritePtr sprites[], char *filename)
{
   Handle picHandle;
   int i, spriteID;

   picHandle = GTELoadSHR( filename );    /* try loading the file */

   if ( picHandle == NULL ) {
      return 0;                           /* signal that we failed */
   }

   GTESetTransparentColor( 0 );           /* make color 0 transparent */
  
   for ( i = 0; i < numSprites; ++i )     /* compile the sprites */
      sprites[i] = GTECreateSprite( *picHandle, NULL, x[i], y[i], w[i], h[i], Opaque );
  
   return i ;                             /* the largest sprite ID */
}

There, that wasn't so bad. Now we have a list of sprites ready to go. The third step is to enqueue the sprites for the next frame of animation. This is a little more complicated than just drawing them directly (though you can do this also), it allows for a better looking end result. There is just a single call that is needed, GTEEnqueueSprite(). There are several options that go with this call also, but we'll ignore them for a bit.

GTEEnqueueSprite() takes an (X,Y) corrdinate pair and a sprite ID and will draw the sprite with the upper left corner at (X,Y). There are two more parameters for various options, but we'll leave them set to 0 for now.

After adding all the sprites we want to a frame, we make the call to GTEUpdate() which takes a starting and ending line along with some parameters. Just follow the example for now and we'll discuss all the options in a bit.

So, building on our previous code, let's make a program that bounces a sprite around the screen

#include "GTE.h"

#define TILE_WIDTH 16
#define TILE_HEIGHT 9

#define TILE_SIZE 16

int greyscale[] = {0x000,0x111,0x222,0x333,0x444,0x555,0x666,0x777,
                   0x888,0x999,0xAAA,0xBBB,0xCCC,0xDDD,0xEEE,0xFFF};

/* Define the locations of all the sprites in the sprite sheet */

#define NUM_SPRITES 1

unsigned int sx[NUM_SPRITES] = {0};
unsigned int sy[NUM_SPRITES] = {0};
unsigned int sw[NUM_SPRITES] = {40};
unsigned int sh[NUM_SPRITES] = {20};
SpritePtr sprite[NUM_SPRITES];

void do_stuff(void)
{
  int x,y;
  int xv,yv;
  int i;

  x = y = xv = yv = 0;

  /* Set up sprite #0 in the OAM */
  OAM[0].flags  = 0;
  OAM[0].sprite = sprites[0];

  /* do 500 frames of animation */
  for (i = 0; i < 500; ++i) {             

   /* Set the position of the sprite */
   OAM[0].x = x;
   OAM[0].y = y;

   /* Draw everything */
   GTEUpdate(0);

   /* Update the sprite's position */
   x += xv;
   y += yv;

   if (x < 0) { x = 0; xv =- xv; }
   if (y < 0) { y = 0; yv =- yv; }
   if (x > TILE_WIDTH*8)   x = TILE_WIDTH*8;   xv =- xv; }
   if (y > TILE_HEIGHT*16) y = TILE_HEIGHT*16; yv =- yv; }
   }
}

int LoadSprites(int numSprites, unsigned int x[], unsigned int y[], 
                unsigned int w[], unsigned int h[], 
                SpritePtr sprite[], char *filename)
{
  Handle picHandle;
  int i, spriteID;

  picHandle = GTELoadSHR(filename);       /* try loading the file */

  if (picHandle == NULL) {
    return 0;                             /* signal that we failed */
  }

  GTESetTransparentColor(0);              /* make color 0 transparent */
  
  for (i = 0; i < numSprites; ++i)        /* compile the sprites */
    sprites[i] = GTECreateSprite(*picHandle, NULL, x[i], y[i], w[i], h[i], Opaque);
  
  return i ;                              /* the largest sprite ID */
}

void setup_stuff(void)
{
  GTESetPallete(0, greyscale);            /* set the palette */
  GTEClearPhysicalScreen(0x0000);         /* clear the screen to black */

  /* We'll center the playing field since that looks nice */
  GTESetScreenOrigin(160 - (TILE_WIDTH * TILE_SIZE / 4),
                     100 - (TILE_HEIGHT * TILE_SIZE / 2));
}



int main(int argc, char *argv[])
{
   /* make a 256x144 playing field */
   if (GTEStartup(TILES_16X16, TILE_WIDTH, TILE_HEIGHT) == NO_ERROR) {  
                                               
      setup_stuff();                            /* set up everything */

      if (LoadSprites(NUM_SPRITES, sx, sy, sw, sh, sprites, "mysprites") == NUM_SPRITES) {
         do_stuff();                            /* animate */
         GTEWaitForKeypress();                  /* wait for the user */
      }

    GTEShutdown();
    return 0;
}