The Nintendo Reverse Engeneering Project
Well it is time for some sprites!
Things covered in day 3 will be:
What is a sprite?
Well I am not really sure were the term came from but it basically means an animated object that moves freely from the background. An obvious example is the Mario character in super Mario bros. Sprites can be any size often ranging from a few pixels to half the screen. They also can have any number of frames of animation. Be it a simple bullet traveling across the screen in search of the enemy with a single tiny frame, or a character in street fighter that has a hundred different frames that each fill the screen.
The most common way of rendering sprites is very similar to the way we drew a picture in day two. Each frame of the sprite is stored in a bitmap in off screen memory and is drawn to the screen pixel by pixel every frame. There are several problems with this. First if you do not take precautions the background is destroyed. You must preserve it, either by redrawing the background every frame or storing the contents of the background that you are going to overwrite in a bitmap in memory then, when the sprite has moved, replace the background you just copied over. And second it is slow.
Fortunately the GBA has hardware rendered sprites with cool features like rotation, scaling and alpha blending. All you need to know is were to put the bit map data for the sprites, the format it is in, and how to tell the GBA how you want your sprites drawn and it will take care of everything for you. This is obviously much faster than rendering them yourself so you may be wondering why I even bothered with mentioning the old way. There are times when hardware rendered sprites just will not work. Especially when doing 3d graphics and part of the sprite is occluded (that is computer geek talk for hidden) by another object. Since hardware sprites are inherently rectangular occlusion is hard to handle, so the only way to do it is blit (more computer geek talk for draw) them pixel by pixel checking for occlusion as you go. Also the memory set aside for sprites is limited so if you have a large sprite you may have no choice but blit him in frame by frame because all of his frames will not fit in memory at the same time. Sounds slow huh? Well it is but there are many tricks to make it fast enough to work on the GBA but I do not know them well enough to teach them and there are many other sites that cover general sprite blitting theories so I will stick to hardware sprites in my tutorial.
Memory and sprites.
There are two areas of concern when it comes to sprite memory. The first is called OAM (Object Attribute Memory). The second is character memory.
OAM is where the characteristics of each sprite are defined. You can have up to 128 sprites defined in OAM at any one time. OAM is arranged as a linear array starting at memory location 0x7000000. There are 128 entries in this array each consisting of 4 16-bit attributes for a total of 1024 bytes of data. The first 3 attributes deal with sprite attributes like position, size, shape color depth etc... the last attribute is for rotation data but is not actual part of that sprite (that probably makes no sense at this point but that is ok I will cover in more detail in a moment). The most common way to define OAM is with a structure and most people I have seen do it like this:
typedef struct tagSprite
This allows you to access your copy of OAM with an array and then once each frame copy it to the real OAM. There are two reasons for using a copy. First OAM is locked when the screen is actually being draw meaning you can only access it during the vblank and hblank (and the hblank only under certain circumstances). The second reason is that if you update OAM while your screen is not finished you may actually update it when your sprite is half draw. This will usually cause undesired effects like shearing (top half and bottom half don't exactly line up). I actually use a slightly different structure for my OAM because attribute 3 really has nothing to do with that sprite. My OAM looks something like this.
typedef struct tagOAMEntry
//Another struct for rotation data
typedef struct tagRotData
u16* OAM = (u16*)0x7000000; //this is actual OAM memory
OAMentry sprites; //this is my array of sprites
RotData* rotData = (OEMEntry*) sprites; //this is my array of rotation data that points to the same area of memory as my sprite array
Both my rotData array and my sprites array point to the same location in memory causing them to overlap. This gives me two major benefits. One, it allows me to access the rotation attributes independently from the others without the extra memory for another array. Two, it allows me to just copy the sprite array into OAM every frame since attribute three is automatically updated when I access the rotData array. You may be wondering what the hell this pa,pb,pc,pd thing is. Don't worry I am getting to it. One thing you should notice though is that my rotData structure is 4 times as large as the OAMEntry structure. If you ignore the fillers the rotData structure accesses the attribute3 of each OAMEntry structure when the arrays are pointed to the same area of memory. The pa,pb,pc,pd variable are the rotation attributes. There are a total of 32 sets of 4 rotation attributes in OAM. There can be 32 different rotation parameters that can be applied to any number of sprites. All the sprites can use the same rotation attributes or you can have 32 independently rotating and scaling sprites. But which sprites go with which attributes? Well, that is up to you. Part of one of the attributes in each sprite tells the GBA which set of rotData to use.
Bits 0-7 are the y coordinate of your object. It is a value between 0 and 255. 0 is the top of the screen and 159 the bottom. To draw a sprite that is half off the screen and at the top then you need 255 + y (assuming that y is a negative value)The reason for this is that a negative y value does not work. The coordinate 10,-1 would actually have to be 10,254 to work as expected.
Bit 8 is the rotation scaling flag. If set it will draw the sprite with the scaling/rotation parameters specified.
Bit 9 is something called the size double flag and I will talk about when I describe sprite rotation
Bits 10-11 are the mode flags and deal with alpha blending. I will talk about that when talking about alpha blending.
Bit 12 is the mosaic flag. When set the sprite will have the mosaic value applied to it. (This will be explained along with tile mode background graphics in day 4)
Bit 13 is the color mode. If set it defines the sprite as 256 colors. If cleared it is a 16-color palette
Bit 14-15 is the object shape and will be described in more detail when we actually draw our first sprite.
Bits 0-8 are the x coordinate of the sprite. Since the GBA screen is much wider than it is tall it needs an extra bit. 0 is left 240 is right and to draw off screen and to the left the equation is x = 512 + x (were x is the actual x coordinate and a negative number).
Bits 9-13 serve a dual purpose. If the rotation scaling flag is set in attribute one then they are the 5-bit index (0-31) into the rotData array and define which set of rot/scaling parameters are going to be used with that sprite. If the rotation scaling flag is not set then bits 9-11 are not used.
Bit 12 is the horizontal flip flag(the sprite will be flipped along the y access) and bit 13 is the vertical flip flag(same but x access). These atributes are not used when the rotation flag is set in attribute 0.
Bit 14-15 is the size and I will give you a nice little table that explains the use of this flag and the shape flag when we draw our first sprite.
Bit 0-9 is the index into sprite character memory of the first 8x8 tile of the sprite (to fully understand this flag wait until I describe the second area of sprite memory)
Bit 10-11 is the priority. As with backgrounds sprites can be assigned a priority of 0-3. Higher numbered sprites are drawn first meaning that a sprite of priority 0 will be drawn over the top of a sprite with priority of 3. Also a sprite priority is always higher than that of the corresponding background priority meaning that if both a sprite and a background have the same priority then the sprite will be drawn on top. One more thing: for sprites with the same priority then sprites with the lowest OAM number are drawn on top.
Bits 12-15 are the palette number. If your sprite is a 16-color sprite then this value will determine which of the 16 16-color palettes are used. If it is a 256-color sprite then these bits are ignored.
Well that pretty much sums up OAM memory except it does nothing for rotation attributes but that will come later when I talk about rotation and scaling. The next area is character memory and this is where your sprite character data is stored (the bitmaps of your sprite).
Character data starts at 0x6010000 and extends for 32KB. It consists of 1024 8x8 16 color tiles (256 color tiles actually take two 8x8 slots). The character name bits in attribute 2 refer to one of these tiles. The problem with this set up is that bit map modes (mode 3,4, and 5) actually extend into the character data area of memory cutting it in half. This means that only character names greater than 511 are allowed in the bitmap modes.
The tiles can be arranged in one of two user definable ways. 1D (1 dimensional) and 2D. If the 1D flag in REG_DISPCNT is set then the layout is 1D else it is 2D. First I will explain 2D mode.
2D mode is laid out as a big square of 8x8 tiles that is 32 tiles wide and
32 tall. In order to use sprites we must first have a way to split sprite bitmaps
into an 8x8 strip that can be easily loaded into character memory. Each character
is 8x4 bytes so the actual address of the first character is 0x601000 and the
address of the next one is 0x601000 + 8x4 = 0x6010020. This makes loading graphics
for sprites that are larger than 8x8 awkward. Fortunately I have written a tool
that will cut your sprite bitmap into 8x8 tiles that can be loaded much easier
into sprite memory. The Character Name in attribute2 points to one of these
tiles. Here is a picture that may help.
8x8 16-color sprite Character name =32
8x8 256-color sprite Character name = 0
16x16 256-color sprite Character name = 36
64x64 256-color sprite Character name = 16
To compute the actual memory address of a character the following equation will work. 0x6010000 + (CharName * 8x4) = addressOfChar. 0x6010000 is the start address of character memory and each character is 32 bytes due to the fact that they are 16-color by default. Storing of the graphics into the character memory must be done one tile at a time which is why a tool to stripe the bitmaps into a strip of tiles is necessary.
The next mode is just a big stack of tiles that are only one wide and 1024 tall (512 if 256 color). This mode may seem less intuitive but is actually the most common. In fact it is easier. The major benefit to this mode comes into play when you consider the work necessary to copy a sprite from your rom to video memory. In 2D mode you have to copy your bitmap tile by tile into video memory keeping track of the width if the video memory(32 tiles). This adds a separate step that can be a significant slowdown when you need to change out sprite graphics often. With 1D mode you can strait copy the data into video memory assuming your sprite is already broken down. This enables the easy use of DMA(direct memory access is a very fast memory copy done by hardware and will be discussed later)to copy sprite data into memory. I have found no real use for 2D mode and do not use it myself. I am sure there is a reason but I do not know what that could be. Here is a picture of 1D mode:
8x8 16-color Character name = 0
8x8 256-color Character name = 1
16x16 256-color Character name = 3
The 16x16 256-color sprite is arranged like this:
This means that a tile by tile copy of the sprite graphics into character memory is not interrupted by the checking for the width of character memory. In 2D you would have to load the first 4 tiles into memory and then recomputed the offset into character memory for the next row of tiles, but 1D always raps around.
Well that about sums up the memory and formats used by sprites now I suppose you want to draw a few.
Drawing your first sprite:
Well hold on just a sec! One thing I have not covered is how to get input from the keypad and since it is a very simple thing I will show you how here. A single register that is at the memory location 0x4000130 controls input. So we will define a 32bit variable called KEYS.
volatile u32* KEYS = (volatile u32*)0x4000130; //volatile just tells the c compiler that the variable is changed outside of your code.
To test for input you just test for the corresponding bits to be clear. So we will just define the bits and all you will have to do is AND them together to test for a key press.
To test for input you just test for the corresponding bits to be clear. So we will just define the bits and all you will have to do is AND them together to test for a key press.
* Keypad.h *
* by dovoto *
#define KEY_A 1
#define KEY_B 2
#define KEY_SELECT 4
#define KEY_START 8
#define KEY_RIGHT 16
#define KEY_LEFT 32
#define KEY_UP 64
#define KEY_DOWN 128
#define KEY_R 256
#define KEY_L 512
volatile u32* KEYS = (volatile u32*)0x04000130;
Now we can check for input by doing the following:
if(! ( (*KEYS) & KEY_A) )
//key 'A' is pressed so do stuff
the ! is because we are checking if the bit is clear and the * is because KEYS is a pointer and needs to be dereferenced.
Okay, now we know how to move sprites around but we still have not drawn any. First lets do a 2D one and then a 1D. Before we do anything we need to make a sprite.h that defines all these attribute bits that we have been talking about and an easy way to use them.
* Sprite.h *
#define ROTATION_FLAG 0x100
#define SIZE_DOUBLE 0x200
#define MODE_NORMAL 0x0
#define MODE_TRANSPERANT 0x400
#define MODE_WINDOWED 0x800
#define MOSAIC 0x1000
#define COLOR_16 0x0000
#define COLOR_256 0x2000
#define SQUARE 0x0
#define TALL 0x8000
#define WIDE 0x4000
#define ROTDATA(n) ((n) << 9)
#define HORIZONTAL_FLIP 0x1000
#define VERTICAL_FLIP 0x2000
#define SIZE_8 0x0
#define SIZE_16 0x4000
#define SIZE_32 0x8000
#define SIZE_64 0xC000
#define PRIORITY(n) ((n) << 10)
#define PALETTE(n) ((n) << 12)
typedef struct tagOAMEntry
typedef struct tagRotData
u16* OAM = (u16*)0x7000000; //the address of OAM
OAMEntry sprites; //My sprite array
pRotData rotData = (pRotData)sprites; //My rotation and scaling array
//and that's it. I know I should use a better naming scheme for my defines but I am to lazy
Now we need to turn on sprites (aka objects) in the REG_DISPCNT and enable 2D mapping. We will use 256-color mode for all sprite demos (mainly because I can not draw in 256 colors let alone 16). This demo relies on the code created earlier today and also that created in day 2. One thing that I forgot to mention is that the color zero will not be drawn weather it is a 256-color palette or a 16-color palette. This allows the parts of your sprite that you do not want drawn to be see through.
//using the setmode macro built in day 2
SetMode(OBJ_MAP_2D | MODE_3 | OBJ_ENABLE);
All right now we need some cool sprite graphics. We will use my trusty pcx2sprite program. Create a 64x64 256 color image in a paint program of your choice and use pcx2sprite on it. Name the file Sprite1.pcx. Just drag your new PCX file on top of the program and it will create a header file called sprite1.h.
Now just include the sprite1.h at the top of your program and we are almost ready to go.
first we will use sprite number 0 to hold all our sprite attributes.
u8 sprite1Atributes = 0; //zero is the first set of attributes.
now to access the sprites attributes just use the following
sprites[sprite1Atributes].attribute0 = (whatever);
//How about a 256 color sprite at an x,y of 10,10 that is 64x64 and starts at character tile zero.
s16 x = 10;
s16 y = 10;
u16 char_number = 0;
sprites[sprite1Atributes].attribute0 = 256_COLOR | SQUARE | y;
sprites[sprite1Atributes].attribute1 = SIZE_64 | x;
sprites[sprite1Atributes].attribute2 = char_number;
//All right our sprite is now set up and we need to copy the sprite data to character memory.
int x_loop = 0, y_loop = 0, index = 0;
#define CharMem ((u16*)0x6010000)
//allright now to copy in the sprites bitmap. The data in sprite1Data is allready striped so we just copy it in one row of tiles at a time. The only difference between this and a 1D sprite is that we would not need to keep track of the y offset. We could just use one big for loop. There are 8 rows (64x64 = 8x8 tiles) so we loop through all 8. There is 512 bytes per row (8 tiles * 8x8 = 512) but since we copy 2 bytes at a time that equals 256. The width of char memory is 32 * 8x8 = 1024 (512 double bytes).
for(y_loop = 0; y_loop < 8; y_loop++)
for(x_loop = 0; x_loop < 256; x_loop++)
CharMem[x_loop + y_loop * 512] = sprite1Data[index];
} //end for x_loop
} //end for y_loop
Now that is all you need to see a sprite appear on the screen. Now we need a function to move it.
void MoveSprite(OAMEntry* sp, int x, int y)
if(x < 0) //if it is off the left correct
x = 512 + x;
if(y < 0) //if off the top correct
y = 256 + y;
sp->attribute1 = sp->attribute1 & 0xFE00; //clear the old x value
sp->attribute1 = sp->attribute1 | x;
sp->attribute0 = sp->attribute0 & 0xFF00; //clear the old y value
sp->attribute0 = sp->attribute0 | y;
Now you know how to display one and how to move it. You must clear the old x and y to make sure all the x,y bits are zero before OR'ing in the new x,y;
Oh one thing I almost forgot! The palette! I believe that in an earlier day I mentioned that there are actual 2 palettes one for the background and one for the objects. To load the object palette is exactly the same except obj palette memory starts at 0x5000200 instead of 0x50000000.
#define OBJPaletteMem *(u16*)0x5000200
for(loop = 0; loop < 256; loop++)
OBJPaletteMem[loop] = sprite1Palette[loop];
//sprite1Palette comes from sprite1.h created by pcx2sprite program with a pcx file named sprite1.pcx.
Now the only thing left for 2D sprites is an annoying artifact that you would get with the code the way it is now. If you turn on sprites all of there x,y values are initialized at zero so you will always have an 8x8 (default shape and size) sprite displayed in the upper left hand screen that will have random data in it. There are two ways to turn off unused sprites the first is to set the x and y values off screen that way they are not drawn. The next is to turn off rotation and turn on the size_double flag. I am not sure why that works but it does, nor am I sure if there is any advantage to this. The only thing is that some emus may not support the later so I will use the first method for now.
for(loop = 0; loop < 128; loop++)
sprites[loop].attribute0 = 160; //y to > 159
sprites[loop].attribute1 = 240; //x to > 239
Just call that before turning on sprites and only the ones you want will be displayed.
Now for the demo. Get DEMO for the complete source and binary file. It allows you to move a single sprite around the screen with the keypad and demonstrates the horizontal and vertical flip flags.
All right, so now you know how to draw 64x64 2D mapped sprite and move it around. You are probably still a little unclear about that whole size/shape thing so here is that table I promised:
Use the size shape portions of attributes 0 and 1 to determine the size of your sprite.
To make a 1D sprite everything is identical except the way you load your data. You specify 1D mapping in REG_DISPCNT then you copy that data in and everything else is the same. You can use the same header created for the 2D demo.
Here is the source and binary for the 1D program DEMO you will notice it looks remarkably similar. :)
Now to the wonderful world of rotation and scaling.
Rotation and scaling.
Rotation and scaling is not that hard if you have a decent grasp of trig and even if you don't then it is not that hard as long as you don't mind cutting and pasting my code.
It is now time to finally explain this rotData stuff. There are four registers that control the rotation and scaling of sprites pa,pb,pc,pd. I am just going to give you the equations that work and let you figure out the trig (I figured it out by trial and error as my trig is not as good as I would like)
pa = x_scale * cos(angle);
pb = y_scale * sin(angle);
pc = x_scale * -sin(angle);
pd = y_scale * cos(angle);
and POOF the magic happens and your sprite rotates and scales. If you actually know any thing about trig and rotation you will notice that these equations are remarkably similar to a rotation matrix. Before we move on we must have ourselves a quick talk of fixed-point numbers and look up tables. First lets tackle fixed point.
Since ARM7TDMI does not support floating point numbers using them for anything that requires speed is not recommended. Floating point numbers are the way a computer represents numbers that have a decimal place. Since the hardware does not support them they must be emulated by software. This is just not acceptable for anything other than setup code or non-speed critical demos. That brings us to fixed point math. We cannot use integers for things like sine and cosine because they are always less than one. Many other calculations require more precision than we can get out of integers. Fixed point is very similar to floating point only, we do our own emulation. The advantage to fixed point is we can do it much faster. How you might ask? Well we cheat. We always keep the decimal place in the same spot. We just pick a place and stick with it. For example say we have a 16-bit integer; we will just state that the first 8 bits are integer and the next 8 bits are the decimal. It is that easy. To make a decimal number into fixed point we just multiply by 2^8 or we use the shift operator and shift the value to the left by 8 bits.
#define FIXED s16 //yep that's it
int x = 95;
FIXED fx = 0;
fx = x << 8;
fx = x * (1<<8); //1 << n = 2^n
To convert a floating point number you must use the 2^8 version as a shift does not work on them.
float x = 1.2424;
fx = (FIXED)(x * (1<<8));
The shift is much faster but since the only time you will use floats is in your setup code were speed does matter that is okay. To add or subtract two fixed numbers you do the same as for any other type and sign is preserved:
x = x + y; //works fine
y = x - y; //this too
Pretty cool huh? Well not so fast as multiplication and division are a bit different. If you remember from math class if you multiply two numbers with decimals then the decimal shifts the total number of places of both numbers. For example 2.21 * 2.21 = 4.8841. That causes a problem for us in that our decimal place is supposed to be fixed. If we just multiplied these two numbers we would end up with 488.41 (kind of) and that is not what we want. So after any multiply we must shift the number to the right by 8 and after a divide we must shift to the left. Well that creates an even bigger problem in that we loose 8 bits of our number if we shift by eight. This means that not only are we limited to 0-255 but also, if we plan on multiplying these two numbers, then we are limited to numbers that are less than 255 when multiplied together because we will loose anything greater than that. There are a couple of thing that can be done to avoid this. The easiest is to shift smartly. What I mean is that you can shift each number by 4 prior to the multiply or shift one by 8 prior depending on what will keep the most precision. For instance if you want to multiply 234.3 * .001234 then it would be best to shift the first number by 6 and the second by 2 then multiply and you will not loose any thing and your number will be in the correct format when you are done. The other option is to use assembly and although this is what most people do it is beyond the scope and really beyond my ability, as I don't do much that requires fixed point. Basically assembly allows you to use two register (64 bits) to hold the result of your multiplication that way you do not loose any data until you shift back down.
Allright, now for the reason for this whole discussion on fixed point math. The pa,pb,pc,pd are in fixed-point format with 8bits decimal and 8bits fraction.
void RotateSprite(int rotDataIndex, int angle, FIXED x_scale,FIXED y_scale)
pa = ((x_scale) * COS[angle])>>8; //(do my fixed point multiplies and shift back down)
pb = ((y_scale) * SIN[angle])>>8;
pc = ((x_scale) * -SIN[angle])>>8;
pd = ((y_scale) * COS[angle])>>8;
rotData[rotDataIndex].pa = pa; //put them in my data struct
rotData[rotDataIndex].pb = pb;
rotData[rotDataIndex].pc = pc;
rotData[rotDataIndex].pd = pd;
and that should do it. As scale increases the size of the sprite increases.
Okay, the last thing left is that COS[angle] SIN[angle] thing. Well actually calculating sine and cosine take forever so, to speed things up, at the beginning I create two 360-entry arrays that hold the precomputed sin and cosine values in fixed-point format. That is done by the following code:
//since the sign cosine functions take radians and we want degrees lets make a little macro to convert them
#define RADIAN(n) (((float)n)/(float)180*PI)
for(loop = 0; loop < 360; loop++)
SIN[loop] = (FIXED)(sin(RADIAN(loop)) * (1<<8));
COS[loop] = (FIXED)(cos(RADIAN(loop)) * (1<<8)); //sin and cos are computed and cast to fixed
Well except for a demo (of course) that is it for the rotation and scaling of sprites. Well here is your demo DEMO with source and binary. The only difference between this demo and the last is that now you can use the left and right keys to rotate your sprite and the a/b keys zoom in and out. Also you can enable the SIZE_DOUBLE flag with the start key and disable it with select. The SIZE_DOUBLE flag doubles the size of your sprite for rotation and scaling purposes. If disabled and your sprite scales or rotates past its 64x64 boundary it is clipped. This is very obvious if you look at the demo. When enabled your sprite can rotate and scale in an area of up to 128x128. The center of the sprite changes by half of its width when the flag is set or cleared. The upper left hand coordinate changing will cause your sprites apparent position to change unless you compensate for it. Look at the start select code in the demo to see how I compensate.
I have just a few more things to add before moving on to day 4. First one thing you will almost certainly run into is odd shaped sprites that do not fit into standard sizes. The way around this is to use multiple OAM entries for the same sprite (two sprites that move together to make up a larger sprite). Also there is the mosaic and transparency setting that I did not discus. Those will be covered when I talk about the same thing for backgrounds. That is all for day 3. Hope it helped. Tomorrow we will cover the tile background modes.If you Want to see it on hardware then you need the MBV2 cable or the Flash advance linker. There is a review in my tools section Or you can just click the link below and go get one :)
Day 2 | Day 4