Structuring data for large Unity games

In Unity’s tutorials and samples data is not separated from code.

From Scope and Access Modifiers:

public class ScopeAndAccessModifiers : MonoBehaviour
{
public int alpha = 5;
// etc.
}

It's a trap!

As your project gets larger, this approach is going to cost you more and more time, and eventually a major refactor to fix.

Problems:

  1. Unity doesn’t provide a mechanism to read only parts of data from a prefab. Suppose in the earlier example there was also a reference to another prefab. Not only do I get the other prefab, but everything that prefab references too, meshes, audio, animations, whatever. I might be pulling in many megabytes of data where all I wanted was an int. This is a huge problem with loading save games, presenting only icons or names from a list of many objects, or selecting from dynamic content.
  2. The editor is very slow to update with many variables in the inspector window, especially during play mode in busy scenes. I’ve seen it take 10 milliseconds per frame.
  3. There no heirarchy of values. If I want every enemy to have 10% more health, I’d have to open every unit prefab in the game and type in the new values.
  4. It’s hard to edit, especially when the editor is not responsive, as it becomes in larger projects.
  5. It’s hard to cross reference. For game design I want to see all the health, damage, and economic values on the screen at once in one place, and change them in that one place. You can’t do that with values in components scattered around the project.

What about Scriptable Objects?

To be fair, Unity’s recommendation is to use Scriptable Objects for your data. However, this only really address the first two problems I listed, and creates new problems of its own.

  1. Instead of everything in one place, now the data for an enemy unit might be in 10 different files.
  2. There’s no easy way to find out which files are in use, so it’s always risky to delete something. Are you SURE that file is not in use somewhere? Usually no, so you end up with a mess, especially with larger teams.
  3. I can’t backwards-reference the object that uses the data from the data itself. If I change “StartingHealth_Low.asset” which units will inadvertently have their health changed, and is that even the right file to begin with?

A better solution

I couldn’t find discussion of a better way to do it and wanted to present my approach.

  1. Create one or more StreamingAssets directories
  2. Any data that a designer might want to change should be in some file format specifically made for data, that has a modern editor. For example, Excel files. JSON, etc. Excel is good if you have a table for data, for example all units and all properties for all units. JSON is good because it’s easy to update.
  3. Create a heirarchy of data files that override each other. For example, I have a JSON that defines (among hundreds of other things) how much data the player’s bow shots do. I have another JSON that overrides the first one, in case the player plays on a different difficulty level. It also allows effortless modding.
  4. On game startup, load the file into a dictionary where the key is whatever the key the JSON uses, and the value is a small class called GameplayVariableLookup that contains the data.
  5. For each object in the game that needs the JSON, look up from the dictionary using the same key and get and keep a reference to the class. This makes lookup O(1).
  6. In my case, GameplayVariableLookup.GetValue() also takes a multiplier and addend. This lets me override values on a per-object basis, for example 10% more health for a particular unit.
  7. Have a hotkey that can hot-reload values, so designers can try different values without restarting the game.
  8. To make things easier, I store keys in an enum that I match up with keys in the JSON file. This way the compiler can keep me from mis-typing key values, but isn’t essential.

Leave a Comment

Your email address will not be published. Required fields are marked *