Thursday, February 24, 2011

Optimization!

I finally got around to purchasing the Creators Club Membership.

The main benefit is I can deploy the games I make to XBox 360, and after Peer Review, have them show up on the XBox Live Indie Games channel.

The set-up and deployment process was pretty painless, but once I loaded up Zombie Bash, I noticed something bad.

The game was incredibly slow! I'd be surprised if I was getting 1 frame per second, especially when the level first started and a bunch of enemies spawned.


The game ran fine on the computer, but the XBox has much weaker hardware. Also, the way XBox environment works under the hood (like disk IO) is different as well.

After doing some research and reading the forums, I came away with some suggestions to try:

1. Run the Xbox Remote Performance Monitor when the game is running on Xbox.
This is provided with the XNA Game Studio under Tools. I was able to get it up and running and look at some of the results in real-time. Unfortunately, the specifics that are profiled were a bit too low level to be of much help. I could tell that the Garbage Collection on the XBox wasn't keeping up with all the objects I was spawning, but why?

2. Run a .NET CLR profiler as you run the game on the computer.
I was able to download the Open Source tool SlimTune and configure it to profile my game. This turned out to be a lot of help. SlimTune actually attached to the CLR and uses Reflection to determine which methods are using the most processing time. This quickly points out the functions and calls that are slowing down the game. (Note: This program can often crash when testing XNA games, however it will still save the profiling to a file).


Which leads us to point number 3 -

3. Look for instances in the code where expensive operations are being done in real-time.
The number one difference between programming games and regular business apps - the same game code runs dozens (hopefully 60+) times a second. If there are any expensive operation in any of the loops, even if they occur once or twice (aka - loading assets), they can slow the entire thing down and lead to a bad gaming experience.

This was the culprit. Every time a new Enemy spawned, I was loading all its sprite textures from disk. This had been fine in the sample code, because the enemies were spawned once when the level was loaded. But Zombie Bash has Enemy Spawners, and dozens of enemies can pop out every few seconds. While the computer can easily handle that kind of disk IO, the XBox was choking.

There were a few other objects (weapons, particles) where the same thing was being done - realtime loading of texture assets into the newly instantiated object.

What I did was create a Loader class that is run once when the game first starts. This goes through and reads all the textures, and also calculates the required bounding boxes and Color arrays needed for collision. It then holds on to them in memory for whatever object needs them down the line (Enemies, Weapons, etc). It remains to be seen if this will lead to issues if the game gets sufficiently large (hundreds of enemies, etc)

Load them (textures and animations) once and save them for later!



A similar approach was taken for the particle system. Each individual particle was calculating its own Sin and Cos curves when initialized in order to follow curved paths. By moving those calculations to the parent class, and simply passing in a pre-initialized Curve object to each individual particle, the particle system was greatly improved!

This is incredibly expensive to do 50 times at once!



To sum up:

-The XBox is much weaker than a computer
-Make sure you don't load or calculate anything expensive during the game loop. Do it before!