Sprite Techniques: Difference between revisions

From Uzebox Wiki
Jump to navigation Jump to search
mNo edit summary
mNo edit summary
Line 2: Line 2:




This tutorial comes about from some questions asked by forum member Vertig0zone. Seems to me there are a few ideas floating around on how to make the most of your sprites, but not a lot of explanation. None of these ideas are new or very complicated, hopefully I explain them well enough. Any areas I am missing please PM me so this can be more complete, you will be personally credited for contributions.
This tutorial comes about from some questions asked by forum member Vertig0zone. Seems to me there are a few ideas floating around on how to make the most of your sprites, but not a lot of explanation in one place. None of these ideas are new or terribly complicated, hopefully I explain them well enough. Any areas I am missing please PM me so this can be more complete, you will be personally credited for contributions.




Line 15: Line 15:


*Mega Sprite - a "mega sprite" is a collection of individual sprites, often times directly side by side and above/below. As an example, the Mario/Jump Man sprite in [[Donkey_Kong]] is a mega sprite comprised of 4 sprites arranged in a square. We would call this a 2x2 (2 sprites high/wide) or a 16x16(actual pixels) mega sprite. This is a common size because going too much bigger gets very expensive when moving. By the way, I think it was Uze who coined this and it's a standard term by now.
*Mega Sprite - a "mega sprite" is a collection of individual sprites, often times directly side by side and above/below. As an example, the Mario/Jump Man sprite in [[Donkey_Kong]] is a mega sprite comprised of 4 sprites arranged in a square. We would call this a 2x2 (2 sprites high/wide) or a 16x16(actual pixels) mega sprite. This is a common size because going too much bigger gets very expensive when moving. By the way, I think it was Uze who coined this and it's a standard term by now.
*Sprite overflow/rotation/flickering are all terms in regard to having too few free ram tiles to draw a scene in it's entirety.




Line 49: Line 51:
Ok, since this is a rant, I'll give another example based on Vertig0zone's game idea. Let's consider a fighting game like Street Fighter 2,3,alpha,whatever. In the early design process you'll see there is no way to use that big of sprites. So after shrinking them down you'll notice some frames of the mega sprites are wider or taller than others. In situations where both fighters were using big frames and perhaps throwing fireballs, you might not have enough ram tiles. So here again we'll prioritize the issues. The fireballs can hurt a player, but they follow a straight line so there is no chance that their position will vary much each frame. Also we get the free shimmer effect mentioned before, and it really doesn't look bad for a fireball(check out SF2 in slow-mo, you maybe never noticed). Right there we've helped out the worst case scenario, but we could still be in trouble considering we do have to draw the fireballs at some point. So in the case that it is inevitable to take sprites from the player, we need to decide which sprites would be less jarring to take away. Since the biggest frames are probably attacking frames, there will probably be some sprites that have more transparent pixels. Perhaps a non attacking foot, the edge of a knee, whatever you decide make a sprite important. They are only adding a little bit to the final image, and therefore are the least jarring sprites to skip.  
Ok, since this is a rant, I'll give another example based on Vertig0zone's game idea. Let's consider a fighting game like Street Fighter 2,3,alpha,whatever. In the early design process you'll see there is no way to use that big of sprites. So after shrinking them down you'll notice some frames of the mega sprites are wider or taller than others. In situations where both fighters were using big frames and perhaps throwing fireballs, you might not have enough ram tiles. So here again we'll prioritize the issues. The fireballs can hurt a player, but they follow a straight line so there is no chance that their position will vary much each frame. Also we get the free shimmer effect mentioned before, and it really doesn't look bad for a fireball(check out SF2 in slow-mo, you maybe never noticed). Right there we've helped out the worst case scenario, but we could still be in trouble considering we do have to draw the fireballs at some point. So in the case that it is inevitable to take sprites from the player, we need to decide which sprites would be less jarring to take away. Since the biggest frames are probably attacking frames, there will probably be some sprites that have more transparent pixels. Perhaps a non attacking foot, the edge of a knee, whatever you decide make a sprite important. They are only adding a little bit to the final image, and therefore are the least jarring sprites to skip.  


