Sprite Techniques

From Uzebox Wiki
Jump to: navigation, search

Introduction

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 terribly complicated, though hopefully I explain them well enough. Note that some ideas could be used for Mode 2, but primarily the examples will be assuming Mode 3. Please read Video_Mode_3 first if you have not already. I'll cover some basic items as well as some more advanced ideas. One thing I'd like to point out, a couple of these techniques are Uzebox specific and you wont see them used on other consoles. Any areas I am missing, please PM me so this can be more complete. As always, you will be personally credited for contributions. Now onto the good stuff!


Terms

Just to so we're on the same page, I'll define some 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 clear, for this document we will consider sprites to be an (8x8 or 6x8 for mode 2) entity that we can assign any pixels to via it's graphic index. This object is different from the standard tile because it can be placed 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 X/Y flipping. The kernel then uses as many ram tiles as the single sprite requires (from 1-4) based on it's position and alignment on 8 pixel boundaries. Assigning, in your makefile, more sprites than than the ram tiles they will require is worthless and wastes ram and a bit of speed.


  • 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 Paul's 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 and un-aligning with the boundaries. By the way, I think it was Uze who coined this and it's a pretty 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. You should spend some careful consideration as to what game you actually want to do. Considering that crafting a quality game is a time consuming endeavor, you should make sure that it is even feasible. I'd venture a guess that a large amount of games are doable, just that some will require significant redesign on your part. This can be a good thing as it's more original, but you'll have to decide how difficult it will be in the long run. So when you are sure on your game, what you'll want to do is visualize it. 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 that 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.
  • Object that come to rest on 8 pixel boundaries. Sokoban and Lolo are examples, somewhat why I chose them actually.
  • 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 handling routines. The kernel provides good functionality for basic mega sprites, but it is setup for generic cases that might not match your specific needs. As for actual game details, I have a few suggestions you might want to consider. As an example, for a Bomberman game I am researching, there are many large player sprites and the original game had situations where they would stop at any offset. After giving it some though and testing the collision detection I found that, although players could stop at any offset they chose, bombs could effectively kill them with only a small offset into the fire. So for that I decided a slight gameplay deviation was worth it and players continue to move to their next 8 pixel offset since it's essentially a grid game anyways. The advantages(especially when combined with the concepts below) allow for a lot more action than would have been possible, and eliminates a few other situations in the original where players would be unaligned both x and y. Also simplifies collision detection so we have extra game logic time so it's win-win.


For another example, we'll think about the original Mortal Kombat. Usually when we think of a fighter, we'd like to think there is a good amount of precision to the movement. In the MK case and others however, there are several grid based calculations and movements at work. This is simply tested in any game by tapping a direction quickly and releasing to see if an object continues on to align itself on a boundary(even after we stopped pushing). The simplification makes a lot of the gameplay logic faster, but for Uzebox this also has the nice implication that a large object will never come to rest under a non-ideal situation. Even if some flickering occurs during the movement, there is an end point when this can be avoided. That is, there will frequently be situations where the conditions are ideal.

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.


This simplest way to go about this, given a set of objects, is to draw them first to last then alternate from last to first. Here you should draw the most important sprite in the middle, since they will always be drawn. If you need specific objects to be on top of others, then this method might not be the best. As example, in Lolo when a shot hits an enemy or an enemy and Lolo overlap they will briefly flicker under and over each other. In practice it is of little consequence, barely noticeable, and I consider it be a good and fast solution. In games where there is a depth element such that lower objects must always be drawn over a "higher" object, this simple solution will not work(see the MegaBomber example below).


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 regardless, 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. A lot of possibilities, for this particular type of game, you'd definitely need to explore them all.


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 it). 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 or are otherwise less important. Perhaps a non attacking foot, the edge of a knee, whatever you decide makes a sprite more or less important. They are only adding a little bit to the final image, and therefore are the best candidates 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,space,and speed, 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 extra time. All you'll need to do 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 information, you can 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. This is the specific part that needs to be setup to match your game and the situations that will occur in it. Hope that all makes sense, I don't have any nice looking code to show you yet. TODO!


  • Mode 2 note - This mode has a lot of sprites available, so the kernels flickering for more than 5 sprites per line is pretty sufficient. If you really wanted to, the same concepts could apply at least to not draw sprites as often(when line overflow will occur) which are less important. Just a thought anyways, not much practical experience with Mode 2.

Partitioned Sprites

