Circuit Breakers | Tech Blog: My code works, but I don’t know why
Recently I’ve been doing some optimization work which is pretty regular occurrence for anyone who’s ever gone beyond “hello, world” and the intro tutorials, and I’ve always been fascinated by the idea of getting games to work on hardware that shouldn’t be possible, my personal favorites being Doom SNES and any version of RollerCoaster Tycoon 1+2. Today’s tech blog revolves around me experimenting with a few different optimization methods in GameMaker: Studio.
Programming is a lot like travelling from one city to another, there are several options to get there and depending on your choices it could take you a little bit more time to reach your destination. For example, the bus might be more cost effective, but it also will take more time than driving a less cost-effective but quicker car. Sometimes we have a lot of money and no time, and sometimes we have a lot of time and no money. Often it is up to the programmer to decide which route will be used. The problem happens when either time or money has been exhausted and you’re not able to reach the destination.
Layman’s terms aside, Circuit Breakers is deceptively CPU-heavy and requires a sizable processor to keep up with hundreds of instances of enemies, players, and energy crystals at 60fps. Most people tend to be under the impression that games that look like Super Nintendo games could run on equivalent hardware. The good news is that we have made it possible for pretty much any x86 desktop in the past 7 years can play the game in its perfect fluid form, however some hardware was not quite as lucky.
We don’t keep extremely low-performance x86 hardware around the office for reasons yet to be discussed, so in the midst of our testing I decided to establish the “Android Test Platform” in order to simulate what low power x86 hardware might behave like. The Android Test Platform (which I’ll just refer to as the ATP from now on) is actually just an Ouya that I binge-bought from a few years ago. The Ouya has a quad-core 1.7 GHz ARM Cortex-A9 that simply doesn’t cut it when it comes to games. I found the worst dual-core Celeron of 2008 and it has a sizable performance increase over the ATP despite it having less cores and clock speed.
It’s very clear that the Cortex-A9 even with its improvements over its predecessors can’t top the greatest processor architecture ever created. This is off topic though, the point here is that the ATP is a glance into pre-2008 computer performance, and probably pre-2012 laptop performance.
Upon booting up the game after exporting it through the compiler I found that the game ran on the ATP with a few visual defects. For one, some of our shaders were not behaving and resulted in some unwanted but not game-breaking graphical issues. More importantly the big problem is we couldn’t hold 60 frames for any period of time, a deal-breaker when your logic is tied to the framerate. In order to quickly combat frame drops I reduced the amount of effects rendered to the screen, decreased particles, and limited the amount of enemy instances that could appear concurrently. Unfortunately none of that helped to the point where I could keep the game running at a steady 60, not to mention it affected the overall game experience quite negatively as the game isn’t as interesting when you remove the explosions, enemies, and particles (who would have thought). It was time to call it a day, and expect people to have better hardware.
Well that’s probably what should of happened, but I was determined.
These kinds of challenges happen to every developer, it’s not some sort of unusual thing for people to try and get the most out of a machine. When faced with low-performance and you’ve done all you can, you must learn to compromise in all the right ways. Masahiro Sakurai mentions how challenging it was to get Super Smash Bros. for 3DS running fluidly, and makes multiple compromises to the graphical quality of the game in order to make that happen. For example, he makes some objects run their animations at 30fps.
So I did exactly that,
but with everything,
and I don’t know why it works, but it does.
Frameskipping is not some foreign concept, in fact many games skip frames on purpose for non-performance reasons like Skullgirls. When I told the game to draw every other frame I was able to hit that coveted 60fps that I had been longing for and was able to add in those effects, enemies, and particles. The only negative result is that I’m only able to see every other frame instead of every frame, a compromise I’m willing to make if I can keep the game the way it was intended to be. Normally a person would stop here because the “problem” has been fixed but I have a new problem now. This is actually impossible and there’s no reason why this should work
From what I understand, GameMaker runs code in the following order
STEP (LOGIC) > DRAW (GRAPHICS) > STEP (LOGIC) > DRAW (GRAPHICS) > etc
Which means that with my code it should look something like this
STEP (LOGIC) > DRAW (GRAPHICS) > STEP (LOGIC) > SKIP THIS DRAW > etc
Only one problem though, as you remember, when I had effects and particles and enemies on the game wasn’t running at 60fps, meaning that
STEP (LOGIC) > DRAW (GRAPHICS)
|-----GREATER THAN 1/60s-----|
A step > logic combination was most definitely taking longer than 1/60 of a second. As a result, that should have caused the second step event to occur at a point later in time, thus slowing the game down. This never happened though, the game runs at 60fps only drawing every other frame and I have no clue why. Should it not be constantly stuttering between a frame that takes more than 1/60 of a second and one that takes much less than that? If you can explain please send me an email at firstname.lastname@example.org