When time is running out, the team is frazzled, and mysterious problems arise, sometimes unconventional solutions are needed. When you’ve just got to get the thing done, all bets are off. In a pair of classic articles originally published in our sister publication Game Developer magazine, we explored some fascinating real-life examples of just that. You can read those timeless pieces here and here.
Gamasutra decided to revisit the topic. We’ve gathered unusual solutions to unusual problems from across the industry. Those who submitted may not all be proud of these “fixes,” but perhaps they should be. They got the game out the door, they didn’t break anything, and more importantly, nobody noticed. At least… not until now.
Delight at the ingenuity, marvel at the audaciousness, learn from the mistakes of your predecessors, but most importantly, release games that work—on time, too.
If you want to share your own surprising solutions for getting games out the door, we want to hear them! Send your dirty tricks to Gamasutra (with "dirty coding tricks" in the subject line) and they may be featured in a future article.
"Instead of re-balancing level files and buffer sizes across the game, we had an easier idea: when you were nearing the rewind memory limit we simply paused the game, flashed a big fat 'low battery' icon on the screen and gracefully booted you to the level select menu."
It may not look like it, but Super Time Force pushed the Xbox 360's memory limits to the brink. With time-rewinding, we had to store all the information of every active object in the level, for every moment in time, for the entire duration of the rewindable timeline. Every additional player, enemy, bullet, explosion, platform, chunk of debris, and dismembered body part eats into the rewind memory, and managing this became the bane of the entire development.
In the final stretch of certification, with every level and rewind buffer painstakingly tweaked to perfect balance, we got a new bug from QA:
"Start any level. Press every button on the controller as fast as possible for 2 minutes. Rewind and do this 20 more times. Out of memory."
Extreme button spamming would eat into the rewind memory faster than what was anticipated for human inputs. Rather than go back to square one with a full re-balance of level files and buffer sizes, we opted for a simpler solution: detect extended periods of unreasonably fast button spamming and immediately self-destruct the player, then rewind them back to the start. We “pretended” that the player accidentally triggered a rewind during their, undoubtedly, furious controller mashing. This bug was now a feature.
A few days later we get another bug report:
"Start 199X level 2. Run through the level blowing everything up and leaving as much debris as possible while continuously shooting machine gun bullets until time runs out. Rewind and do this 20 more times. Out of memory."
The rewind memory limit could be reached in the obscenely rare case of enough bullets and explosions triggering all of the debris at once in this particular level. Again, instead of re-balancing level files and buffer sizes across the game, we had an easier idea: when you were nearing the rewind memory limit we simply paused the game, flashed a big fat "low battery" icon on the screen and gracefully booted you to the level select menu.
No, your controller was not really low on batteries. No, we never explained this to anyone. Yes, QA accepted this fix and we passed certification.
Technical Director, Capy Games
I was the lead programmer on the PS2 & PSP versions of a racing game, developed in parallel with the PS3 and 360 versions.
QA complained that the car handing was subtly different from the other versions of the game. It just didn’t feel quite right. We couldn't find any problem, so we invited one of the testers to our office to discuss the issue. He played the game there and confirmed that the problem did not occur in our office, though it did occur in the QA build.
"We spent a couple of weeks, on and off, trying to track down the cause of this problem. But we could find no cause or fix. It was during this last test, on the eve of submission, that we realized the solution to the problem – if not the cause. "
We started to try to narrow down the differences between the QA build and our development build. After some testing, we found that the difference in handling was related to the frame-rate counter that we were printing in the corner of the screen in our dev build. With this disabled, as in the (master) build we supplied to QA, the handling did indeed feel slightly different. Better, in fact. We spent some time blind testing (covering up the frame-rate counter, switching it on and off) to convince ourselves that this was the case. The difference was subtle, and difficult to pin down exactly. But it was clear – printing the frame-rate “fixed” the handling issue.
We spent a couple of weeks, on and off, trying to track down the cause of this problem. We inspected the physics code, which was supplied by the team working on the other versions of the game, looking for uninitialized variables (which could be affected by the stack state left behind by the font print routine) or possible timing issues. We tried initializing memory. But we could find no cause or fix.
As a last resort we investigated whether moving the frame rate counter around the screen affected the car's handling. It didn't seem to matter where the frame rate was printed, or even whether it was on or off the screen, for the fix to be effective.
It was during this last test, on the eve of submission, that we realized the solution to the problem – if not the cause. We shipped the game with the debug frame-rate counter permanently enabled, printing the frame-rate just off the right hand side of the screen.
"One of the QA testers ended up using these squirrels we had as ambient creatures as the animation timer, and they became the default timing mechanism. "
Without a doubt the hackiest thing I remember from Titan Quest is how we managed the event scripting. The quest / event tech had a major weakness in that there was no way to delay an action once it was triggered. So if you wanted something to happen 5 seconds after a player ran through some bounding volume, there was no way to set a delay. It would always be instant.
We were nearing the end of production, so it was hard to request additional features, as engineers were slammed just trying to meet their milestone deliverables. One of the QA testers had started helping out with scripting work, and figured out that there was in fact a way to delay an action from triggering based on the length of an animation.
He ended up using these squirrels we had as ambient creatures as the animation timer, and they became the default timing mechanism. He created an invisible version of the squirrel, which he would place in the levels where he needed them, then would time everything based on the duration of their idle animation. Because of his creative problem solving, he was promoted to designer on the next project.
Owner / lead designer, Crate Entertainment
"We added a new rule that never evaluated to true (hence its action never executed), but it invoked the code that refreshed videos, allowing us to "bend" the system into a primitive remote code execution mechanism."
The latest version of our game had a new feature that gave players in-game currency by watching video ads (also increasing revenue while we were at it). Following the release of the game, we found that the video ads were not being properly requested, effectively showing only one video per game session. Serious bummer.
Our game uses a server-side infrastructure for running custom actions for different scenarios, allowing us to extend the game after it was released. The system works by evaluating rules and running predefined actions in case they evaluate to true.
Rules are evaluated by reflection. For example, defining a rule VideoAds.IsVideoAvailable = true, will invoke that property using reflection, and run the action in case it evaluated as true.
Luckily for us, the system could be totally hacked, and so we added a new rule VideoAds.RefreshVideos = true. This rule never evaluated to true (hence its action never executed), but it invoked the code that refreshed videos, allowing us to "bend" the system into a primitive remote code execution mechanism. Problem solved!
We were making a major update to an Android game, two weeks before gold. QA reported to us that on some random devices, after a cutscene was played the device would crash. My teammate spent one week trying to isolate the bug or discover the cause. We never found it. But we discovered that pressing the back key to skip the video would avoid the crash. So our little dirty trick was to put a timer in the cutscene and one second before the cutscene ended we would simulate a back key input as a temporary fix. Six months later the "fix" is still there.
Marlon Ruvalcaba Muñoz
In Black Ice, I wanted to make a sky that looked like an infinity room (like this), but placing hundreds of objects in the sky didn't perform well, and it's not like mirrors have been viable in games since the 90s. So I decided to make it with particles. That was easy enough, I wrote a script to place them in the world, but I had a problem – the particles would all disappear when the player looked away from the origin point of the world.
It took me a while to figure out that Unity's legacy particle system would disable all particles in the system when the invisible particle emitter was outside of the camera's view. (Everyone knows what frustum culling is now, right?) I struggled with this for a while, but eventually my solution was to attach the emitter to the player's face, about six inches away. That way, the player could never avoid seeing the emitter, no matter which direction they looked! I had to change the particles over to emit based on the world position, and update when the player moved, but it worked!
Attached a couple pictures of the effect, in-editor and out.
"We did what anyone pressed for time would do: we partially implemented Direct X on the PS3."
Back in the day at Backbone Entertainment, Kevin Wilson and I were on a super tight project to get some of the Midway arcade games up on PSN. This was in the very early days of the PS3. I think our six games: Joust, Championship Sprint, Rampage World Tour, Rampart, and Mortal Kombat 2 were among the first 11 games available to download, or something ludicrous like that.
Backbone had previously released these titles on XBox Live Arcade, but they had not been built in a very cross-platform fashion. The project needed to wrap up in a literal matter of weeks, so we did what anyone pressed for time would do: we partially implemented Direct X on the PS3. I spent the last week of that project trying to cover our tracks.
The client, super-protective of their special sauce, had given us DLLs for our target console platforms only, which were basically a black box. We distinctly did not receive libraries for the PC, no matter how much we begged. The entire front end of the game ran on PC and there was just enough of a stub engine in code so it could start gameplay but you couldn’t test real game functionality on PC, which sucked for a variety of reasons.
We made it all the way through a PS3/X360 dev cycle like this. Then for the next iteration of the game, we moved to Xbox One and PS4, receiving updated versions of the engine libraries, but still no PC version, which made work very difficult, especially on PS4.
The magic turned out to be that the Xbox One libraries actually are fully valid 64-bit PC code, and you can just link them into a Windows executable. You just have to have a PC with a pretty newish CPU that includes the same instruction set extensions as the CPU on the Xbox. I ran around the house dancing when I actually got this working.
Epilogue – the next cycle, when we did a Steam version, the clients finally coughed up a real PC library, so the hack faded away.
More of a kit bash than a coding trick, but when I first started Grim Dawn, I was basically by myself. I had a couple other guys helping out in their spare time (mostly former Iron Lore colleagues) but I was the only one working full-time. A few months later, a programmer joined up, but we never had a full-time artist until after our Kickstarter. I ended up using some of my personal savings to pay for contract art, and one of the first things I had made was this wooden bridge. It had flat wood boards, long rounded beams and these iron railroad spike looking things like giant nails holding the wood together.
Over time, the people who were volunteering their time got busy with work and life and drifted off. It got to a point where not a lot of new art was getting made. I’m no artist, and my skills are limited to photo-source texture work, and only the most very basic of “modeling.” Really, I can’t do much more than move existing pieces around, and can adjust UV maps with mixed results.
But I was desperate to get more environmental art so I could keep making progress with the world-building. Out of necessity, I’d open up this bridge and use its individual pieces to create new wooden structures. It ended up that I created quite a lot of stuff from it. Other bridges, plank walkways, porches for the houses, fences, debris piles, the gallows platform that you see at the start of the game, sign posts, a table, house roofs… Eventually, after the Kickstarter, when I was able to hire full-time artists, some of that stuff was remade but pieces from that original bridge are still used for a good number of things.
Owner / lead designer, Crate Entertainment
When working on Mega Man Legacy Collection for 3DS, I encountered a sound bug I couldn’t track down. Basically, the first sound would either get garbled or not play properly, no matter what sound it was.
I had written the audio playback layer myself, but it built upon Nintendo's audio API for the 3DS. The playback system had to support continual streaming for things like background music and NES audio emulation (the latter of which I didn't write), plus short little one-off sounds for things like UI flourishes and the opening logo stinger.
Each of the 3DS's sound "channels" carried with it a data structure containing various flags and things to inspect its current state. One of them was a flag that indicated if that channel was active or playing. Nintendo's official documentation said that the flag was true if the channel was playing something, false if not.
I ran into a couple of bugs with this for which I never found the root cause because of time. Basically-- 9 out of 10 times when you launched the game, the stinger sound that accompanies the Digital Eclipse [developer of the collection] logo would glitch in some way-- it'd stutter or just not play at all. This was the first sound you’d hear in the game, and I discovered it always affected the very first sound played-- subsequent sounds were fine.
When I dug in to debug it, that aforementioned "playing" flag would be true, even though nothing was playing in some cases! We couldn't ship it that way obviously, and I was reaching my wits' end, so my solution?
When loading the game before playing that first stinger, play a second of silence. And that shipped.