Building on this concept, how can our game know which are the least important sprites? This is why I recommended building your own sprite setup. You can add a table to store priorities for each sprite in a mega sprite frame. Although it will require some extra work and space, this gives you the best possible fine tuning to make your game look the best it can under any situation. Once you have setup your rendering to handle all this, you can add new frames for objects without recoding anything simply by adding the frames attributes to your tables.
Building on this concept, how can our game know which are the least important sprites? This is why I recommended building your own sprite setup. You can add a table to store priorities for each sprite in a mega sprite frame. Although it will require some extra work and space, this gives you the best possible fine tuning to make your game look the best it can under any situation. Once you have setup your rendering to handle all this, you can add new frames for objects without recoding anything simply by adding the frames attributes to your tables. Finally to make this all useful, we'll need a way to know how many ram tiles will be required for our scene versus what is available. This is easily accomplished though does take time. All you'll need is to run through each object that is to be drawn while taking into consideration it's width,height, and 8 pixel alignment. Using that to calculate how much we will "overflow". Depending on how you declared your priorities specific to your game, you might choose not to draw sprite below some calculated threshold based on the severity of the relative overflow. Hope that makes sense, I don't have any nice looking code to show you yet.  




...SOME CODE ON THIS IS COMING




Line 66: Line 67:




The mega sprite in the above picture is 16x32 pixels or 2x8 tiles. In this example you'll see on the left a whole vertical column is filled with transparency. This adds nothing to the screen, but still takes extra ram. On the right side you'll see we have shifted have of the object to the left into that transparent part, essentially making this 15x32. Now when we go to draw it, we simply move it over by one pixel to line it back up. Although this doesn't seem like much in this example, now we can offset it by 1 from an 8 pixel boundary and still not need extra ram tiles(since it still fits in the 16 wide grid, think about it). Other graphics could see an even bigger improvement, this stuff does adds up!
The mega sprite in the above picture is 16x32 pixels or 2x8 tiles. In this example you'll see on the left a whole vertical column is filled with transparency. This adds nothing to the screen, but still takes extra ram. On the right side you'll see we have shifted have of the object to the left into that transparent part, essentially making this 15x32. Now when we go to draw it, we simply move it over by one pixel to line it back up. Although this doesn't seem like much in this example, now we can offset it by 1 from an 8 pixel boundary and still not need extra ram tiles(since it still fits in the 16 wide grid, think about it). Other graphics could see an even bigger improvement, this stuff does add up!




Line 85: Line 86:




Pretty big advantages if your game can implement this well. On the default setup of 28 rows with complicated game logic, you will often run out of time before you utilize and blit the available ram tiles. So this is probably the more important part of it, as it would allow a robust rotation system enough time to run. If you already have your game drawing to the screen, you will now need to make sure that it doesn't draw outside of the ''new'' vram window. So in the above example, SetTile(0,26,tileIndex) would write into memory that is no longer vram which will cause problems. Below is an example of this technique in action in an early game prototype well suited to it. It uses 24 rows and 31 ram tiles with sound channel 4 turned off(tons of time still available for extra gameplay logic). The mega sprites are 3x3 and sprite flickering is barely an issue.
Pretty big advantages if your game can implement this well. On the default setup of 28 rows with complicated game logic, you will often run out of time before you utilize and blit the available ram tiles. So this is probably the more important part of it, as it would allow a robust rotation system enough time to run. If you already have your game drawing to the screen, you will now need to make sure that it doesn't draw outside of the ''new'' vram window. So in the above example, SetTile(0,26,tileIndex) would write into memory that is no longer vram which will cause problems. Below is an example of this technique in action in an early game prototype well suited to it. It uses 24 rows and 31 ram tiles with sound channel 4 turned off(tons of time still available for extra gameplay logic). The mega sprites are 3x3 and sprite flickering is barely an issue, the extra time allows a good sprite rotation system(accounting for y-ordering also).




Line 95: Line 96:




//TODO MORE TO COME
Feel free to ask any questions, and I will probably add them to this tutorial.

Revision as of 06:44, 26 March 2010

Sprite Techniques

This tutorial comes about from some questions asked by forum member Vertig0zone. Seems to me there are a few ideas floating around on how to make the most of your sprites, but not a lot of explanation in one place. None of these ideas are new or terribly complicated, hopefully I explain them well enough. Any areas I am missing please PM me so this can be more complete, you will be personally credited for contributions.


