Page 1 of 1

Save System slots and custom data

Posted: Sat Jul 20, 2019 3:43 am
by alkord
Hello!
How can I load (and save) custom data from every slots (like save time, game time, player level etc) for display in load menu? See example.

Re: Save System slots and custom data

Posted: Sat Jul 20, 2019 5:38 am
by alkord
I created custom saver

Code: Select all

public class SlotDataParser
{
    private readonly DataContractJsonSerializer serializer
        = new DataContractJsonSerializer(typeof(SlotData));

    public string Serialize(SlotData data)
    {
        var stream = new MemoryStream();
        try {
            serializer.WriteObject(stream, data);
            var json = stream.ToArray();
            return Encoding.UTF8.GetString(json, 0, json.Length);
        } finally {
            stream.Close();
        }
    }

    public SlotData Deserialize(string json)
    {
        var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
        try {
            var data = (SlotData) serializer.ReadObject(stream);
            return data;
        } finally {
            stream.Close();
        }
    }
}
Save:

Code: Select all

var data = new SlotData();
// ... fill data
SaveSystem.currentSavedGameData.SetData(SlotData.KEY, SaveSystem.NoSceneIndex, parser.Serialize(data));
SaveSystem.SaveToSlot(0);
Load:

Code: Select all

var save = SaveSystem.storer.RetrieveSavedGameData(slot);
var slotData = save.GetData(SlotData.KEY);
var data = parser.Deserialize(slotData);
I don't know how good this way is.

Re: Save System slots and custom data

Posted: Sat Jul 20, 2019 8:33 am
by Tony Li
Hi,

If your saved games remain medium to small in size, that's a fine solution, and I'd continue using it. Each saved game will contain data like this:

[SavedGameData: {slotData}, {regularSaveData} ]

where {slotData} is the extra info (save time, game time, player level, etc)

However, if your saved games become very large, then to show the custom data for all of the saved games you will need to read all of the saved games first, which could take more time than you want. Instead, you could save each slot like this:

{slotData}, [SavedGameData: {regularSaveData} ]

This way, when showing the "load game" screen, you can read each slot's {slotData} without having to read and parse the regular save data.

One way to do this is to make your own subclass of SavedGameDataStorer (or one of its subclasses such as DiskSavedGameDataStorer).

Before saving, store the slot data in a key just like in your post.

Then override StoreSavedGameData(slot, savedGameData) to do this: (pseudocode)

Code: Select all

public void StoreSavedGameData(int slot, SavedGameData savedGameData)
{    
    // Get the slot data and save it first:
    var slotData = save.GetData(SlotData.KEY);
    /* save slotData here */
    
    // Then save the regular save data:
    /* save savedGameData here */
}
Override RetrieveSavedGameData(slot, savedGameData) to skip the slotData: (pseudocode)

Code: Select all

public SavedGameData RetrieveSavedGameData(int slot)
{
    // Skip slotData:
    /* read past slotData */
    
    // Read regular save data:
    /* read and return savedGameData */
}
Finally, add a new method called something like RetrieveSlotData(int slot): (pseudocode)

Code: Select all

public SlotData RetrieveSlotData(int slot)
{
    /* read and return slotData */
    // No need to read regular save data; just return slotData.
}
To show the "load game" panel, use RetrieveSlotData() to get the slot data without having to read each saved game's full data.

Re: Save System slots and custom data

Posted: Sun Jul 21, 2019 2:23 am
by alkord
Wow, thanks!
I will definitely try this. It looks most logical.

Re: Save System slots and custom data

Posted: Sun Jul 21, 2019 4:15 am
by alkord
Save files separately, so as not to break the working.
I created custom disk storer:

Code: Select all

public class SavedGameDataStorer : DiskSavedGameDataStorer
{
    public override void StoreSavedGameData(int slot, SavedGameData save)
    {
        var slotData = save.GetData(SaveSystem.SlotDataKey);
        var filePath = GetSlotDataFilename(slot);

        if (debug) {
            Debug.Log($"Save System: SavedGameDataStorer - Saving Slot Data{filePath}: {slotData}");
        }

        WriteStringToFile(
            filePath,
            encrypt ? EncryptionUtility.Encrypt(slotData, encryptionPassword) : slotData
        );

        save.SetData(SaveSystem.SlotDataKey, SaveSystem.NoSceneIndex, null);
        base.StoreSavedGameData(slot, save);
    }

    public SlotData RetrieveSlotData(int slot)
    {
        var filePath = GetSlotDataFilename(slot);
        var data = ReadStringFromFile(filePath);
        if (encrypt) {
            data = EncryptionUtility.TryDecrypt(data, encryptionPassword, out var plainText)
                ? plainText
                : string.Empty;
        }

        if (debug) {
            Debug.Log($"Save System: SavedGameDataStorer - Loading Slot Data {filePath}: {data}");
        }
        return SaveSystem.Deserialize<SlotData>(data);
    }

    private static string GetSlotDataFilename(int slot)
    {
        return Application.persistentDataPath + "/save_meta_" + slot + ".dat";
    }
}
Сustom SaveSystem wrapper:

Code: Select all

public class SaveSystem : PixelCrushers.SaveSystem
{
    public const string SlotDataKey = "slotData";
    
    public static void SaveToSlot(int slot, SlotData slotData)
    {
        slotData.slot = slot;
        slotData.date = DateTimeOffset.Now.ToUnixTimeSeconds();
        var data = Serialize(slotData);

        currentSavedGameData.SetData(SlotDataKey, NoSceneIndex, data);
        SaveToSlot(slot);
    }

    public static SlotData RetrieveSlotData(int slot)
    {
        return ((SavedGameDataStorer) storer).RetrieveSlotData(slot);
    }
}

Re: Save System slots and custom data

Posted: Sun Jul 21, 2019 8:46 am
by Tony Li
Thanks for sharing!