“Data management” might not be the sexiest term to throw around, but it’s proven to be one of the most important considerations in the development of our past two games, Mythos Unbound and Saeculum. It is fundamental to game development and can drastically affect how quickly and easily a team can implement and modify ideas.
But what exactly do I mean by data management? A game like Saeculum is filled to the brim with all sorts of information that is critical to how the game functions. For example, during a cutscene, a character may need to speak a line of dialogue. To accomplish this, we may need to know a number of things, such as:
- Who is speaking?
- Which voice acting audio file do we need to play?
- What is the written text for this line (for, say, subtitle purposes)?
- Which animations will the speaking character emote?
- At what times during the line will these animations occur?
Games are absolutely stuffed with information like this, whether it be fun gameplay systems like combat (“How much damage does this attack do?”) or less exciting backbone infrastructure (“Where is the save file serialized?”). All of this information can be considered “data,” and how well a developer manages it can make their life easy (relatively speaking) or extremely painful.
In our case, it was the latter, at least for our first projects. For the most part, we simply embedded data directly into the game objects that needed it, and these objects were saved within their respective levels (which are called “scenes” in the Unity editor). It seems straightforward and intuitive, but it’s a bit naive and came back to bite us rather hard.
To give an example, in my combat posts (Part 1 and Part 2) I mentioned howSaeculum includes a client system, where clients can join the player’s entourage to help in various tasks. A “client” consists of a ton of data, such as their name, character model, competency in a variety of skills, and more. When it came time to give the player a client, a designer could simply create that client within the scene and fill out all of these data fields.
That solution may work in a more simple game, but when your game’s scene count crosses into triple digits, you start to run into problems. When a designer is balancing the game and needs to change a client’s stat values, now they need to remember which scene each client is added so that the corresponding scene can be loaded into the editor (which can be a nontrivial wait when the scene is filled with large amounts of art assets). Then, they have to comb through the scene to find and edit the client’s data.
Let’s take it a step further. What if, due to the narrative and the player’s actions, a client may join at different points in the game? Does the designer have to reimplement the same client across multiple scenes? Now when a data change needs to occur, multiple scenes have to be updated. What if the designer makes a mistake and the two clients no longer share the same data values?
Now we’ll make it even more complex. Maybe, in another scene, the sister of a potential client will react differently to the player depending on whether or not that client is in the player’s entourage. How do we determine if that client is present? We could simply compare the name of the client to the name the sister is looking for, such as “Gaius.” But what if the name later needs to be changed, and the client is now called “Marcus” while the sister is still looking for “Gaius?” Now, in addition to updating each scene where a client can be added, we now need to edit each scene that references that client for other logical purposes.
Clearly, this is a messy problem that can get out of hand very quickly. And let me tell you from personal experience, keeping track of all that data across all of those scenes is not fun at all.
How did we solve this problem in our latest iteration of Saeculum? Well, I found out about a little thing in Unity called ScriptableObject, and boy, do I wish somebody had told me about it earlier.
The name might not convey much, but a ScriptableObject can be thought of as a sort of data container, an asset no different than an image or audio file. However, rather than storing information about pixels or notes, they can be written to store custom data (like, for example, a client). Since the data is saved as an asset, it is integrated into Unity’s existing data management systems (which, trust me, is very nice), and can be easily sorted into folders like any other file. Objects within scenes can then use these data assets as references when performing their operations.
This solves all of the problems I outlined in my client example. Rather than writing the client data directly into objects in the scene, we can now extract the data and wrap it up into its own ScriptableObject data asset. When a designer needs to update a client, they simply find and edit the client’s data asset within a neatly organized folder. We can ensure data consistency across all scenes since in-scene objects will all refer to the same source for data values. Also, rather than relying on variable data fields as a point of reference (such as in the aforementioned client name comparison), game objects can instead simply compare their actual references to data assets, essentially checking to make sure they’re referring to the same “thing” regardless of any one data field.
That said, using ScriptableObjects isn’t the only solution to the data problem. Many developers prefer to manage data in a way that isn’t quite so dependent upon native Unity structures, or is more universal and easily manipulated outside Unity. For example, in his Unite 2014 presentation, Zach Aikman of 17-BIT explained that the map data in their Unity game Galak-Z is stored in a format called JSON, which is commonly used even outside of games. I’ve also seen developers use XML, spreadsheets, and custom file types to store their data. However, for us, the advantage of keeping everything within Unity (and thereby take advantage of Unity’s built-in data editing tools) was too good to give up, and we really haven’t seen a disadvantage to this approach yet.
So far, being liberal with ScriptableObjects and keeping data out of the actual game scenes has been a big win for us. In particular, it has greatly benefitted our large data-dependent systems, like dialogue and voice acting (which we will cover in future blog posts). It has been much easier for designers to change game values, and it has made our systems more robust and flexible so that I can more easily meet new feature requests. We plan on continuing this approach to data management in our future projects, and I encourage those new to Unity to consider it when building your next game!