Terms

Just to set things straight I'll define terms you'll find in this document. I don't mean to repeat things that are already documented (and that you should have read before hand :P Video_Modes) in several places, so I'll keep them brief.


  • Sprite - Just to be concise, for this document we will consider sprites to be an 8x8(6x8 for mode 2) entity that we can assign any pixels to. This object is different from the standard tile because it can be place at any (x,y) offset. In Mode 3 a sprite is NOT a ram tile. The sprite is a structure that we can assign a position, graphics tile, and horizontal flipping. The kernel then uses as many ram tiles as the single sprite requires (from 1-4) based on it's position. Assigning in your make file, more sprites than than the ram tiles they will require is worthless and wastes ram.
  • Mega Sprite - a "mega sprite" is a collection of individual sprites, often times directly side by side and above/below. As an example, the Mario/Jump Man sprite in Donkey_Kong is a mega sprite comprised of 4 sprites arranged in a square. We would call this a 2x2 (2 sprites high/wide) or a 16x16(actual pixels) mega sprite. This is a common size because going too much bigger gets very expensive when moving. By the way, I think it was Uze who coined this and it's a standard term by now.
  • Sprite overflow/rotation/flickering are all terms in regard to having too few free ram tiles to draw a scene in it's entirety.


DK MegaSprite.png


Initial Design

This is the first thing you'll want to do, but to be confusing you'll need to understand all the following concepts before you start. What you'll want to do is visualize your game. If you are remaking an existing game I suggest you play it for a good while, with an eye on it's design. The easiest games are the ones who have some of these attributes:


  • Smaller mega-sprites (16x16 is pretty reasonable)
  • Sprites that are often aligned on one or both axis ie (8,64),(16,24)
  • Objects that could be "flickered" without being disastrous, that is, some objects are clearly more important than others.
  • Games that wont require a lot of variables. Lets you have more free ram for extra ram tiles.


Another important thing, in my opinion, is to roll your own sprite routines. The kernel provides good functionality for basic mega sprites, but it is setup for generic cases that might not match your specific needs.


Sprite Rotation

This technique is also referred to as flickering, and is currently used in such games as Donkey_Kong(barely noticeable during play) and Adventures_Of_Lolo. Basically what you want to do is "gain" extra sprites by "time-sharing" them. To do this, you'll draw some sprites one frame and other sprites on another. Personally I think this is the way to go considering the limited ram and the desire to make graphically impressive games. It does cause some noticeable "irritants" to the display, but this can be minimized by designing your system intelligently based on whats most important for your game. It does allow you to display extensively more sprites with very reasonable results.

As example, imagine the NES game Contra. If you were going to design a simple sprite rotation system for this, you'd first want to see what the most common sprite is under a situation that requires flickering. Likely it would be the player's projectiles because of shotguns/etc, and conveniently they are also not nearly as important as anything else. During play you want to know where your character is, where the enemy is, and what they're shooting at you. Your own shots are no danger to you, they are fast, and you know where they are going anyways. In a situation like this I would draw projectiles flickering on and off all the time irregardless, gives them an interesting "shimmer", and when rotation is needed it doesn't look much different anyways. Now all you have to worry about is too many enemies on the screen. Likely you'll want all enemies to be drawn somewhat, but you would probably determine that enemies who are closest to you are the most important and are drawn for longer periods of time.

Ok, since this is a rant, I'll give another example based on Vertig0zone's game idea. Let's consider a fighting game like Street Fighter 2,3,alpha,whatever. In the early design process you'll see there is no way to use that big of sprites. So after shrinking them down you'll notice some frames of the mega sprites are wider or taller than others. In situations where both fighters were using big frames and perhaps throwing fireballs, you might not have enough ram tiles. So here again we'll prioritize the issues. The fireballs can hurt a player, but they follow a straight line so there is no chance that their position will vary much each frame. Also we get the free shimmer effect mentioned before, and it really doesn't look bad for a fireball(check out SF2 in slow-mo, you maybe never noticed). Right there we've helped out the worst case scenario, but we could still be in trouble considering we do have to draw the fireballs at some point. So in the case that it is inevitable to take sprites from the player, we need to decide which sprites would be less jarring to take away. Since the biggest frames are probably attacking frames, there will probably be some sprites that have more transparent pixels. Perhaps a non attacking foot, the edge of a knee, whatever you decide make a sprite important. They are only adding a little bit to the final image, and therefore are the least jarring sprites to skip.

