Having trouble loading skill levels with OnAfterDeserialize! Please help!

I use a BinaryFormatter to save a bunch of information about my player into a file, then load it again later. I’ve been doing this with no problem for a while, but I just tackled a new system and it seems to not be working, and I can’t figure out why.

I have this PlayerData class, and it stores information (relevantly, it stores the Name as a string, and it stores a Dictionary of SkillLevelData. The dictionary uses a GUID for each skill as the key, and the value it stores is a struct called SkillLevelData which has very simple data - the guid, the level of the skill.)
Because unity can’t serialize a dictionary, I use ISerializationCallbackReceiver to store the dictionary as two Lists.
This seems like a simple implementation, but for some reason, in OnAfterDeserialize, all of the data seems to be completely blank. I know that it does successfully serialize and deserialize, because my code later references other saved data - like the Name, particularly. But if I ask the code to print the Player’s Name during OnAfterDeserialize, it comes up as null. What’s going on? And more importantly, how do I fix it?

    [System.Serializable]
    public class EntityData : ISerializationCallbackReceiver
    {
        public string Name;

        [SerializeField] private List<SkillLevelData> _skillLevelData = new List<SkillLevelData>();

        public virtual void OnBeforeSerialize()
        {
            _skillLevelData.Clear();

            if (SkillLevels is not null)
                foreach (var kvp in SkillLevels)
                {
                    //Debug.Log($"Saving into list: {kvp.Key} - {kvp.Value.RawLevel}");
                    _skillLevelData.Add(kvp.Value);
                }
        }

        public virtual void OnAfterDeserialize()
        {
            SkillLevels = new Dictionary<string, SkillLevelData>();

            foreach (var data in _skillLevelData)
            {
                Debug.Log($"Trying to deserialize skill data: {data.GUID} - {data.RawLevel}");
                SkillLevels.TryAdd(data.GUID, data);
            }

            Debug.Log($"Entity {Name} deserializing. Size of _skillLevelData: {_skillLevelData.Count}");
        }

Code has been simplified to remove unnecessary details. This is what SkillLevelData looks like.


    [System.Serializable]
    public struct SkillLevelData
    {
        public string GUID;
        public CappedValue RawLevel;

        public bool IsNull => string.IsNullOrEmpty(GUID);

        public SkillLevelData(string guid, float level)
        {
            RawLevel = new CappedValue(level, 1000);
            GUID = guid;
        }
    }

CappedValue is a class that just contains a float for a value and a float for its limit, then handles making sure it doesn’t go over that limit. It is also marked as Serializable.

Any help at all would be greatly appreciated. Thanks for reading.

Are you sure about this? I ask because your SkillLevelData does not contain an no-argument constructor, something I believe is essential to any kind of deserialization process.

Otherwise the generic deserializer would know how to make one of those things. AFAIK this requirement still applies to struct as well as class …

1 Like

The BinaryFormatter is part of .Net and doesn’t use Unity’s serialization. ISerializationCallbackReceiver doesn’t do anything when using BinaryFormatter. How does BinaryFormatter and Unity’s serialization come together?

I put your code into a MonoBehaviour and into a scene at it was working fine, EntityData.Name was logged properly in OnAfterDeserialize.

P.S: Don’t use BinaryFormatter. It’s not guaranteed to be able to read previously created data after a Unity update and makes update to e.g. save data cumbersome. It’s also been phased out and will be removed in .Net 9, you likely won’t be able to use it in further versions of Unity that are using the .Net runtime.

It’s a struct, which has a default no-argument constructor.

2 Likes

That’s really good to know, and would absolutely explain why the data is just being lost. (I guess I forgot to mention - my SkillLevelData is being completely lost when I load a character, even though the other information seems to save correctly. That’s the actual problem I’m trying to fix, I just suspect it’s related to why I’m not getting any data in OnAfterDeserialize).

Unfortunately it’s outside of my scope right now to switch save formats (This project is being done on a static Unity Version, so I won’t need to deal with deprecation), but even if I did, I suppose I’d have the same problem, wouldn’t I?
What’s the solution to this? How can I serialize a Dictionary for the purpose of saving to a file?

I mean BinaryFormatter is a security risk and you absolutely should drop it for that reason. Microsoft say not to use it under any circumstance. And there are far more modern and more powerful serialisers out there these days.

I have no idea about the Binary Formatter API but many serialisers let you run some sort of extra processing on objects as they get serialised/deserialised. So I can imagine you can implement something of the sorts that calls the necessary methods of ISerializationCallbackReciever during serialisation or deserialisation.

It’s a pain, but right now I’m thinking I’ll just have to manually call my “preserialization” function every time I save or load, since I’m doing it at very specific times anyway. it feels a little grody, but I suppose it works just fine.

Thanks for the help, everyone. I’ll look into switch binaryformatter when it comes down the line

Eh, I’ll still love you… I don’t think it’s unreasonable to have to decompose it into simpler lower-level data structures (eg, paired arrays) to go off to serialize… considering the bit-flipping bit-wiggling shenanigans we had to pack our data into back on Gameboy / DS days, heck, if you already wrote the code, just call it all ready… I give you my blessings! :slight_smile:

1 Like