Last week, we talked about how we approached refactoring our Food Fight code from a prototype to a scaleable codebase. This week, we continue that discussion and show how we made the transition to where we are now.
Programming Behaviors & Embracing ‘The Sandbox’
A behavior, in the way we’re treating it, can be almost anything. It could be a simple script to just make sure the item is affected by gravity. Or it could be a script to show a message when the item is picked up. Or maybe it provides a ‘homing’ feature that draws it closer to the player, or a ‘throwing helper’ that calculates a smoother release on a throw.
But what all the behaviors have in common is that they somehow need to use the information about when an object is picked up, put down, hits a wall, and so on. In this way of structuring our code, it’s possible to write small modular chunks that don’t have to notice or check for other behaviors or other code that might ‘undo’ what it’s trying to. Every behavior we write doesn’t necessarily “know” about what other behaviors are affecting the object, it just does its own job and that’s all.
Not only does this leave a much more maintainable system that’s easier to debug, it also allows us as game designers to think of each item as a ‘sandbox’ of different behaviors we can add to it, and so we can easily check the results to see if they add anything to the game. For instance, we have a separate ‘edible’ behavior for some of the food, which plays an effect and tells the player-manager when a food item is ‘eaten’ (or just shoved near the players face). A ‘splatter’ behavior leaves behind splatter marks when an item collides with any static part of the level.
But a menu item — which we want the player to still be able to ‘physically’ interact with but neither eat nor throw away — wouldn’t need either of these. Instead, it might have a ‘show text’ behavior to show something to the player when picked up and another behavior to ‘snap back’ to its original position if it’s released.
These different behaviors all use or are triggered by the same events — being picked up with the controllers — but can then share most of their code while differing only in what they do with those events.
Most importantly, this more divided approach will be what we need to better grow the game both in gameplay and content since so much of the game is going to derive from largely the same types of actions. We are now at the point where we can get all the same gameplay from our food and other items, but the code we’re using is much more modular and cleaner to debug.
Conclusions & Takeaways
Not all projects take the same arc, but it will almost always pay off to prototype quickly to determine what “kind” of game or software you want, and then strip it back to build it out in a broader and more modular way. We are now taking the same approach of refactoring to our “character” base and will end up allowing us to use the “Food Goblin” (which right now just throws food at the player relentlessly and little else) and apply similar architecture of modular behaviors to provide a variety of different possibilities.
On the other side is recognizing that often enough “done is better than perfect” and that until an area of the code becomes hard to work with, it may not need a rewrite even if some of the code was written fairly quickly. In both cases of the items and characters, we found we were going back over and over to make more changes and adding complexity each time. It’s in cases like these where it’s important to step back and realize what could be possible if you take the time needed for a refactor.