The equation in discussions of coding quality runs something like:
“Yes, it’s faster to write code that is bad. But whoever comes to use this code later will spend far more time trying to maintain it. And that person using it later is most likely you!”
leaving aside for the moment the impossibility of clearly defining bad versus good code, this argument seems at first glance to be sound. For example, if you it’s faster to not add any comments to your code, but uncommented code is definitely much harder to come back to later.
So what does that mean when you go to write code for a game jam? with just 48 hours to build everything this must be an example where ‘fast’ is the priority.
re-reading this post it kind of assumes you know what game jams are: They’re a weekend event where you and a small team try to complete a playable game in 48 hours. They generally start Friday afternoon and end Sunday night, keep this is mind as I describe the pacing
After seeing some tips from a Unity employee where she mentioned ‘remember the code you write in a game jam would never pass code review,’ I tried to really live by this principle in the recent Cartoon Network Game Jam. I’d like to discuss some ways I tried to write things ‘quick and dirty’ and how it worked out:
Debug.Log lines everywhere
Huge success just adding Debug.Log lines around what I was doing, before I was even sure I would need them.
I actually did this right off the bat, logging stuff like ‘I’m trying to match strings, here’s my progress’ without that bit actually breaking yet (of course it did break later so hashtag success).
This isn’t faster than not logging anything, but is faster than writing careful code comments.
The only downside was if the logs were inaccurate for some reason (not checking the right variables or whatevs) or if I ended up logging so much it drowned out the important info. Unity by default displays the last log message at the bottom of the editor, so it’s kinda annoying when that’s no longer useful info.
Generally even logfile chattyness is okay as long as it’s not happening on every frame.
The benefit here should be obvious: if you just have the customer say a string you just fed in, you know it’s working, you don’t have to muck with data storage or parsing.
… good, right?
The problem here is one of negative feedback: the better your game is doing at the gam, the more likely you’re going to wish you had better control of the dialog.
If you end up with a game that people want to play for 5 minutes (that’s a massive success btw), your 1 or 2 hardcoded strings will probably look more like an in-code dictionary.
Worse, with my most recent game I really wanted associated info with bits of dialogue. My game is a sandwich shop, I wanted each customer dialog to also include an ‘order’ and some indication of which character sprite should be asking.
I ended up with a 2-dimensional array that took several dozen lines. Since C# doesn’t let you store mixed types in arrays, I had to store the integer for sprite selection as a string, then parse that back to an int.
So I had to write a lot more code to handle this big gross blob than I meant to at the start.
I think basically there might be another lesson here entirely:
life lesson 1: Make sure it’s always easy to add dialogue to your gamejam game
Dialog, just like loop lines in film production, is a cheap and easy way to paper over small issues with your game. you’re going to find yourself wishing you could make the NPC’s say ‘be sure to click that blue gem at the bottom to spend your spirit tokens!’ on Sunday, so hard coding dialogue is probably not that smart.
when you’re storing sound effects in an array, it’s nice to take in some strings, use a
switch statement and make it easy to call the right sound effect at the right time with something like
Buuuuuuuut it’s a lot faster to just toss them in an array, then use a legal pad or extra .md file to keep track of which index is which sound. It also removes one bit of work from your code: you have a much simpler PlaySound function to keep track of.
This also goes for stuff like hard-coding the length of arrays. First of all c# doesn’t let you mutate the length of arrays at runtime, so your array should always have the same length, also for most arrays except perhaps those that contain dialogue as noted above, you really shouldn’t be changing your arrays late in the weekend. Want to add a third attack move to your character? Should have thought of that before noon Saturday.
Finally, array index errors are relatively easy to debug: the errors thrown tend to point right to the problem.
When your game is being judged, you will be very lucky if someone plays it for 2 minutes. In that time, if a modal pops up and says ‘oh my god a random monster just appeared?!?!11?!?’ then the player will know that this was, as part of the game narrative, a random event.
…does it have to be random tho?
in general anything you might want to code that feels like cheating is probably the right thing to do in a game jam
No! And in fact testing stuff that contains random number generators is a hassle, obviously. You can try locking the RNG with a
seed but then you haven’t really tested it. Even testing it with min and max values isn’t certain (maybe 1 of your 10 array entries is broken, but not the first or last) isn’t surefire.
Worse, what if when the 1 person whose opinion matters at all tries your game and something possible but unlikely gives him a bad experience? He wanders the tall grass for 10 minutes but no encounters trigger.
I love cruft and noise in games, but for anything other than visual touches, it’ll actually be a better experience for players if you hard-code the path they will follow. One good example for something totally cosmetic: the npc’s in my most recent game said various phrases at random if you failed their task. When I tried it out with each one picking a random phrase, the first 4 all said the exact same thing. So I ended up wishing I’d just ordered my array and stepping through it.
Unity makes it real easy to make calls between scripts. Just declare a Public var of type
otherscriptname, drag the gameobject into the new field in the inspector, and boom, you can call any public function on the otherscript.
But surely when you’re hooking up GameManager to PlayerHealth, and then later you hook up PlayerHealth to GameManager, your alarm bells will go off that this is a Bad Way To Do Coding.
Here we get to some basic coding philosophy: If this is a bad way to code, you have to know why it’s bad.
When working on code that is a component of a larger codebase, decomposing your code into objects is critical: if someone wants to mess with the ‘audio’ object later, they probably don’t want to have to import the ‘scoreManager’ object. However, if you’re trying to use code you wrote during a game jam on another project later without editing it…
Interdependence/Circular Referencing does make code a huge hassle to refactor later, but even so you don’t necessarily want to optimize for a problem you’ll never face: if the audio played is dependent on the score, and the score manager needs to be able to play audio, the fact that both scripts reference each other reflects real interdependence.
Anyway this saved me tons of time and I don’t regret it at all.
Some “timesavers” not even I was dumb enough to try
Functions named “DoTheThing()”
partway through the gam I looked up a code example that used ‘foo’ and ‘bar.’ Then it used ‘Foo()’ and ‘Bar().’ note that the variables and the functions were unrelated in this code snippet, creating a pattern in the code where none should be.
While this is unforgivable in a tutorial (I’ve got about 10,000 words in me about why ‘foo’ and ‘bar’ should be banished from the programmer’s lexicon), it’s also just a turbo-bad idea in a game jam.
This gets to a basic assumption which is that someone else is going to be looking at your code. It might be another programmer at this jam, but even if you’re solo it might be you trying to fix something late on Sunday.
If you fill your code with ‘MoveTheThing(float x, float y)’ you’re making it almost impossible to figure out what’s broken when you come back to it. Any logs pointing to this function will be similarly unreadable. So yeah, don’t do this.
If you’re too tired to come up with with helpful names, go to bed. Any code you write will be more trouble than it’s worth.
just one huge script file
This one should be kinda obvious but it doesn’t actually save any time to keep everything in one huge file. If you notice that GameManager.cs has a lot of audio control in it and you should maybe create AudioManager.cs, this might be a refactor that’s not worthwhile while you’re working full-speed, but some stuff is clearly separate from the beginning, like enemy AI and the UI/score manager.
The worst thing about scripts/classes longer than 100 lines or so is how hard it is to to view them all at once. I find myself scrolling up and down constantly to figure stuff out.
Destroy instead of object pooling
I actually think of
Instantiate as a kind of insidious anti-pattern. Sure there are times when you can’t avoid it, and it does make it easier to send your project around if it’s just script files that instantiate everything they need from prefabs, but in my experience this kind of stuff is just asking for trouble.
Is it because I don’t understand these functions perfectly and often fuck it up? Yes it is. So, I assume, will you; if you’re a journeyman Unity dev, I’m a bit surprised you’re reading anything I’m writing.
Yeah it’s true that
Destroy kicks off garbage colleciton and can hurt performance, but that’s not the reason to avoid this in a game jam.
‘Instantiate’ creates an object that, unless you do stuff with it right then is ‘lost’ in your scene requiring you to find it by tag or brute force later.
Instantiating an object with a collider inside another object causes some crazy Unity behavior.
approximately 100% of all glitches people post in AAA games are due to object instantiation errors
Above all: this is a very short, small-scope game. If you can’t figure out what objects need to be in the scene before the player starts playing, you’re working on code that’s too robust for the task at hand. If you’re not certain how many cacti you’ll need to spawn, just drop an extra dozen into the scene. Make them the children of an empty object so they don’t clutter the heirarchy, but for god sakes don’t waste time mucking about with the careful management of instantiate.
All this isn’t to mention the problems that arise when trying load or re-load the scene, or call public functions on an instantiated object that isn’t present at build time.
So, this is Agile
I believe in game jams because they’re a microcosm of what it’s like to be a ‘real’ developer: Not everything is going to get fixed. Not every class you write will be something that people want to hang in museums.
When GitHub first came on the scene there was enormous pushback from people who were afraid to show off code. Often they described projects they had worked on for months or years as ‘unfinished’ and only a ‘finished’ project was ready for people to gawp at.
Git has had it’s own noxious influence in enabling a culture of calling out ‘bad code’ but it’s also helped reveal the reality of development: this world runs on bad code that everyone thought they were going to re-write years ago.
There are certain things I do in game jams that would never fly in my day job, but the best lesson to take away is to use use the tools you have available in a way that makes sense at the time
Even if you end up spending all Sunday debugging the code you wrote Friday night, you’ll have learned something about process. Instead of wasting months producing code you regret, you’ll just have burnt a weekend learning this valuable lesson.