I don't think I understand Custom savers

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
User avatar
rykaan
Posts: 75
Joined: Tue Feb 11, 2020 6:22 am

I don't think I understand Custom savers

Post by rykaan »

Hi Tony,

Sorry to bother you again so soon but I'd been working through two issues concurrently and I've hit a dead end with this one too.

I've been setting up savers in my scene for monsters being killed/items picked up/etc. The pre-written scripts all work fine, having no issues there. But I am intending to use custom savers for information like the contents of the Inventory and what state Monsters are in (if the player killed them or saved them etc).

I have a feeling I'm not writing these custom scripts quite right as the ApplyData keeps returning blank Lists for both Inventory and MonsterTracker data. I'll post both scripts below, could you let me know what dumb mistake I'm making. Pretty sure there's something simple I'm just missing when it comes to writing these.

Here's the Monster Tracking custom saver:

public class MonsterDataSaver : Saver // Rename this class.
{
public override string RecordData()
{
string dataToSave = SaveSystem.Serialize(gameObject.GetComponent<MonsterTracker>().monsterDatabase);
return dataToSave;
}

public override void ApplyData(string s)
{
if (string.IsNullOrEmpty(s)) return;
var data = SaveSystem.Deserialize<List<InitialMonsterData.MonsterData>>(s);
if (data != null)
{
GetComponent<MonsterTracker>().monsterDatabase = data;
}
}

The MonsterDatabase is a List of a custom class made up of a string, an int and two bools. Both of those scripts are marked as serializable.

And here's the Inventory one.

[System.Serializable]
public class InventoryDataSaver : Saver // Rename this class.
{
[SerializeField]
List<int> itemIds = new List<int>(24);

public override string RecordData()
{
for (int i = 0; i < 24; i++)
{
itemIds.Add(gameObject.GetComponent<Inventory>().items.itemID);
}
string dataToSave = SaveSystem.Serialize(itemIds);
itemIds.Clear();
return dataToSave;
}

public override void ApplyData(string s)
{
if (string.IsNullOrEmpty(s)) return;
var data = SaveSystem.Deserialize<List<int>>(s);
if (data != null)
{
Debug.Log(data.Count);
List <Item> itemDatabase = gameObject.GetComponent<ItemDatabase>().itemDatabase;
for (int i = 0; i < 24; i++)
{
Debug.Log(data);
Inventory.instance.items = itemDatabase.Find(x => x.itemID == data);
}
}
}

The inventory saver I'm trying to restore the information via a list made up of the Item ID's as they are scriptable objects. The itemDatabase contains a list of every item that can appear in game. Is it okay to serialize the data on the saver itself? It's just a temporary store for the itemId's before being recorded so I didn't see much point separating it. Data.Count returns 0 btw.

Sorry to post all this in one go but I reckon I've run into some rudimentary error with both of these custom savers. Maybe I need to make reference to the key somewhere in the script etc.

Cheers,
Rob
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: I don't think I understand Custom savers

Post by Tony Li »

Hi Rob,

By default, the Save System uses Unity's built-in serialization.

Unity's built-in serialization cannot directly serialize Lists or arrays. (It also can't serialize Dictionaries or anything else that doesn't appear in the Inspector when you make it a public variable.) You'll need to put the list inside a class. I'll add a note in a comment in the SaverTemplate.cs script.

Try changing your monster saver to something like this:

Code: Select all

public class MonsterDataSaver : Saver
{
    [System.Serializable]
    public class Data
    {
        public List<MonsterData> monsterDataList;
    }
    
    public override string RecordData()
    {
        var data = new Data();
        data.monsterDataList = new List<MonsterData>(gameObject.GetComponent<MonsterTracker>().monsterDatabase));
        return SaveSystem.Serialize(data);
    }

    public override void ApplyData(string s)
    {
        if (string.IsNullOrEmpty(s)) return;
        var data = SaveSystem.Deserialize<Data>(s);
        if (data != null)
        {
            GetComponent<MonsterTracker>().monsterDatabase = new List<MonsterData>(data.monsterDataList);
        }
    }
}
User avatar
rykaan
Posts: 75
Joined: Tue Feb 11, 2020 6:22 am

Re: I don't think I understand Custom savers

Post by rykaan »

Hi Tony,

So Unity can't serialize any Lists or arrays or just lists or arrays that appear in the inspector? Since a List of int's for example does appear in the inspector. Just trying to get my head around this whole thing.

The code you suggested doesn't quite get across the finish line sadly. Here's what I changed the script to:

Code: Select all

public class MonsterDataSaver : Saver
    {
        [System.Serializable]
        public class Data
        {
            public List<InitialMonsterData.MonsterData> monsterDataList;
        }

