10 SPRITES 10.1 INTRODUCTION Sprites are small objects that can be moved around the display without changing the background. The Amiga has eight DMA channels which allows us to use 8 Hardware Sprites. The advantages of using hardware sprites is that they can be moved around, animated etc without interfering with the main processor (CPU). They are therefore extremely fast to move, and also very easy to handle. Sprites can be used in many situations. If you make a game you can use the sprites as aliens, missiles, explosion etc. Even now, when you are reading this, a sprite is used. Intuition's Pointer is actually a sprite as described in chapter 2 WINDOWS. 10.2 LIMITATIONS Sprites are wonderful small things but there exist some limitations: 1. Sprites may only be up to 16 pixels wide. (There is no limit on how tall they may be.) (Sprites are always using low-resolution pixels. Even if you have a high-resolution screen, the sprites will only be in low resolution. This shows how independent sprites are.) 2. Each sprite can only use three colours + one "transparent" colour. 3. There are only eight hardware sprites. These limitations can be avoided: 1. You can place two or more sprites side by side in order to make the object wider than 16 pixels. 2. You can "connect" two sprites and boast the number of available colours from 3 to 15 plus one transparent. 3. It is possible to reuse each hardware sprite to "plop" out several sprites (VSprites) on the display. 10.3 COLOURS Each sprite may have three different colours plus one transparent colour. Sprite zero and one will use colour register 16-19, sprite two and three will use colour register 20-23 and so on: Sprite Colour Register ----------------------------------- 0 and 1 16 - 19 (16 transparent) 2 and 3 20 - 23 (20 -"- ) 4 and 5 24 - 27 (24 -"- ) 6 and 7 28 - 31 (28 -"- ) Two important thing to remember: 1. The sprites 0 and 1, 2 and 3, 4 and 5, 6 and 7, use the same colour registers. If you change colour register 17, both sprite zero and one will be affected. 2. If you have a low-resolution screen with a depth of 5 (32 colours), the last 16 colours will be shared between the sprites and the screen. However, if you only use a 16 coloured screen (depth 4) you still use the top sixteen colour registers for the sprites. That means you can have a 16 coloured screen, and still use another 16 colours for the sprites. Colour register 16, 20, 24 and 28 are "transparent" which means that the background colour of the screen will shine through. Those registers can have any kind of colours since they will not affect the sprites. 10.4 ACCESS HARDWARE SPRITES If you want to use a hardware sprite you need to: 1. Declare and initialize some sprite graphics data. 2. Declare and initialize a SimpleSprite structure. 3. Call the function GetSprite() with a pointer to your SimpleSprite structure, plus a request for which sprite you want, as parameters. 4. Move the sprite around by calling the function MoveSprite() and animate it by changing the sprite graphics (ChangeSprite()). 5. Return the sprite to the hardware when you do not need it anymore, by calling the function FreeSprite(). 10.4.1 SPRITE DATA We have already described how to create your own sprite data in chapter 2 WINDOWS, but here is a short summary: [A] The first thing you need to do is to draw on a paper how the sprite should look like. Remember that the sprite may only be 16 pixels wide, but any height. (You can of course put two sprites beside each other if you need a sprite which is wider than 16 pixels). Remember also that you may only use three colours/sprite (described above). Imagine that you have come up with a suggestion like this: 0000000110000000 0: Transparent 0000001111000000 1: Red 0000011111100000 2: Yellow 0000111111110000 3: Green 0001111111111000 0011111111111100 0111111111111110 2222222222222222 2222222222222222 0333333333333330 0033333333333300 0003333333333000 0000333333330000 0000033333300000 0000003333000000 0000000330000000 [B] You now need to translate this into Sprite Data. Each line of the graphics will be translated into two words of data. The first word represents the first Bitplane, and the second word the second Bitplane. The idea is that if you want colour 0 both Bitplane zero and one should be 0, if you want colour 1 Bitplane zero should be 1 and Bitplane one 0 and so on: Colour Bitplane One Bitplane Zero Since ---------------------------------------------------------- 0 0 0 Binary 00 = Colour 0 1 0 1 " 01 = " 1 2 1 0 " 10 = " 2 3 1 1 " 11 = " 3 The data for the pointer would then look like this: Bitplane ZERO Bitplane ONE 0000000110000000 0000000000000000 0000001111000000 0000000000000000 0000011111100000 0000000000000000 0000111111110000 0000000000000000 0001111111111000 0000000000000000 0011111111111100 0000000000000000 0111111111111110 0000000000000000 0000000000000000 1111111111111111 0000000000000000 1111111111111111 0111111111111110 0111111111111110 0011111111111100 0011111111111100 0001111111111000 0001111111111000 0000111111110000 0000111111110000 0000011111100000 0000011111100000 0000001111000000 0000001111000000 0000000110000000 0000000110000000 [C] The last step is to translate the binary numbers to type UWORD. Group the binary number in four and translate it to Hexadecimal: Binary Hexadecimal ------------------- 0000 = 0 0001 = 1 0010 = 2 0011 = 3 0100 = 4 0101 = 5 0110 = 6 0111 = 7 1000 = 8 1001 = 9 1010 = A 1011 = B 1100 = C 1101 = D 1110 = E 1111 = F The result will look like this: ONE: TWO: ------------ 0180 0000 0000 0001 1000 0000 0000 0000 0000 0000 03C0 0000 0000 0011 1100 0000 0000 0000 0000 0000 07E0 0000 0000 0111 1110 0000 0000 0000 0000 0000 0FF0 0000 0000 1111 1111 0000 0000 0000 0000 0000 1FF8 0000 0001 1111 1111 1000 0000 0000 0000 0000 3FFC 0000 0011 1111 1111 1100 0000 0000 0000 0000 7FFE 0000 0111 1111 1111 1110 0000 0000 0000 0000 0000 FFFF 0000 0000 0000 0000 1111 1111 1111 1111 0000 FFFF 0000 0000 0000 0000 1111 1111 1111 1111 7FFE 7FFE 0111 1111 1111 1110 0111 1111 1111 1110 3FFC 3FFC 0011 1111 1111 1100 0011 1111 1111 1100 1FF8 1FF8 0001 1111 1111 1000 0001 1111 1111 1000 0FF0 0FF0 0000 1111 1111 0000 0000 1111 1111 0000 07E0 07E0 0000 0111 1110 0000 0000 0111 1110 0000 03C0 03C0 0000 0011 1100 0000 0000 0011 1100 0000 0180 0180 0000 0001 1000 0000 0000 0001 1000 0000 [D] Since the Amiga need to store the position of the sprite, the size etc, you should also declare two empty words at the top, and to empty words at the bottom of the Sprite data. These words will be initialized and maintained by Intuition, so you do not need to bother about them. A declaration and initialization of the sprite data would therefore be: UWORD chip my_sprite_data[36]= { 0x0000, 0x0000, 0x0180, 0x0000, 0x03C0, 0x0000, 0x07E0, 0x0000, 0x0FF0, 0x0000, 0x1FF8, 0x0000, 0x3FFC, 0x0000, 0x7FFE, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x7FFE, 0x7FFE, 0x3FFC, 0x3FFC, 0x1FF8, 0x1FF8, 0x0FF0, 0x0FF0, 0x07E0, 0x07E0, 0x03C0, 0x03C0, 0x0180, 0x0180, 0x0000, 0x0000 }; IMPORTANT! Remember that all image data must as (always!) be loaded into the Chip memory (the lowest 512k of RAM). If you use Lattice C V5.00 or higher you can use the keyword chip in front of the image name. Check your compiler manual. 10.4.2 SIMPLESPRITE STRUCTURE The SimpleSprite structure look like this: struct SimpleSprite { UWORD *posctldata; UWORD height; UWORD x, y; UWORD num; }; posctldata: Pointer to an array of UWORDs which is used for the graphics of the sprite. height: Height of the sprite (lines). (Hardware Sprites are always 16 pixels wide.) x, y: Position on the display relative to the ViewPort/ View (Display). num: Sprite number (0-7). When you call the GetSprite() function it will automatically initialize this field with the reserved sprite number, so set it to -1 for the moment. 10.4.3 RESERVE A SPRITE Before you may use a hardware sprite you must have reserved it. (Since the Amiga is multitasking it can happen that another program is using the sprite.) You reserve a sprite by calling the function GetSprite(): Synopsis: sprite_got = GetSprite(my_sprite, sprite_wanted); sprite_got: (long) GetSprite() returns the number of the sprite you got (0-7). If it could not get the desired sprite, it returns -1. Remember to check if you got the sprite you wanted! (the SimpleSprite structure's num field will also be initialized automatically.) my_sprite: (struct SimpleSprite *) Pointer to your SimpleSprite structure. sprite_wanted: (long) The number of the hardware sprite you want to use (0-7). If it does not matter which sprite you get you can write -1. (The System will then give you any free hardware sprite.) 10.4.4 PLAY WITH THE SPRITE Once you have reserved a sprite you may start to play around with it. You move the sprite by calling the function MoveSprite(): Synopsis: MoveSprite( view_port, my_sprite, x, y ); view_port: (struct ViewPort *) Pointer to the ViewPort which this sprite is connected to, or 0 if the sprite should be connected to the current View. my_sprite: (struct SimpleSprite *) Pointer to your SimpleSprite structure. x, y: (long) The new position on the display. (Sprites use low-resolution pixels.) For example, MoveSprite( 0, &my_sprite, 30, 50 ); moves the sprite (my_sprite) to position (30, 50). (Relative to the current View.) You can also change the image (sprite data) of the sprite in order to animate it. You simply call the function ChangeSprite() with a pointer to a new sprite data as a parameter, and the rest is done for you. Synopsis: ChangeSprite( view_port, my_sprite, new_data ); view_port: (struct ViewPort *) Pointer to the ViewPort which this sprite is connected to, or 0 if the sprite is connected to the current View. my_sprite: (struct SimpleSprite *) Pointer to your SimpleSprite structure. new_data: (short *) Pointer to the new sprite data. 10.4.5 FREE THE SPRITE When you do not need the sprite any more (when your program quits for example), you need to free the sprite so another task can use it if necessary. Important! You must free all sprites you have allocated, if not no other tasks can use them, and the only way to free the hardware is then to reset the machine. You free a sprite by calling the function FreeSprite(): Synopsis: FreeSprite( sprite_got ); sprite_got: (long) The sprite you want to free (0-7). 10.4.6 PROGRAM STRUCTURE A program using sprites would look something like this: /* Since we are using sprites we need to include this file: */ #include /* 1. Declare and initialize some sprite graphics data: */ UWORD chip my_sprite_data[36]= { 0x0000, 0x0000, 0x0180, 0x0000, 0x03C0, 0x0000, 0x07E0, 0x0000, 0x0FF0, 0x0000, 0x1FF8, 0x0000, 0x3FFC, 0x0000, 0x7FFE, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x7FFE, 0x7FFE, 0x3FFC, 0x3FFC, 0x1FF8, 0x1FF8, 0x0FF0, 0x0FF0, 0x07E0, 0x07E0, 0x03C0, 0x03C0, 0x0180, 0x0180, 0x0000, 0x0000 }; /* 2. Declare and initialize a SimpleSprite structure: */ struct SimpleSprite my_sprite= { my_sprite_data, /* posctldata, pointer to the sprite data. */ 16, /* height, 16 lines tall. */ 40, 20, /* x, y, position on the screen. */ -1, /* num, this field is automatically */ /* initialized when we call the */ /* GetSprite() function, so we set it */ /* to -1 for the moment. */ }; UWORD chip new_sprite_data[26]= { 0x0000, 0x0000, and so on... main() { /* Open the Graphics library etc... */ /* 3. Call the function GetSprite() with a pointer to */ /* your SimpleSprite structure, plus a request for */ /* which sprite you want, as parameters. Try to */ /* reserve sprite number 2: */ if( GetSprite( &my_sprite, 2 ) != 2 ) { /* ERROR! */ /* Could not reserve sprite number 2. */ } /* 4. Move the sprite around by calling the function */ /* MoveSprite() and animate it by changing the */ /* sprite graphics by calling the function */ /* ChangeSprite(). */ /* Move the sprite to position (20, 30) MoveSprite( 0, &my_sprite, 20, 30 ); /* Change the sprite data: */ ChangeSprite( 0, &my_sprite, new_sprite_data ); /* 5. Return the sprite to the hardware when you do not need it */ /* anymore, by calling the function FreeSprite(): */ FreeSprite( my_sprite.num ); /* Close other things etc... */ } 10.5 TECHNIQUES Since both moving and changing the sprite is done by special hardware it goes with lightning speed without the main CPU even knowing about it. If you can you should use the sprites since the hardware is specialized in handling them. However, there are, as mentioned before, some limitations which can cause some problems, but by using some special techniques you can manage almost anything. Sprites can also be used to create some interesting special effects, and are perfect to animate. 10.5.1 WIDER SPRITES If you want a space ship top be 32 pixels wide you can use two sprites side by side in order to get the desired effects. Every time you move the ship you call the MoveSprite() function twice. Since sprites are moved so fast the user will never realize that the ship is built up of two sprites. (30,50) (30+16, 50) *--------------*---------------- | ####|#### | | # #|# ## | | #############|############## | |##############|###############| | #############|############## | -------------------------------- Sprite 2 Sprite 3 If you use all eight sprites you can get an image which is 128 pixels wide and any height. I do not think you need a bigger ship than that. 10.5.2 MORE COLOURS Each sprite may only use three colours plus transparent. However, it is possible to connect two sprites and be able to use 15 colours plus transparent. (Since each sprite use two Bitplanes, it means that an attached sprite has four Bitplanes which gives 16 combinations = 15 colours + 1 transparent.) The sprites may be attached as follows: 1 to 0 (Set the SPRITE_ATTACHED bit in Sprite 1's sprite data) 3 to 2 ( - " - 3 - " - ) 5 to 4 ( - " - 5 - " - ) 7 to 6 ( - " - 7 - " - ) The even sprites (0, 2, 4 and 6) are called Bottom Sprites, while the odd sprites (1, 3, 5 and 7) are called Top Sprites. When you want to use an attached sprite you need to: 1. Declare and initialize two sprite data. 2. Set the Attach bit (on the odd sprite 1, 3, 5 or 7) in the second word of the sprite data. 3. Once you have reserved both of the sprites, GetSprite(), they can be moved around. It is important that both sprites are moved together so the Attach mode works, otherwise they will become three coloured sprites again. 10.5.2.1 15 COLOURED SPRITE DATA As you have seen before, the sprite data is built up of two bitplanes. When we attach two sprites we consequently get four Bitplanes, and therefore 15 colours plus transparent. The Bottom Sprite contributes with Bitplane zero and one, while the Top Sprite contributes with Bitplane two and three. So if you want the sprite to look like this: 0000000000000000 0: Colour register 16 (Transparent) 1111111111111111 1: - " - 17 2222222222222222 2: - " - 18 3333333333333333 3: - " - 19 4444444444444444 4: - " - 20 5555555555555555 5: - " - 21 6666666666666666 6: - " - 22 7777777777777777 7: - " - 23 8888888888888888 8: - " - 24 9999999999999999 9: - " - 25 AAAAAAAAAAAAAAAA A: - " - 26 BBBBBBBBBBBBBBBB B: - " - 27 CCCCCCCCCCCCCCCC C: - " - 28 DDDDDDDDDDDDDDDD D: - " - 29 EEEEEEEEEEEEEEEE E: - " - 30 FFFFFFFFFFFFFFFF F: - " - 31 The four Bitplanes should be like this: Bitplane: THREE TWO ONE ZERO ------------------------------------------------------------------- 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 1111111111111111 0000000000000000 0000000000000000 1111111111111111 0000000000000000 0000000000000000 0000000000000000 1111111111111111 1111111111111111 0000000000000000 1111111111111111 0000000000000000 0000000000000000 0000000000000000 1111111111111111 0000000000000000 1111111111111111 0000000000000000 1111111111111111 1111111111111111 0000000000000000 0000000000000000 1111111111111111 1111111111111111 1111111111111111 1111111111111111 0000000000000000 0000000000000000 0000000000000000 1111111111111111 0000000000000000 0000000000000000 1111111111111111 1111111111111111 0000000000000000 1111111111111111 0000000000000000 1111111111111111 0000000000000000 1111111111111111 1111111111111111 1111111111111111 1111111111111111 0000000000000000 0000000000000000 1111111111111111 1111111111111111 0000000000000000 1111111111111111 1111111111111111 1111111111111111 1111111111111111 0000000000000000 1111111111111111 1111111111111111 1111111111111111 1111111111111111 Translated into hexadecimal it would be: 3 2 1 0 ------------------- 0000 0000 0000 0000 0000 0000 0000 FFFF 0000 0000 FFFF 0000 0000 0000 FFFF FFFF 0000 FFFF 0000 0000 0000 FFFF 0000 FFFF 0000 FFFF FFFF 0000 0000 FFFF FFFF FFFF FFFF 0000 0000 0000 FFFF 0000 0000 FFFF FFFF 0000 FFFF 0000 FFFF 0000 FFFF FFFF FFFF FFFF 0000 0000 FFFF FFFF 0000 FFFF FFFF FFFF FFFF 0000 FFFF FFFF FFFF FFFF Now we only need to put this into the two sprite data. Remember that the Bottom Sprite (0, 2, 4 or 6) contributes with Bitplanes zero and one, while the Top Sprites (1, 3, 5 or 7) contributes with Bitplanes two and three: /* Sprite Data for the Bottom Sprite: */ UWORD chip bottom_sprite_data[36]= { 0x0000, 0x0000, /* Bitplane */ /* ZERO ONE */ 0x0000, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000 }; /* Sprite Data for the Top Sprite: */ UWORD chip top_sprite_data[36]= { 0x0000, 0x0000, /* Bitplane */ /* TWO THREE */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000 }; 10.5.2.2 ATTACH SPRITES To connect two sprites you simply set the SPRITE_ATTACHED flag in the second word of the sprite data of the Top Sprite. So if you want to connect sprite 3 with sprite 2, you should set the SPRITE_ATTACHED flag in sprite 3's sprite data. top_sprite_data[ 1 ] = SPRITE_ATTACHED; or UWORD chip top_sprite_data[36]= { 0x0000, SPRITE_ATTACHED, and so on... 10.5.2.3 MOVE ATTACHED SPRITES Once two sprites are attached you simply reserve them, and you have got one 15 coloured sprite: /* Reserve sprite 2 as Bottom Sprite: */ bottom_sprite_got = GetSprite( &my_bottom_sprite, 2 ); if( bottom_sprite_got != 2 ) /* ERROR! */ /* Reserve sprite 3 as Top Sprite: */ top_sprite_got = GetSprite( &my_top_sprite, 3 ); if( top_sprite_got != 3 ) /* ERROR! */ It is important that both of the sprites are moved simultaneously so the Attach function will work. The nice thing is that if you move the Bottom sprite, the Top sprite will also move automatically. If you on the other hand move the Top sprite only, the Bottom sprite will remain unchanged, and the special Attach function will no work. (Back to 3 colours.) If you want to have some fun you can reserve sprite 1 and set the SPRITE_ATTACHED flag. Since sprite 0 is already used by Intuition as the pointer, sprite 1 would be attached to the pointer, and you could have a 15 coloured pointer! (Whenever Intuition moves the pointer (sprite 0, which is the Bottom sprite), your sprite (sprite 1, Top Sprite) will move automatically.) (You can change Intuition's pointer by calling the function SetPointer. See chapter 2 WINDOWS for more information.) 10.5.3 LEVELS Each sprite has its own "level" (priority) which means that some sprites will move over, and some under other sprites when they touch each other. The higher sprite number the lower level (priority). That means that sprite 3 will move over sprite 4, while sprite 2 will move over sprite 3: -------- | 7 | | -------- ----| 6 | | -------- ----| 5 | | -------- ----| 4 | | -------- ----| 3 | | -------- ----| 2 | | -------- ----| 1 | | -------- ----| 0 | | | -------- This can be used in many ways. For example, if you are writing a game with a small man jumping into a box, you can use two sprites. One for the man, and one for the box. The important thing to remember is that the box must have a lower sprite number than the man. If you then would place the man at the same position as the box, the man would appear to go inside the box: O ----- | ^ ####### / \ ####### Man Box (Sprite 2) (Sprite 1) O ----- | ####### ####### Man (Sprite 2) Box (Sprite 1) 10.6 FUNCTIONS GetSprite() This function reserves a sprite. You must always reserve a sprite before you may use it. Synopsis: spr_got = GetSprite( my_sprite, spr_wanted ); spr_got: (long) GetSprite() returns the number of the sprite you got (0-7). If it could not get the desired sprite, it returns -1. Remember to check if you got the sprite you wanted! (the SimpleSprite structure's num field will also be initialized automatically.) my_sprite: (struct SimpleSprite *) Pointer to your SimpleSprite structure. spr_wanted: (long) The number of the hardware sprite you want to use (0-7). If it does not matter which sprite you get you can write -1. (The System will then give you any free hardware sprite) MoveSprite() Use this function to move a sprite. Synopsis: MoveSprite( view_port, my_sprite, x, y ); view_port: (struct ViewPort *) Pointer to the ViewPort which this sprite is connected to, or 0 if the sprite is connected to the current View. my_sprite: (struct SimpleSprite *) Pointer to your SimpleSprite structure. x, y: (long) The new position on the display. (Sprites use low-resolution pixels.) ChangeSprite() This function changes the sprite data (image) of a sprite. Synopsis: ChangeSprite( view_port, my_sprite, new_data ); view_port: (struct ViewPort *) Pointer to the ViewPort which this sprite is connected to, or 0 if the sprite is connected to the current View. my_sprite: (struct SimpleSprite *) Pointer to your SimpleSprite structure. new_data: (short *) Pointer to the new sprite data. FreeSprite() This function returns an already reserved sprite. Synopsis: FreeSprite( sprite_got ); sprite_got: (long) The sprite you want to free (0-7). WaitTOF() This function waits for the video beam to reach the top of the display. Can be used if you want to slow down the speed a bit, and make the animation smoother. Synopsis: WaitTOF(); 10.7 EXAMPLES Example1 This program shows how to declare and initialize some sprite data and a SimpleSprite structure. It also shows how to reserve a sprite (sprite 2), and how to move it around. The user moves the sprite by pressing the arrow keys. Example2 This program shows how to declare and initialize some sprite data and a SimpleSprite structure. It also shows how to reserve a sprite (sprite 2), and how to move it around. The user moves the sprite by pressing the arrow keys. In this example we animate the sprite (6 frames, taken from the arcade game Miniblast). Example3 This program shows how to set up a 15 coloured sprite, and how to move it around.