As the name hopefully implies, this is the process of offsetting sprites that are part of the same mega sprite. That is, instead of placing sprites directly side by side on over each other, we use some offset causing them to occupy a few of the same pixels(which are transparent). This is an interesting technique because it is almost certainly Uzebox specific, for instance doing this on the NES/SNES/etc hardware would yield no benefit. Probably a bit complicated to understand at first(in words), this is only useful for a mega sprite that does not occupy all the pixels of it's individual sprites. You know that you could draw 32 different sprites(8x8), all at (16,16), and it would only require 1 ram tile. You also know that just 1 sprite overlapping even 1 pixel over the 8 pixel boundaries will require 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 AggregatedSprites2.PNG


The mega sprite in the first 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 if that pixel were to overlap a boundary. On the right side you'll see we have shifted half 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!


Now we notice on the second picture, we see the actual graphics data on the left side. The top is the normal method and the bottom has had it's graphics offset so it displays correctly when we offset the sprite into the main body a couple pixels. Since we will the void with transparency, no one will be the wiser for our little trick but we have just made our mega sprite 21 pixels wide instead of 24. Now I hope the picture is fairly self explanatory, what you have basically accomplished is being able to offset this mega sprite up to 3 pixels horizontally(from 8 pixel boundaries) and still not require extra ram tiles since it is only 21 wide. Perhaps one way to look at it, there is a 3/8 "chance" that it will no longer require extra ram tiles given some random x offset. So during the intensive phases of a game, where many objects are all doing their thing, this chance can make a big deal especially if other objects have these same chances. It could be the difference, in the worst case scenarios, between a stream of images that are reasonably acceptable or a stream of severally lacking graphics.


Let's consider a couple situations when this would be useful, and when it is undue effort. The first thing I would immediately consider this for, is free moving mega sprites. Here you are encountering worst case scenarios often, and this is really the most likely to have severe "overflow" problems. So you could apply this method for both axis', such as the picture below. On the left is how you might do a 14x14 ball the standard way, on the right is a version optimized for this method.


OverlappedBall.PNG


Secondly in situations where the sprites are generally aligned on one axis, this is only useful in the direction of travel. Take for example this mega sprite(2x3):


WhiteBomber.PNG


You see that the bottom frame benefits from from this, whereas it is worthless on the top. So you might find it easier to make this frame normally and use (0,0) offsets for the sprites. Even though this demonstrates that this is not always are even usually useful, I'd like to point out an idea you probably had at some point. Given the sprite limits, to make the most of them, the graphics should preferably fill up as much of the frame as possible. This is not realistic in a lot of graphics and they just look to be smaller sprites though they still cost the same amount. Now at least there is a benefit for sprites not requiring the whole frame, and you needn't feel compelled to use or modify graphics to look bigger. Make the graphics how they should look naturally and reap the slight rewards when things get hectic.


To wrap it all up, this method wouldn't be difficult to implement once you understand it. Even at that, I consider it pretty hard core since this would work best be coupled with the previously mentioned robust sprite rotation system. I don't have an implementation to show you(yet!), nor have I seen an example of it. So you might consider this experimental/theoretical as to how effective it would be in action, but logically it seems like the way if you are going all out on a cutting edge Uzebox title. Certainly no draw backs other than the extra effort, and I'd be surprised if these methods didn't pay off big time in a game with "too many" sprites. As for implementation you probably have your own ideas as for your needs and your personal taste for ease of use. Just for a lame example, I'm using something like the following in a demo I'm working on. This is just how I do it, I'm not claiming it's elegant or clever but seems fast enough. You have many reads from flash and extra space usage, but the whole basis is that better control of sprites is worth it. This simple example does not implement the overflow and priority conditions described in Sprite Rotation, since it can be way more complicated than the whole example. I leave that for you to design to your needs.


  const char FrameTable[] PROGMEM = {
  
  //Frame 0 White Bomber Facing up(mirrored)
  
  0,0,//top left tile index,top right
  1,1,//middle row...
  2,2,//bottom...
  
  1,0,//mirror reference,offset reference - these indicate which mirror/offset frame to use
  //etc...
  };
  const char MirrorTable[] PROGMEM = {//mirror reference(in frame table) indicates which mirror map to use for horizontal mirroring
  0,0,//Index 0 - no mirror
  0,0,
  0,0,
  0,1, //mirror the right side
  0,1,
  0,1,
  //etc...
  };
  const signed char OffsetTable[] PROGMEM = {
  2,0,  -2,0,//x/y offsets for each sprite
  2,0,  -2,0,
  2,0,  -2,0,
  };


  • Now to make this information useful, something like this should suffice:...I'll get some code up in a while
    • TODO

Letter Box

This is documented in Ram_Tile_Effects_Primer (and of course brought up by Uze originally) but I will cover it anyways. You can use this in conjunction with scrolling. 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 2x3 but sprite flickering is barely an issue, the extra time allows a good sprite rotation system(accounting for y-ordering also). Not to mention Ram Tile Effects on the sprites...muahaha :D


