John Haddock and I working late into the night before Eddo’s exhibition at the Beall Center at UC Irvine, CA in 2015. John was helping Eddo with illustrations for his GoldenStern pachinko game.
Vietnam Romance is a complex project with changing needs. We alternate between long periods of more methodical work towards the standalone version of the game, and intense sprints lasting weeks at a time for an exhibition or live performance. Each exhibition is different, sometimes with scenes excerpted onto different displays, sometimes with a single instance of the game running on multiple projection surfaces.
I've arrived at a workflow for implementing new features that looks like this:
- Meet with Eddo to discuss a new feature or level. Usually I come away with a handful of bullet points. Sometimes we work together to create other visual aids to clarify the design.
- On my own, brainstorm what the feature needs to do in detail. Sometimes think of this as a user story, a series of drawings, but often bullet points suffice. Because it these materials are for my own use I avoid being too precious with them.
- Sketch out a preliminary idea (often in a text doc) of how a new feature could reasonably be structured from a coding perspective, including some pseudocode.
- Iterate on prototypes, using stub functions with debug statements to gradually test and build out the feature. At this point I begin preparing a demo scene with a variety of use-cases and example scripts.
- Once basic functionality is in place, review how the prototype feels from a polish / game-feel perspective and iterate on my implementation / design. At this point I will sometimes generate some documentation and review it with Eddo before continuing, but it depends on schedule and on the feature.
- Once I am happy with the basic behavior, review the code and tidy it up with an eye towards maintainability. Is it human-readable? Is it structured logically? Are there dependencies I can reasonably get rid of? Is it resistant to human error or vulnerable to typos or missing references in the editor? etc.
- In general, I avoid adding many comments to my code, and instead give classes, methods, and variables descriptive names without abbreviation. There's no reason for abbreviated variable names when working with intellisense.
- I avoid leaving blocks of commented-out code. I've found I almost never come back to 'restore' them, and it can be confusing later when trying to identify bugs.
- Where reasonable I extract large blocks of code into their own named functions to prevent confusion later on.
- Once I am basically happy with the organization of the code, I start to consider what it will be like for one of my (non-coding) collaborators to work with the feature. I've found a good and simple stress-test is to try to set up a new level from scratch that incorporates the feature. This usually prompts some further iteration.
- Is it painful to set up, with a variety of interdependent scripts and prefabs?
- Is it prone to breaking if not set up exactly the right way?
- Is it easy to get started, just a matter of refreshing a prefab instance or dropping a prefab into the scene?
- Finally, I generate some documentation of what I did, and how it works, including a short tutorial video on how to set it up, and/or a video demo of the feature in action.
Sketch by collaborator Michael Luo illustrating a possible layout for a hub environment.
Having a background in art and design, this was the first time I had worked as a programmer on a large software project, and along the way I've come to rely on a variety of concepts like model view controllers, state machines, and the observer pattern to build code that is more dependable and maintainable.
We use a 'systems' paradigm for organizing many features of the game.
- Features are organized into a handful of related scripts, often using a model-view controller and a few other helper scripts.
- The scripts within the system are allowed to be fairly interdependent, with only a small handful of public, static methods and events made available for other classes to interact with the system.
Scenes intended for production follow a fairly strict organizational convention, with objects and scripts sorted into empty game objects for static environment elements, interactive 'actors', gui elements, and managers, among other things. Scripts pertaining to the sequence of events in a level are nested inside an object called "events" and put in chronological order.
To manage events that unfold in a level, at first I wrote a huge array of tiny interconnected scripts, which I imagined my teammates could use to add complex interactions to a scene. This proved to be extremely difficult to maintain in the long run, as control was distributed across many scripts in many different places. It was easy to introduce bugs by rearranging or deleting objects in the editor, and very difficult to track what had gone wrong.
Now in the interest of creating a single "source of truth" about the sequence of events in a scene, I usually create a single unique 'level manager' script for each scene that interacts with the more general-purpose game systems. Often implemented as a simple state machine (or even a list) which uses delegates with anonymous functions and editor-accessible unityevents for flexibility If a level has major logical divisions (a first and second section that are dramatically different, for instance) I may separate these into their own scripts for simplicity and brevity, and have one 'hand off' control to the other.