Saving and Loading Games in Unity with Json.NET

As I expected game persistence turned out to be a bit of beast and has taken up all of my development time for the past 2 weeks. The result is not perfect and in retrospect I would have designed for persistence earlier. Nevertheless here are a few tips and ideas that may help you on your projects:

Not Persisting MonoBehaviours

The decision to move all game state out of MonoBehaviour classes was the right one when it came to persistence. I don’t have to worry about Unity’s inner workings. I persist and load my own entity classes, and inject them into MonoBehaviours which in turn instantiate all the needed graphics and UI. I had to do some minor tweaks to these components but for the most part, it just worked.

Persistence and Dependency Injection

I’m using Zenject for dependency injection so the persistence had to fit into that. After several false starts, I ended up with something like this:

Saving:

  • All top level objects, like Product, Company, and Finances have a persistence model associated with them (ProductPersistModel, CompanyPersistModel, etc), and a corresponding IPersister implementation.
  • When saving, we resolve all top-level objects from the container, create their persistence models using IPersisters, and put them in a final GameSave object. The majority of this is automated with the use of the container and marker interfaces so we don’t have to explicitly instantiate any classes.
  • Finally the GameSave object is serialized to a file using Json.NET.

Loading:

  • We load the GameSave object from file.
  • All persistence model objects are registered with container during initialization.
  • The persistence models are (optionally) injected into the constructor of the relevant class. So ProductPersistModel is injected into Product which can use it to hydrate itself.
  • The rest of the game is initialized per normal.

Now it might seem overkill to have specialized persistence classes. However it does prevent me from having to sprinkle the [JsonIgnore] and other persistence specific code everywhere. Do keep in mind that the persistence model only exists for the top level objects (aggregate roots) and not any “inner classes”. For those I fully serialize and deserialize them, which bring me to my next point.

Serializing Child Entities

As I mentioned, only the aggregate roots have a special persistence model and are resolved from the container. Other, inner classes are persisted fully and instantiated using Json.NET. That means there are certain restrictions on these which I actually consider to be good (DDD) coding practice anyway:

  • Any dependencies (like services) should not be passed to the constructor and stored in a member variable. Instead the entity should either inform outer objects via events, or dependencies should be passed to the methods that need them. For example the Feature class informs its parent (Product) when it has completed using an event called FeatureCompleted rather than explicitly holding an instance of Product.
  • I use a custom ContractResolver to get Json.NET to serialize all private and public fields and properties. It looks something like this:

public class GameContractResolver : CamelCasePropertyNamesContractResolver
{
  protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
  {
    var property = base.CreateProperty(member, memberSerialization);
    property.Readable = true;
    property.Writable = true;

    return property;
  }

  protected override List<MemberInfo> GetSerializableMembers(Type objectType)
  {
    const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
    var properties = objectType.GetProperties(bindingFlags).Where(p => p.CanWrite);
    var fields = objectType.GetFields(bindingFlags)
      .Where(f => !f.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any())
      .Where(f => !typeof(Delegate).IsAssignableFrom(f.FieldType));

    var allMembers = properties.Cast<MemberInfo>().Union(fields.Cast<MemberInfo>()).ToList();
    return allMembers;
  }
}

Types and References in Json

I have enabled 2 features in Json.NET that greatly simplify persistence. Under normal circumstances where you are using Json for communication between two systems this would be code smell and not advisable. However here I’m just using it for persistence and so it’s less of an issue:

  • TypeNameHandling.Auto – This option adds a $type property to all Json objects that have an ambiguous type (ie interfaces and generics) so that during desensitization the engine knows what type to instantiate. The downside of course, is that if a type gets renamed or moved you’ll have trouble.
  • PreserveReferencesHandling.Objects – This is an awesome little option. I was originally going to implement it myself before finding out about it. In short when this is enabled if a particular object is referenced from multiple places (which will invariably happen), Json.NET will serialize it once with a $id property and for all other references simply uses $ref property to refer to that instance. So during deserilization you won’t get two instances of something that is only meant to have one instance.
  • ReferenceLoopHandling.Serialize – This goes hand in hand with the previous option, essentially allowing you to serialize a circular dependency (which while might indicate poor design, is sometimes unavoidable).

Dealing with Database Items

Like any game some assets like text, images, definitions, etc. are loaded into the game from a data store (in my case files). For these it made sense not to persist them with the rest of the game but rather load them again from the database. This way any updates to those assets would be correctly applied to a game that was previously saved.

Doing this is easy enough with Json.NET. I created a new interface to be put on any database class, called IDataStoreItemWithId which contains an Id property. I then implemented a JsonConverter class that uses the database to save/load such objects:


public class DataStoreItemConverter : JsonConverter
{
  readonly IDataStore _dataStore;

  public DataStoreItemConverter(IDataStore dataStore)
  {
    _dataStore = dataStore;
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    var item = (IDataStoreItemWithId) value;

    var jObj = new JObject { { "id", item.Id } };
    jObj.WriteTo(writer);
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    var jObj = JToken.ReadFrom(reader);
    return _dataStore.GetById(objectType, jObj.Value<string>("id"));
  }

  public override bool CanConvert(Type objectType)
  {
    return objectType.GetInterfaces().Any(i => i == typeof(IDataStoreItemWithId));
  }
}

What’s Next

I still have a bunch more to do on persistence but I think at this point I have hit all the pain points and now it’s just a mechanical process of getting everything into the persistence pipeline and tested. There is probably another week of that left to do.

I have also been moving forward on the art front with some exciting new progress, but that’s a topic for another post…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s