MegaBomberSprites.PNG


  • Update - Experimenting, I believe that the later you start drawing(set first render line higher) the more time you have for game logic. Here's the newer version of the bomberman game, here this has allowed me to use a bit more ram and not overflow the stack(buying me an extra tile line).

Bomberv2.PNG

Another switch you might want to use in conjunction with the above is:


  KERNEL_OPTIONS += -DSOUND_CHANNEL_4_ENABLE=0


The funny thing is that there is a lot to be gained in graphics, simply by not using the 4th audio channel. Mostly this is useful if you are running out of time somewhere, such as when you need to blit 30+ ram tiles. You lose the ability to use Noise/PCM sounds when you decide on this, but some quick testing indicates it buys you enough time to blit somewhere between 2-4 extra ram tiles. A useful switch if you don't want to crunch your letter box down any further just for a little speed. You could turn off additional channels as well, but be mindful the sacrifice to music probably isn't justifiable.

Sprite Curtains

An advanced usage of sprites, this was originally demonstrated by Paul and is mostly an Uzebox specific concept. This is approaching Ram Tile Effects(RTE), but is easier and cleaner to accomplish. Only because the kernel code is still doing the majority of the work, I make this distinction. More on that later perhaps. The basic idea here is to have the kernel blit a sprite over a tile, then directly set vram to the ram tiles used for the sprite. When you do this you have effectively made a copy of the sprite in multiple locations. The original sprite is still the only real sprite keep in mind. The copies will look exactly the same, so it will only work in situations where the background looks correct at all the positions. This would allow you to have drastically more "apparent sprites" on the screen but, again, they would all look the same and be at the same offset. If another sprite overlaps it, it will be visible in the copies also. There are some situations where this could be of use, lets take a look.

////// //TODO //////


PlatzSpriteCurtains.PNG


So this could end up pretty simple. Let's look at some psuedo code that might accomplish something like this.


//TODO


If you are only after a couple little foreground effects, you needn't implement anything too elaborate. The easiest method to give the appearance of a foreground, isn't an effect at all. Simply use sprites with higher indices than the sprites you want to overlap, and make them look like a foreground of some sort. They will be drawn over the top and you have your self a permanent foreground, as long as your sprite rotation code is aware of this! Of course organization is critical to pull this off, because this has all the same caveats of RTE. When combined with other techniques things can get very complicated and you might end up with more than you bargained for in the debugging phase. I'd recommend this for situations where it is easy to implement. For the original discussion on the concept and good picture examples, you should look here [1]

Sprite Effects

If you are interested in this, well, you've officially entered Ram Tile Effects territory. First read Ram_Tile_Effects_Primer unless you understand this already. As this is really beyond the scope of the tutorial, this will be very brief. Don't let any of the RTE stuff scare you though, in reality it's not very complicated to understand once you get going. Because this tutorial is mostly aimed at using the kernel to blit sprites, I will try to use practical examples you could do it this way. First of all, you must call your effect function inside ProcessSprite(). Without this you will not have access to them after they are blit. Since the kernel waits until VSYNC once it's done, no user code is run. When you check out ProcessSprite() you'll come to a point where the sprite is finally blit. You will probably want to do something like this:


  BlitSprite(i,bt,(y<<8)+x,(dy<<8)+dx);   
  PostProcessRamTile(i,bt);//your code here: i = sprite #, bt = ram tile # that was just blit


You'll have to determine the most useful methods for your situation. Keep in mind that this function will be called for every ram tile the kernel uses, which might fluctuate a lot leading to unpredictable performance or situations that totally crash your game. If you actually end up trying this, you'd want to make this code as fast as possible and make sure unforeseen situations can't cause a crash(which is hard since their unforeseen...). It's probably just a dangerous thing to do timing wise.


Assuming you want to worry about this, RTE can be applied directly onto each ram tiles blitted by the kernel. As long as you have a way of knowing which ram tiles are the ones you want to modify, you can pull it off. This would have to be handled by the function you placed into ProcessSprites(). The possible problems inherent with integrating this into a sprite rotation system could become staggering! The amount of extra time this will take away from the rest of your VBLANK time will also be a critical consideration. I'd say this is pretty special case stuff maybe for a demo or something, I'd try to stay away from it if I actually wanted to get a game done(bah, overrated :D ). Maybe you are just that hard core though!


Conclusion

For now I'm considering this tutorial usable and near complete, though I'd love to know how to improve it. So, as with any of my tutorials, questions are appreciated and I'll try to add an example based on them. Thanks for reading, hopefully this helps someone design the next big Uzebox game!