I was overcome with sheer excitement this evening as I linked up my phone and desktop and exported the current build of my ambitious platformer to Android. It was a massive level (by my own standards at least, compared to the other levels I’ve built so far), chalk full of enemies and collectibles and secrets, so I was definitely excited to give it a shot on the device it was meant to be played on.The game booted up on my phone, the black overlay faded out to reveal my latest level, and my heart immediately sunk: it was running as slow as molasses. And molasses certainly never ran a marathon.
On my desktop it ran buttery smooth, and it’s a simple 2D platformer with a fairly low resolution, so I never even anticipated I’d run into any speed issues on my phone. Whenever I encounter overwhelming lag like this, I try and make sure I didn’t accidentally open up some sort of memory leak. Everything seemed to be in order. So then comes debugging and testing and changing things around until I get an inch closer to figuring out why the heck the fps dropped down to about half of its regular speed. After a bit of experimenting, I found the nasty problem: I just had too many collision blocks.
But this being the problem made it an even bigger problem than I initially imagined. See, the reason it was slowing down wasn’t necessarily because I had a lot of collision blocks (which are just purely object I created with absolutely NO code at all, unless we’d really want to be crawling along at a snail’s pace), but the problem arose because of the frequent collision checks. Say I had one enemy that made a collision check every single frame. In my level, I had 2,249 collision blocks, so every single frame, when the enemy wants to move forward, it must check itself against 2,249 other blocks (Not 100% certain, but I believe this is how Game Maker handles collision detection via commands like place_meeting() – you call the function once, but behind the scenes it’s actually iterating through every single object of the type you specified and checking if it collides). That’s pretty costly. But now imagine a level with 14 enemies. Each enemy must check its position against 2,249 other blocks, so in essence, that’s essentially 31,486 collision checks each step. And at my game running (or attempting to) at 60 fps, that’s 1,889,160 collision checks each second. Ouch. With over 1 million collision checks a second, it should come as no surprise that my game is crawling.
Of course, I became a bit frustrated and discouraged, thinking of how I could manage this game-breaking conundrum. I already coded a system for splitting larger rooms into separate segments (where the player walks off the screen to reach the next area), but I didn’t want to split this room up any further. I thought and thought… and it finally came to me, a lightbulb moment: I could drastically decrease the number of collision block objects by resizing individual collision blocks in the room editor to stretch out over multiple blocks. Each block is 16×16. Let’s say I have a chain of 6 blocks in a row, horizontally. Being that each is 16×16, and I have 6 side by side, this means this would take up 16×96 pixels. But instead of laying down 6 individual blocks side by side, why not just put down one block and stretch it out horizontally in the room editor to occupy the same exact space of 16×96? In that manner, we’re using only one block where we before had to use 6. and that accounts for 5 fewer collision checks each frame.
And that’s just the beginning.
By using this method, I was able to decrease the number of collision blocks in my level from 2,249 to a whopping 265. That’s about 89% less blocks than before.
And what did this do to the framerate? The room went from ~30 fps up to ~170 fps. And I could optimize it even further. How?
Each enemy or moving object needs to make the collision check every single frame. This occurs even if the object is offscreen. This isn’t necessary – for some enemies, particularly those that must strictly adhere to a very specific timing pattern, they need to continue running their code even while offscreen. We could have a global timer and sync up all the enemies to that, but that’s a lot of unnecessary code for something that could be accomplished much quicker. But what about those enemies that don’t need to be active while offscreen?
You might automatically assume that we could use the instance_deactivate_region() command, but this wouldn’t work in this situation. This function deactivates all instances outside of a specified region, but being that some enemies still need to run their code even while offscreen and they’d be included in this deactivate_region check and be shut off, using this command wouldn’t be an ideal solution. Instead, we could add a line of code into all of the enemies-that-don’t-need-to-run-through-their-code-while-offscreen that checks if they’re onscreen. If they are, great, run their step event. If they’re not onscreen? Then don’t run through any of their code. It’s as simple as that, and that would essentially mean that any enemy that isn’t onscreen wouldn’t run their code or check for collisions, saving us a lot of precious processing power. At the moment on my phone, with 17 enemies doing collision checks every single frame against 265 objects, I’m averaging ~170 fps, which is well over the minimum 60fps I’m aiming for, so of course I feel a bit inclined to not even bother with checking if the enemy is onscreen before running their code. But, who knows if I’ll suddenly need to add some heavy stuff that’ll take a toll on the CPU, and I’ll be kicking myself thinking, “Oh, why didn’t I optimise!?!? WHY OH WHY.” So, it’s definitely for the best if I do. And plus, who’s to say the great performance and fps I’m hitting on my 2014 Android phone would even be remotely similar to the performance on a a super old cheap phone?
Just as a final note – that’s why it’s absolutely essential to test your project on your intended hardware. I’ve been mainly compiling and testing on my desktop, but it’s a beast of a system, so of course it’s going to run almost anything I throw at it with ease, especially a simple 2D platformer. There’s a huge difference between my desktop with a quadcore 3.4 ghz processor and 32 gb of ram vs. a small mobile phone with (insert phone specs here, I can’t remember them off the top of my head :P).
Good luck, happy programming!