        public override string RecordData()
        {            
            var data = new Data();
            data.monsterDataList = new List<InitialMonsterData.MonsterData>(gameObject.GetComponent<MonsterTracker>().monsterDatabase);
            //for loop to test where code stops working. (Works here).
            for (int j = 0; j < data.monsterDataList.Count; j++)
            {
                Debug.Log(data.monsterDataList[j].monsterName);
            }
            return SaveSystem.Serialize(data);
        }

        public override void ApplyData(string s)
        {            
            if (string.IsNullOrEmpty(s)) return;
            var data = SaveSystem.Deserialize<Data>(s);
            if (data != null)
            {
                GetComponent<MonsterTracker>().monsterDatabase = new List<InitialMonsterData.MonsterData>(data.monsterDataList);
                //for loop to test where code stops working. (Doesn't work here).
                for (int j = 0; j < data.monsterDataList.Count; j++)
                {
                    Debug.Log(data.monsterDataList[j].monsterName);
                }
            }            
        }
But code seems to fail in the same place. At the point where it's applying the data back to the original list:

Code: Select all

GetComponent<MonsterTracker>().monsterDatabase = new List<InitialMonsterData.MonsterData>(data.monsterDataList);
I get the following error:
ArgumentNullException: Value cannot be null.
Parameter name: collection
System.ThrowHelper.ThrowArgumentNullException (System.ExceptionArgument argument) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.Collections.Generic.List`1[T]..ctor (System.Collections.Generic.IEnumerable`1[T] collection) (at <437ba245d8404784b9fbab9b439ac908>:0)
PixelCrushers.MonsterDataSaver.ApplyData (System.String s) (at Assets/Scripts/Management Scripts/Save System/MonsterDataSaver.cs:33)
PixelCrushers.SaveSystem.ApplySavedGameData (PixelCrushers.SavedGameData savedGameData) (at Assets/Plugins/Pixel Crushers/Common/Scripts/Save System/SaveSystem.cs:597)
UnityEngine.Debug:LogException(Exception)
PixelCrushers.SaveSystem:ApplySavedGameData(SavedGameData) (at Assets/Plugins/Pixel Crushers/Common/Scripts/Save System/SaveSystem.cs:602)
PixelCrushers.<LoadSceneCoroutine>d__95:MoveNext() (at Assets/Plugins/Pixel Crushers/Common/Scripts/Save System/SaveSystem.cs:706)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

I find this very odd as there is a check for null data built into the code to prevent this. Is it possible that when the load is triggered, at the point this data is trying to apply it's data the MonsterTracker isn't in a state to receive it? The MonsterTracker is a Singeleton to track across all scenes.

Thanks for the continued help. As you can tell I am new to the coding side of game development. I always focused on the design side until I realised if I ever want to make a game, no ones going to do the coding side for me. It took far too long to bite the bullet and start the learning process and some simple things clearly trip me up now and then. I'm very grateful for your time, and the Dialogue Manager for making a big chunk of the work less complicated.

Cheers,
Rob
User avatar
rykaan
Posts: 75
Joined: Tue Feb 11, 2020 6:22 am

Re: I don't think I understand Custom savers

Post by rykaan »

I did update the Inventory with the same logic of putting the List into a class and saving the class and it works great :).

Cheers for that, just the MonsterTracker being a problem now.
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: I don't think I understand Custom savers

Post by Tony Li »

Hi,

Try breaking down that line and checking each part individually. For example:

Code: Select all

if (data.monsterList == null) Debug.Log("data.monsterList is null.");
var monsterTracker = Get component<MonsterTracker>();
If (monsterTracker == null) Debug.Log("monsterTracker not found on " + name);
User avatar
rykaan
Posts: 75
Joined: Tue Feb 11, 2020 6:22 am

Re: I don't think I understand Custom savers

Post by rykaan »

It was the data not being serialized correctly. I fiddled with the MonsterData class and it's fine now. I finally have two working custom savers. Thanks a tonne for your time.

Best regards,
Rob
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: I don't think I understand Custom savers

Post by Tony Li »

Great! Glad to help.
Post Reply