Save yourself some pain, don't fight against the platform you're using.
Our game, The Fall, is a trilogy. With each instalment, we're essentially iterating on the same core design and tech, which provides an opportunity to optimise some of our design strategies after each instalment. Not surprisingly, this process has mainly involved learning to lean more on Unity's strengths, and letting go of the tenancy to find custom and often over-complicated solutions to design challenges. With our recent announcement of The Fall Part 2, we thought we'd take a moment to share some of the processes with you. First however, if you’re curious about what it is we do… We have a teaser. Hot off the presses (do they press these things? How do these computer things work?)
In my opinion, one of Unity's main strengths is its ability to allow for a fairly loose, easy, flexible and fast implementation of game design ideas, so that a given dev can quickly identify whether or not something is fun. Tragically, it's all too common that I see more experienced programmers attempt to shoehorn design patterns into Unity games that, in my opinion, really work against the strengths of the development environment. I've seen programmers create scenes at run time, for example, because they somehow think that having designers create scenes and cross-wire GameObjects in the inspector is sloppy. There's no question that a more loose design philosophy can create more bugs, and it's important to strike a balance between being able to hack things in where you need them, and avoid creating sloppy software that's easy to break. This article will be a few thoughts I have on that topic, and a few examples of patterns (in no particular order) that I've leaned more heavily on while moving forwards from The Fall, and onto The Fall Part 2.
One final note... We have a small team so there's not much room for people to make a mess of things. I acknowledge that every team is different.
The Core Goal
A lot of my design decisions are centred around one core goal-- I like to be able to start playing the game, from any point in any scene, from the editor. This might seem simple, but most projects I've worked on in the games industry at large haven't been this way. Small changes can sometimes only be tested after lengthy loading processes and time to return to the targeted area through gameplay or cheat menus. This is a tragic waste of time.
The Fall is primarily an adventure game, which means that the game is littered with interactive objects that all require unique scripting. For example - player walks to the object, turns left, waits for a second, a specific dialogue moment plays, a custom animation plays on completion, etc etc. While building each of these moments, I need to be able to test them quickly, and the quickest way to do that is to move the player character right next to the object that needs testing in the editor and then hit the play button. There are a few things that help here.
1. Common functionality, and where to put it.
Every game has common functionality. Input systems need to be initialized, in game menus are likely the same in all gameplay areas, etc. There's a common design pattern where the game needs to start in a "loader" level that initialises all of that. However, if you want to be able to start playing from any point in the game, that design pattern is unwanted.
All of my common functionality code is set up on a prefab object that is copied into every scene that needs it. Copying is sloppy, right? Well thankfully, the prefab system can help here - if I update one instance, every other instance in the game should update.
Every common functionality related script that should only have one instance of itself in a given scene (like the in-game menu, for example) has a public static variable of it's own type, and the given instance sets that value to itself when it starts up. When a new scene is loaded, the old instance is simply unloaded and the new instance takes its place. That way, I can start the game anywhere and access the in-game menu, or anything else, if I need to.
2. Building scenes and relying on the inspector.
We have a ton of stuff that requires custom scripting, and all of those objects are placed in the scene with their related stuff (animators of other objects, colliders, etc) plugged into them via the inspector. There's some room here for error, as forgetting to plug something into something else will cause a soft lock. However, I've found the ability to lay out gameplay objects visually and connect them tends to be a very fast and intuitive process. Again, if you learn Unity's features, this process can be improved.
I'm learning to rely more and more on custom gizmos to help me navigate my scenes. Drawing arrows between connected objects, or even displaying custom icons when important variables are null has ended up saving me some time. That way, having objects connected via the inspector offers a great bonus to show comprehensive design overviews, as opposed to just being a pain in the ass.
I initialize as much as I reasonably can or write simple testing code inside Awake or Start -- that way, if I forget something that isn't hooked up right in a scene, I get errors as soon as I hit the play button.
3. Most objects need "cheat" code, but for the love of god, it needs to be build safe.
Zipping around the world to test functionality here and there can be tricky when objects are interconnected in a long sequence. In The Fall Part 1, I simply added a public bool called "cheat" to many objects that I could switch on, which, on start, would cause, perhaps, an item to be added to my inventory, a boundary to open, a state to change, what have you. Needless to say, I sometimes forgot to turn these OFF before cutting a build, and lots of time was lost as a result of this type of idiocy.
Simple cheat functionality can be handled with custom inspector buttons, which obviously ensure that they'll only ever be triggered in the editor, when the developer wants them to be triggered.
For more complex functionality that can be triggered with a simple button press, or if you'd like to cheat outside of the editor, #ifdefs are your friend, google them if you don't know what they are. Basically you can make it so that some code only compiles if you've turned a global "cheat" flag on, or if you're on a specific platform (like the editor) Additionally, when that flag is on, you can have large, brazen text somewhere that says "DEVELOPER MODE" or something, so even my dumb ass wouldn't miss it.
That's all for now! I could go on, but I'll wait to see if anyone finds this of much use. Don't hesitate to let me know if you liked it, or if you think I'm everything that's wrong with game development (the internet is a divisive place.) If you’d like to keep in touch, you can always find me on twitter @OverTheMoonGMS