DISPLAYING SPRITES Sprites, sprites, and more sprites. They are simply everywhere. Imagine you want to draw a nice fancy graphic onto your screen. How are you going to do it? 1. Use mathematical formulae (or otherwise), and plot the pixels directly onto screen. Yuck. This is way too low-level. 2. Draw a graphic in a software package beforehand, and simply use that information and blit to screen. Obviously the second method is far more attractive. In this document, my aim to how to display sprites on screen. Any suggestions or criticisms on this document welcome. James Chow aka jc 9 June 1998 james@chowfam.demon.co.uk --------------------------------------------------------------- Creating A Sprite ----------------- Before we a sprite is created, we must understand how the video memory works in (slightly) more detail. (0,0) ----------------- | | | | | | | | | | (x,256) ----------------- | | | | | | | | | | ----------------- (1023,511) (0,y) (64,y)..(960,y) The video memory is divided into a number of "texture pages". Their boundaries lie in multiples of 64 in the x direction and multiples of 256, in the y direction. Each pixel in the frame buffer also has a width of 16 bits. If we were to draw all our sprites with a colour depth of 16 bits, you'll soon find that it runs out quite fast. To compensate for this, the facility is provided so that we can use 4 bit or 8 bit Colour Look-Up Tables (CLUT). Each point in the sprite does not hold the actual colour but an index to the CLUT. What this means is that in one 16 bit value in the memory we can store 4 pixels (if using a 4 bit CLUT), or 2 pixels (if using a 8 bit CLUT), or a single pixel if using direct colour. Although, in a single sprite we cannot mix these. This gives a slight restriction when we are drawing our sprites. If we decide to use a 4 bit CLUT, then the sprite width must be a multiple of 4, and if 8 bit CLUT, then multiple of 2. If a pixel is partially occupied, it is considered to be completely used by the libraries, and so when the sprite on screen, it may not match exactly how it was origrinally drawn, or there may be black borders along the edges. Where to Place the Sprite ------------------------- Using TIMUtil, we can allocate a position in video memory where the sprite should be placed. Although, we can ignore this information during coding, it's more flexible to do it this way. In addition to the position of the sprite, if we're using a 4 bit CLUT or 8 bit CLUT, we need to allocate a position for that as well. We need to make sure that the positions of the sprite and CLUT does not conflict with the display buffers, or with any other sprites and CLUTs. If this happens, you'll probably get nothing on screen, even though your code runs perfectly. As well as fixing the position in video memory, where in the sprite should be placed, we must also fix the position in normal memory where the TIM file, now containing the sprite, should be downloaded to - somewhere in between 0x80090000 and 0x801fffff. Outside these boundaries, and nothing will work. The reason for this, is that we cannot directly download our TIM file into video memory and so must be transferred from main memory into video memory during runtime. To do this, we do, #define TIMLOC (0x800f8000) //for example //somewhere in our code GsIMAGE img; RECT rect; GsGetTimInfo((u_long*)(TIMLOC+4),&img); rect.x = img.px; rect.y = img.py; rect.w = img.pw; rect.h = img.ph; LoadImage(&rect,img.pixel); if ((img.pmode>>3)&0x01) { rect.x = img.cx; rect.y = img.cy; rect.w = img.cw; rect.h = img.ch; LoadImage(&rect,img.clut); } GsIMAGE is structure, which holds the details of our TIM file - the location of the pixel data, CLUT data, and whether there is a CLUT. If there is a CLUT then, the pmode field will hold 0x08 (4 bit) or 0x09 (8 bit). RECT is a convenience structure (albeit an important one), so that the system knows where to load the info to. Setting GsSPRITE and Displaying ------------------------------- After the sprite and CLUT has been loaded into video memory we need to set the fields of a variable of GsSPRITE struct accordingly. attribute fields indicate which properties are valid for the display of this sprite, and whether semi-transparencies apply - similar to those for lines and filled boxes. x,y fields indicate where to display the sprite on the display buffer. w,h fields indicate the width and height of the *rendered* sprite. If we're using a 4 bit CLUT, since 4 pixels occupy 1 pixel space in the video memory, this will 4 times the *image* width. And for an 8 bit CLUT, this will be twice the *image* width. tpage indicates which texture page in memory the beginning of the sprite is located. We use GetTPage(), to find this out. u,v fields indicate the offsets in a texture page where the sprite is located, since a texture page can hold many sprites. Using these fields and w,h fields we can select a small portion of a larger sprite to display on screen. cx,cy for the position of the CLUT, if any. r,g,b indicate the brightness of the image. For a red tint, we have a high r value and low g,b values. For white light we simply have equal values of each. mx,my indicate the position in the sprite to scale and rotate around. This becomes the origin of the sprite and which will transformed so that the origin is located at the x,y positions. scalex,scaley are scaling factors. 4096 produces a scaling factor of 1. In other words - original size. rotate indicates amount to rotate. 4096 for every 1 degree. We can sort our GsSPRITE into the ordering tables. Using either, GsSortSprite(&sprite,&ot,0) or GsSortFastSprite(&sprite,&ot,0) Using GsSortFastSprite ignores translation (mx,my), scaling (scalex, scaley) and rotation (rotate) fields. One final thing - if the sprite in video memory is larger than the size of a texture page, then only GsSortFastSprite can be used. If GsSortSprite is used, it will fail to be displayed correctly. Even if the sprite spans between texture pages it should work. When you want to translate/scale/rotate a large sprite, then you will need to work in texture page sizes. Code ---- I've decided not to include a demo TIM file. But the creation is simple. Load it in at address 0x80094000. To save space, I'm reusing the pad reading routines from "Reading the Controllers" and the main function from "Understanding Drawing Primitives". /**********/ /* main.c */ /**********/ #include#include "pad.h" #define OTLEN (1) #define SCNW (320) #define SCNH (240) #define TIMLOC (0x80094000) GsSPRITE sprite; void initGame(void) { GsIMAGE img; RECT rect; FntLoad(960,256); FntOpen(0,0,SCNW,SCNH,0,512); GsGetTimInfo((u_long*)(TIMLOC+4),&img); rect.x = img.px; rect.y = img.py; rect.w = img.pw; rect.h = img.ph; LoadImage(&rect,img.pixel); if ((img.pmode>>3)&0x01) { rect.x = img.cx; rect.y = img.cy; rect.w = img.cw; rect.h = img.ch; LoadImage(&rect,img.clut); } switch (img.pmode) { case 0x08: //4 bit with CLUT sprite.attribute = 0x00000000; sprite.w = img.pw*4; sprite.tpage = GetTPage(0,0,img.px,img.py); break; case 0x09: //8 bit with CLUT sprite.attribute = 0x01000000; sprite.w = img.pw*2; sprite.tpage = GetTPage(1,0,img.px,img.py); break; default: //16 bit sprite.attribute = 0x02000000; sprite.w = img.pw; sprite.tpage = GetTPage(2,0,img.px,img.py); break; } sprite.x = 50; sprite.y = 50; sprite.h = img.ph; sprite.u = 0; sprite.v = 0; sprite.cx = img.cx; sprite.cy = img.cy; sprite.r = 128; sprite.g = 128; sprite.b = 128; sprite.mx = sprite.w/2; sprite.my = sprite.h/2; sprite.scalex = 4096; sprite.scaley = 4096; sprite.rotate = 0; } int updateGame(void) { static int padstate; if (padConnected(0) == PADin) if ((padType(0) == PADstandard)|| (padType(0) == PADanalogue)|| (padType(0) == PADdualshck)) if ((padState(0) & PADstart)&& (padState(0) & PADselect)) return 1; if (padState(0) & PADLup) sprite.y--; if (padState(0) & PADLdown) sprite.y++; if (padState(0) & PADLleft) sprite.x--; if (padState(0) & PADLright) sprite.x++; if (padState(0) & PADRleft) sprite.r = (sprite.r+1)%256; if (padState(0) & PADRdown) sprite.g = (sprite.g+1)%256; if (padState(0) & PADRright) sprite.b = (sprite.b+1)%256; if (padState(0) & PADL1) sprite.rotate = (sprite.rotate+359*4096)%(360*4096); if (padState(0) & PADR1) sprite.rotate = (sprite.rotate+4096)%(360*4096); if (padState(0) & PADL2) if (padState(0) & PADselect) sprite.scalex -= 256; else sprite.scalex += 256; if (padState(0) & PADR2) if (padState(0) & PADselect) sprite.scaley -= 256; else sprite.scaley += 256; padstate = padState(0); return 0; } void drawGame(GsOT *ot) { FntPrint("X:%d Y:%d\n",sprite.x,sprite.y); FntPrint("SCALEX:%d SCALEY:%d\n",sprite.scalex,sprite.scaley); FntPrint("ROTATE:%d (%d DEGS)\n",sprite.rotate,sprite.rotate/4096); FntPrint("R:%d G:%d B:%d\n",sprite.r,sprite.g,sprite.b); FntFlush(-1); GsSortSprite(&sprite,ot,0); } void main(void); //same as from Understanding Drawing Primitives