Now that non-binary is walking on its little legs and we are resting (no, we are not, but let us dream), we would like to share with you some thoughts, and give you a glimpse of what it meant to build this game in its various aspects. We will start at the lowest level, the technical level. This is LazyFox speaking, and I will tell you a bit about the choices and conclusions reached during the development with Unity.
WARNING: There will be some vague spoilers in this explanation. If you haven’t played the game yet, take half an hour for yourself and jump in!
I chose to approach the development by following an event-driven architecture based on ScriptableObjects, as I did before for Le Andande (you can find the devlog here). There were several new lessons learned, and some of them I will treat as best practices in the future:
- The Asset Usage Detector plugin is great for tracking which objects are affected by which events, both inside and outside the current scene.
- Initialization of variables created with Unity Atoms should always be done with a constant value: if it must be initialized by a manager instead, it is best to reserve a “special” initial value to signal it.
- When an event is a change event, set the replay value to 1 in order to always receive the value of the variable when a listener is added, otherwise always set it to 0. Ignore this suggestion at the risk of bugs that are very, very tricky to find (ouch!).
Using Unity Atoms does not eliminate the need for managers, sometimes expressed as ScriptableObjects, other times as MonoBehaviours. Remember to have a reference of your managers in all scenes, especially if they are ScriptableObjects! An interesting technique is the one explained by Tarodev (discovered after finishing the project, and yet very similar).
Influenced by both Unity Atoms and the my development background with React, the main structure of the project is different from what is often recommended, and separates assets into components. Each component is a self-contained folder that contains its own scripts, animations, sprites, and everything else necessary for its functionalities, while the components talk to each other through Unity Atoms events.
In order to bring the plot twist found at the end of the game, we had to joust in creating a 3d design that is actually 2d almost the entire time, thus suffering the complexities of the former without any advantage of the latter. Lucky us. In practice we had to brush up on the old techniques of z-ordering 2d elements to handle overlaps, while all the collision and bullet hell parts are handled in 3d with a lock of the motion axes to prevent shifting on the depth axis.
Another aspect we investigated was shaders: it was really fun to experiment with the various customizations possible on the main character! It was a training ground for algebra, discoveries about Shader Graph, and pure creativity, which should also be let loose now and then. Fun little fact: there is no sprite for the street, it is all calculated via shaders. Was that necessary? Obviously not. Was it fun to do it this way? Of course it was!
The tool we used the most for managing this project was Ink, which allowed an easy communicate between the programming and the text of the game. The idea was to use Ink files as the driver of all events in the game, and provide it the tools to do so through functions, tags and markers, thus following an approach similar to that of Yarn Spinner. In future games we have every intention of enhancing this approach even more.
The other tool we abused is Unity’s Timeline, to manage the various pieces of the animations. One of the challenges was to interface Unity Atoms with the Timeline, considering that Atoms variables are not animatable properties, and that Signals are not events. The solution we found was to dedicate an object to each timeline, set scripts on that object that would act as a bridge between the serialized properties and the Atoms, and conversely to trigger events in reaction to the Timeline’s Signals (which for all intents and purposes perform the exact same function). After getting the hang of it, it turned out to be a very pliable and effective approach!
What to do with these talks? One of the goals of owof is to share in order to grow all together. So: if you feel like talking about other aspects together or to dive deeper in some elements, we are here with open paws. And in a few weeks we will share something that we hope will make you happy.
Next week, on the other hand, QueerWolf will share some thoughts on narrative design.