2D Art Tips: Learn From My Mistakes
From TDN
Don't let me fool you. This is not an exhaustive list of all the stupid things I've done, but they are the highlights which may prove useful to have listed all in one place. My trials and tribulations the last few weeks with wrangling our game into shape on TGB have taught me a few lessons that I thought I would share. Suggestions, corrections and additions welcome.
Background:
My Bogle is not physics heavy, is not a platformer and isn't even real-time, so it may seem odd that I think my experience is relavent, but what we do have, and what we will be adding a lot more of in the next month or two is a lot of animations. We've recently incorporated the complete set of animations for just 2 characters into the game. Each of the characters will contain the following animations - Attack, Taking Damage, Dodge, Move, Rotate (clockwise and counter-clockwise as the characters aren't symetrical), Victory, "Death", the Selected Character animation loop and a small "idle" animation. Each animation is a different length, but in total there are over 150 unique frames of animation for each character. We intend to have 18 characters/variations, which gives us just shy of 3000 frames of animation. Some animations are oblong (like attack and move) but most are displayed on screen at around 113x113 square. Our source animations were created at 226x226 from Toon Boom as we wanted as much detail as we could get when we "zoomed" the camera in. Oh, and we currently have few particles and sound effects in, so this is going to focus on image related topics. If others have suggestions for sound and such, please feel free to send 'em our way as I'd rather not have to learn the hard way :)
Okay, so with our 2 characters in, and with pretty much all the default setting when creating the image maps and animations, the game was running at over 300 megs of memory. Clearly we were going to have issues if we were going to hit 18 different characters... and 300 megs was already 2-3 times my original target memory utilization. And that was without much sound and with few particle effects. And with a small map without any animations in it yet... So you can understand the state of panic that I was in. Now, without further ado -
Andrew's Helpful List of 2D Tips Created Based on His Close Personal Experience of Doing Everything Wrong at Least Once:
1. Don't preload all your image maps unless you are creating a truly trivial game.
You know your imagemaps are all preloading when you hit your peak memory utilization before you even load your first level. :)
2. Allow unload on your image maps where you can.
This can be slightly dangerous as loading and unloading imagemaps constantly is bad, but if you wonder why, once you hit your peak memory utilization, you never seem to get your memory back down no matter what's on the screen, it's because you aren't allowing anything to unload. When a character dies, let that bad boy go! Also take a look at this great resource for dynamic imagemap loading/unloading.
3. Don't create graphics that are larger then they will ever appear on screen.
Now, we have some great graphics to use for close ups, but most of the animations will never be zoomed in on, and so keeping an image that's essentially 4 times bigger than it needs to be (113x113 is a quarter the size of 226x226) is the worse kind of wishful thinking. Besides, linear filtering isn't a great way to show off your graphical assets. Some day, when the market allows for extremely high memory requirements for games, monitors have densely packed resolutions and taking up a half a gig of ram for a 2d game is okay, then we will go back and use all those 226x226 animations we did... but until then, we're down to 113x113 for 90% of our animations now. The great thing is they look better than ever.
4. 800x600 or 1024x768 background imagemaps.
Don't just throw a big old imagemap in as the background of your level. Your standard ImageMap needs to be a power of 2 in height and width (so 32, 64, 128, 256, 512, etc.), and TGB will do it's darnedest to keep the size in memory small, but it can't do much with one big old image. Which means the image is will be padded out the nearest power of 2, and that means a bunch of wasted memory. Try out a ChunkedBitmap as they'll most likely solve your problem. They don't need to be power of 2 and so you don't waste any of that space. If you really need a big image map, you may be able to get better utilization by slicing it into power of 2 sizes and just displaying multiple images as the background. Just beware of #5.
5. Beware the filter padding.
TGB does a great job of removing those nasty edges around images caused by the linear filtering done on the graphics hardware, but if your expecting your image size or your cell size inside your images to be X pixels wide by Y high, bear in mind that if filter padding is enabled, then they'll be X+2 wide and Y+2 high. So your nice 128x128 image that fit so well in the texture will now be loaded into memory as 256x256. Ouch! Turn off filter padding and add your own border or just create your images/cells just a couple pixels smaller and you'll be all set.
6. Size Matters.
Stick with power of 2 sizes or as close as you can get for your animations as it will be far less stressful in the long run. See, we needed our images to be 113x113, so we were able to fit 9x9 cells into a 1K by 1K image, which is great. But originally we had 226x226 images, and they fit very poorly into a 1K square texture. We ended up not only wasting space in each texture, but with longer animation sequences, it meant we needed to use linked image maps. And so we went from bad to worse.
7. Avoid linked image maps unless you really know what you're doing.
I clearly didn't know what I was doing, and I was just asking for it. You have to have the base image maps loaded in memory before you can use the linked image maps, and that means that you've got a bunch of memory potentially going to waste. They are a great feature, but in the hands of a fool like me, they can cause some serious memory problems down the road.
8. Aspect Ratio doesn't matter for your images in TGB.
The only thing that matters is the center of the image must be the same, relative to the other animations. See, we originally got confused. Okay, I got confused. In order for our rotation animations to work correctly with all our oblong images mixed up with our square images, I simply made all our square images into oblong images with white space. Poof! They worked, and our memory utilization shot straight up. Once I realized that it wasn't the aspect ratio that was making it work, and that all we needed to do was make sure the character was in the same relative position, we were able to cut all our square cells back down into squares and just pad the couple of oblong images so that they had the character in the same relative position as the square images. That was a huge savings and meant that we could throw all the square images into one 1k texture for each character. The oblong sequences are handled as separate textures now. I have trouble explaining this one well, and it's not clearly documented in the imagemaps documentation that I can see... if you want more info on this one, just holler.
9. Being ignorant is no excuse.
Take the time to actually turn the preference settings on for tracking what's going on with your imagemaps every once in awhile.
$pref::T2D::imageMapDumpTextures = 1;
$pref::T2D::imageMapShowPacking = 1;
Look at the resulting games\imageMapDump directory and console.log. If your wasting more than 10% of the space for any of your imagemaps, or if you are surprised by the sizes or number of images in the dump directory, then it means you probably have some easy savings available to you. You should take a close look as well on the remaining images and packing details in the log as little bits of inefficiency repeated many times add up really quickly.
10. Run without the editors every once in a while.
The editors are great, but you need to realize that your application may very well perform quite differently when the editors aren't doing their thing. Tuning memory can be especially difficult as the editor is loading up imagemaps and such that normally wouldn't be loaded. This is potentially good but also possibly bad for your application. It's great to get a drop in memory, but if your application starts chugging when you aren't running with the editors, it's probably because your imagemaps/textures are swapping in and out of memory more often. See, when the editors run, they'll force those textures to stay in memory, using up memory, but potentially significantly improving runtime performance.
11. Optimizing color usage.
Now that you've got the application running well, you're ready to ship it (okay, we've not gotten to that point yet, but it doesn't hurt to be prepared, right?)... but you've got all these huge images that don't compress well that will make your download size astronomical. Well, one quick way to reduce your png's is to convert them to indexed images. In memory, they'll still be as big as ever, but on disk they'll be, potentially 1/3 the size of the originals. Depending on the content, you probably won't even notice that they are indexed instead of RGB.
12. Know your audience.
Now that you've deployed your application, you've got people complaining that it doesn't run on their computers (okay, that has happened to me). It's probably because your textures are too big. Here's a handy site for finding out which video cards support which size textures. http://zp.lo3.wroc.pl/cdragan/wizard.php - Choose D3DCAPS8 and choose MaxTextureWidth and MaxTextureHeight. It would seem that if you are going to go with larger 1K textures, then there's little reason not to look at using 2K textures as only one integrated intel chipset and the old ATI Rage cards appear to be limited to 1K.
Results:
Okay, so I hope that helps. What did all of these lessons do to improve our games' memory utilization? Well, we are down to under a 125 megs on average with a peak of around 150 megs, and there's some more room to be had. The best part is that the additional overhead of each new character should be fairly low, so I'm hoping we'll be able to ship with around 150 megs peak memory utilization once we finalize our optimizations and get sound and music in. Feel free to throw in your 2 cents or just laugh and say "how come you didn't read the imagemap documentation"
... but we live and learn... or at least we live. While there's truth to the statement about not optimizing code too early, when it comes to designing your game's images and animations, an ounce of prevention is worth a pound of cure. Alright, enough with the cliches. Back to work.
-Andrew Douglas