Building on this concept, how can our game know which are the least important sprites? This is why I recommended building your own sprite setup. You can add a table to store priorities for each sprite in a mega sprite frame. Although it will require some extra work and space, this gives you the best possible fine tuning to make your game look the best it can under any situation. Once you have setup your rendering to handle all this, you can add new frames for objects without recoding anything simply by adding the frames attributes to your tables. Finally to make this all useful, we'll need a way to know how many ram tiles will be required for our scene versus what is available. This is easily accomplished though does take time. All you'll need is to run through each object that is to be drawn while taking into consideration it's width,height, and 8 pixel alignment. Using that to calculate how much we will "overflow". Depending on how you declared your priorities specific to your game, you might choose not to draw sprite below some calculated threshold based on the severity of the relative overflow. Hope that makes sense, I don't have any nice looking code to show you yet.



Aggregated Sprites

  • I think this is what is meant by aggregated sprites, I'll make sure and correct this otherwise.

Another concept by Uze, this is the process of overlapping sprites that are part of the same mega sprite. This is only useful for a mega sprite that does not occupy all the pixels of it's individual sprites. You know that a sprite overlapping even 1 pixel over the 8 pixel boundaries requires extra ram tiles, and with this technique we will minimize this situation as much as possible. Because a sprite overlapping another is blit on top of the SAME ram tile, we can avoid using an extra ram tile in more situations since the edge will not overlap another boundary. Of course we will have to setup our graphics with this in mind, really the only issue that requires much work. This might be hard to understand in words, let's look at a picture.


AggregatedSprites.PNG


The mega sprite in the above picture is 16x32 pixels or 2x8 tiles. In this example you'll see on the left a whole vertical column is filled with transparency. This adds nothing to the screen, but still takes extra ram. On the right side you'll see we have shifted have of the object to the left into that transparent part, essentially making this 15x32. Now when we go to draw it, we simply move it over by one pixel to line it back up. Although this doesn't seem like much in this example, now we can offset it by 1 from an 8 pixel boundary and still not need extra ram tiles(since it still fits in the 16 wide grid, think about it). Other graphics could see an even bigger improvement, this stuff does add up!


Letter Box

This is documented in Ram_Tile_Effects_Primer (and of course brought up by Uze originally) but I will cover it anyways. First off, you can only do this if you aren't using scrolling(TODO - does this work in Mode 2?). This is how you set it up:

KERNEL_OPTIONS += -DSCREEN_TILES_V=26 -DFIRST_RENDER_LINE=28

Using the switch in your make file, you are specifying that the vram array is 26 tiles tall(28 is default) and that the first line drawn(first line of the top tile) starts 28 pixels from the top of the physical screen. You can decrease 26 to whatever you need, but you'll need to adjust the first render line accordingly if you want your image centered vertically. (TODO INCREMENTS HAVE TO BE MULTIPLES OF 2?) What you'll gain from this:


  • for each 2 rows you shrink vram, you basically get an extra ram tile.
  • for each 2 rows, ~14,000 extra cycles to run your code and pre-process ram tiles. More than enough time to make 30+ ram tiles possible even at a conservative 26.


Pretty big advantages if your game can implement this well. On the default setup of 28 rows with complicated game logic, you will often run out of time before you utilize and blit the available ram tiles. So this is probably the more important part of it, as it would allow a robust rotation system enough time to run. If you already have your game drawing to the screen, you will now need to make sure that it doesn't draw outside of the new vram window. So in the above example, SetTile(0,26,tileIndex) would write into memory that is no longer vram which will cause problems. Below is an example of this technique in action in an early game prototype well suited to it. It uses 24 rows and 31 ram tiles with sound channel 4 turned off(tons of time still available for extra gameplay logic). The mega sprites are 3x3 and sprite flickering is barely an issue, the extra time allows a good sprite rotation system(accounting for y-ordering also).


MegaBomberSprites.PNG



Feel free to ask any questions, and I will probably add them to this tutorial.