Serialization/Persistence beyond basics

Most tutorials and other resources only cover very basic serialization. Like a single String, or a list of Ints with a BinaryFormatter. But I’d like some help with how to deal with more complicated situations.

Below is a simplified example of my code, where I want to serialize List worldTiles:

// Singleton
public class ShopLayout
{
    public GameObject worldTile;    // Prefab
    public List<WorldTile> worldTiles = new List<WorldTile>();
}

// Represents a single tile in the world grid
public class WorldTile : MonoBehaviour
{
    public Vector2 coordinates = new Vector2();        // x & y position of this tile in grid.
    public PopulatedWorldObject populatedWorldObject;
}

// Some WorldObjects like chests and shelves also contain Items.
// It made sense to make WorldObject & Item children of same parent.
// But storedItems could be moved to WorldObject, and this class removed.
public class PopulatedWorldObject : MonoBehaviour
{
    public WorldObject worldObject; // Sprite is populated in Inspector
    public List<Item> storedItems = new List<Item>();
}

// Can be anything placed in the world. A rock, wall, door, chest, shelf.
public class WorldObject            // Populated from list of WorldObjects in a ScriptableObject asset
{
    public int ID;                    // Unique identifier     
    public string title;            // Name of the Object
    public Sprite sprite;        // Sprite
}

// Items that the player can add to inventory or use.
// Some items come from a WorldObject.
    public abstract class Item
{
    public string slug;              // Unique Identifier
    public string title;            // Name of item
    public Sprite sprite;           // Sprite
    public float durability;        // While the other properties will not change ever, durability will decrease
}

// Several classes derive from Item, such as; Weapons, Armor, Food, etc
    public class Weapon : Item        // Populated from list of Items in a ScriptableObject asset
{
    public float damage;
}

Some issues I have with this:

  1. Should BinaryFormatter be used for cases like this, or are there better options?
  2. Is it recommended to serialize a list of custom class, containing a custom class, containing a list of a custom class? It seems like polymorphism hell.
  3. Sprite can’t (and probably shouldn’t) be serialized. How to handle that? Do I mark Sprite as [NonSerialized], and then when I deserialize, I use the unique identifier (ID or slug) to Resources.Load the Sprite? But everyone seems to advice against Resources.Load and GameObject.Find.
  4. Do I really need to serialize WorldObject & Item? Should I create a reference to the ScriptableObject? But how then to handle durability?
  5. How to smoothly handle instantiating of prefabs and populating them with deserialized data / classes, setting parents etc?

I think I could manage to write some working code, but since Unity is fairly new to me, I learn so much from tutorials and posts.

If someone could point me to some good resources (such as this), or has some advice to push me in the right directions, it would be very appreciated.

A couple of notes:

  1. Don’t use binary serialization, use JSON. It’s human-readable. You’ll really thank yourself when you’re debugging and you can pull the data up in a text editor and tweak it to your heart’s content. Don’t use Unity’s JSON: it is critically-flawed inasmuch as it fails to handle Dictionary objects. Not sure why Unity chose to live with this crippling limitation. I use LitJSON but there are plenty of others. Each one is supposedly “compliant” but in practice they ALL have little warts and limitations.

  2. Some folks like making custom objects that they turn into JSON, or just blast a Dictionary of key/value pairs through.

  3. Serialize the name of the sprite and then have a factory method that can spin it back up at runtime when you deserialize. That requires the names be unique, plus you need to have a strategy to fall back to an available sprite if the JSON tells you to load a name you don’t have.

  4. WorldItem: Not sure what all is in here, but if you can strip it down to a class that contains just the stuff you need persisted, that can simplify your serialize/deserialize needs.

  5. Generally you won’t need ad-hoc parenting to be serialized out as such. If your gameplay requires the state of GameObject hierarchies to be preserved, you need to come up with a strategy, preferably one using unique strings, to put your GameObject trees back together. In practice, JSON data will be read and used to instantiate pre-made extant chunks, i.e., prefabs.

2 Likes

Thank you, that is exactly the type of advice I was hoping for.

My item database class is actually deserialized from a json file (albeit using the native json utility) into a ScriptableObject list, and later I Resources.Load the sprite using the slug string. It felt a bit unreliable (because of dependency on strings), but it’s worked so far.
I didn’t consider json for game saving due to it being “unsecure”, but that is really not relevant at this point, and readability should be a huge help during debugging as you say. Seems like a good plan.

Yeah, I read about the custom objects here. I thought that it would be preferable to avoid using them (for maintainability), but they seem to solve a few other issues as well, so they may be necessary.
The Dictionary method sounds a bit scary to me. =)

I’m sorry, there is no WorldItem class in the code. If you mean PopulatedFurnitureItem, I do agree.

What worries me is, that in order to instantiate the prefabs after deserialization, I need to consider every single property (that may have been changed between previous instantiation and serialization) in every single Component attached to the GameObject (and its children) I want to recreate. But I guess there are no shortcuts.