Performance optimization in Minecart Madness
Written by: Mark Bouwman
Performance optimization in Minecart Madness. Minecart Madness is an entertainment game for iOS, aiming at casual gamers who play games on their phones and tablets. The game is a 2D racing game with a view from the side.
The entire game is developed using Unity3D, a free program that allows developers to create games on multiple platforms. We however, are using the licensed Unity3D Pro and iOS Pro features, in order to obtain the best gameplay results possible.
This week I would like to talk about performance for iOS. It’s one of the most valuable things to have: good performance. You can easily notice the difference between 10 frames per seconds or 60. Our goal is to keep the game at a steady 60 frames per second.
But Mark, how do you do that?!
Well, there’s a lot of ways you can increase the performance in a game. There’s all sorts of things you can do to keep the graphics down, but there’s also some real nice things you can do by programming. I’ll list a few, just to get you started.
Tip #1: Profiler
This is where getting your performance to the best possible starts. The profiler. It tells you exactly what you need to know: What is killing your game’s performance? Unity allows you to dig deep into your scripts, telling you exactly which calls take performance and how much those calls hurts. You’ll quickly find out that those nasty Debug.Logs you’ve been calling take up a lot of performance compared to the rest!
The profiler has two modes, normal and deep profiling. With normal you can get an overview of the actual performance you have, with deep profiling you get more information at the cost of some performance. It’s really nice when you need just that extra information.
Whenever the frame rate drops, it’s represented by a big spike in the profiler. You want as little spikes as possible, with as little difference from the average as possible. If you do this, your game will be running as smoothly as possible!
Tip #2: Pools
That’s right. Pools. Not the swimming pool type, no. The ones where you instantiate objects at the start and reuse them. Instantiating and destroying objects at runtime is a killer for any program. Instantiating the objects first and then using them over and over again keeps the overhead of instantiating to a low. You can do this for a lot of objects! You can use it for bullets, audio sources, small graphic effects or even entire randomly spawned worlds!
The math: I’ll explain the math using the audio system we use in Minecart Madness. In Minecart Madness we have an average of five sound effects playing every second. Since every sound only lasts a second and a half tops, we can have ten audio files playing at the same time. Knowing this number, we create ten objects at the start of the game. It increases the loading time by around 3ms, but that’s hardly noticeable. Creating a sound object on runtime takes about 0.3ms, removing it around 0.5ms. This might not sound like a lot, but let’s calculate.
We want to reach 60 frames per second. A whole second is 1000ms. This means we have around 16ms for each frame to use. Out of this, we need 10ms for the actual rendering of the game. This leaves us with 6ms to use for coding or 360ms in a second. With ten objects a second, that takes 8ms. That’s roughly 2% of the calculations we are allowed to make, just from creating an empty gameobject with two scripts (I’m not even talking about playing the actual audio file!). If you instantiate and destroy a lot of objects (or complicated objects, like geometry with scripts on them), consider this neat trick!
Tip #3: Object Culling
Culling happens when an object is outside of your view. By default, every object still calculates the rendering and updates whenever it’s in your world. Always. Even when you don’t need it because it’s outside your view. This is where Object Culling helps.
A big part of the culling can be handled by Unity3D itself. There’s a thing called occlusion culling, which handles the culling for the camera in the game. Unity’s website has some pretty nice documentation about it.
Another part of the culling happens in programming. Would you need something to check for precise collisions with a specific part of the player, when they’re miles away? No. Imagine you’re casting a ray to check for a certain collision, but this ray has a high overhead (Let’s say, 2ms each frame). If you first check the distance from the player, which shouldn’t take more than 0.02ms, you save your program from casting this calculation-heavy ray!
Tip #4: Using the cache (save object references for later use)
Wow, this one can save so much time. Imagine you have 20 enemies, all checking if the player is close to them or not. They check their own position and compare the distance to the player. Nothing special, right? Nope. However, you can make this quite heavy on your program. If you don’t save the player’s transform in your script, you have to get it every single frame. GameObject.Find might seem harmless, but you should really take care using this!
The math
Let’s think about this simple calculation: The distance between the player and the current object. If we use GameObject.Find for this it takes around 0.5ms every frame, for each object that calculates the difference. However, if we already declared the player’s object at the beginning of the game and just use that reference each frame it takes just around… 0.002ms. That’s 250 times better!
Alright, I believe you.
There’s a lot of small tricks like these to increase the performance of your game. Feel like you want to share yours? Just reply to this post!
Written by: Mark Bouwman