Named save games

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Named save games

Post by VoodooDetective »

To have a name for each slot, do I need to subclass one of the save game storers or is there already the ability to do that somehow.

Also, it seems like the save system was written with the understanding that there would only be one user playing the game per computer. Like the auto-saver takes slot number as a hard-coded parameter. If I want to allow for multiple save games , do I need to modify auto-saver to check lua state for a variable named "currentSave" or something?
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Named save games

Post by Tony Li »

Hi,
VoodooDetective wrote: Wed Jan 22, 2020 10:51 pmTo have a name for each slot, do I need to subclass one of the save game storers or is there already the ability to do that somehow.
Some options:
  • You can subclass one of the saved game data storers, or the base SavedGameDataStorer class if you want to start with essentially a blank slate. (For example, the ESSavedGameDataStorer for Easy Save 3 subclasses SavedGameDataStorer since it doesn't need to inherit functionality from any of the other storer classes, and some mobile games make a subclass of SavedGameDataStorer that saves games to a server over WWW.)
  • Or you can write a separate "directory" that associates names with slot numbers.
  • Or you can include the name in the saved game data itself. Write a Saver component whose RecordData() method returns the saved game name. ApplyData() can be empty. The caveat with this option is that, to show the list of saved game names in a load menu, you will need to open and deserialize all of the saved games to get the name.
VoodooDetective wrote: Wed Jan 22, 2020 10:51 pmAlso, it seems like the save system was written with the understanding that there would only be one user playing the game per computer. Like the auto-saver takes slot number as a hard-coded parameter. If I want to allow for multiple save games , do I need to modify auto-saver to check lua state for a variable named "currentSave" or something?
Only the AutoSaveLoad component assumes a specified slot number, and that's simple enough that you could subclass it or write a replacement -- or just set its saveSlotNumber in code for the current player.

Other than that, slots are simply arbitrary numbers used to identify saved games. You can manage them however you want for any number of players.
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Re: Named save games

Post by VoodooDetective »

Fantastic, thank you!
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Named save games

Post by Tony Li »

Glad to help!
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Re: Named save games

Post by VoodooDetective »

I went with the "directory" approach. I figured I'd share what I wrote in case anyone else is interested.

Code: Select all

    /// <summary>
    /// Allows you to perform various operations on saved games.
    /// </summary>
    public class SaveGameDirectory
    {
        // Constants
        private const bool PrettyPrint = true;
        private const int MaxSaveGames = 10;
        // Properties
        private static SaveDirectory _directory;
        private static SaveDirectory directory
        {
            get
            {
                if (_directory == null) _directory = ReadOrCreateSaveDirectory();
                return _directory;
            }
        }
        public static Save currentSave;

        public static IEnumerable<Save> ListSaveGames()
        {
            for (int i = 1; i < directory.slotToSave.Length; i++)
            {
                Save save = directory.slotToSave[i];
                if (save != null)
                {
                    yield return save;
                }
            }
            yield break;
        }

        public static int AvailableSaveCount()
        {
            int availableCount = 0;
            for (int i = 1; i < directory.slotToSave.Length; i++)
            {
                if (directory.slotToSave[i] == null)
                {
                    availableCount++;
                }
            }
            return availableCount;
        }

        public static void NewGame(string name)
        {
            int slot = GetSlotForSave(name); // Check if game exists
            if (slot <= 0)
            {
                slot = GetEmptySlot();
                if (slot <= 0)
                {
                    throw new Exception("No save slots available");
                }
                currentSave = new Save();
                currentSave.name = name;
                currentSave.dateCreated = DateTime.Now;
                directory.slotToSave[slot] = currentSave;
            }
            else
            {
                currentSave = directory.slotToSave[slot];
            }
            currentSave.dateLastSaved = DateTime.Now;
            WriteSaveDirectory();
            SaveSystem.SaveToSlot(slot);
        }

        public static void SaveGame()
        {
            if (currentSave == null) throw new Exception("No current game to save!");
            int slot = GetSlotForSave(currentSave.name);
            if (slot <= 0) throw new Exception("No slot found for current save game!");
            directory.slotToSave[slot].dateLastSaved = DateTime.Now;
            WriteSaveDirectory();
            SaveSystem.SaveToSlot(slot);
        }

        public static void LoadGame(string name)
        {
            int slot = GetSlotForSave(name);
            if (slot > 0)
            {
                currentSave = directory.slotToSave[slot];
                SaveSystem.LoadFromSlot(slot);
            }
            else
            {
                throw new Exception("Save game did not exist");
            }
        }

        public static void DeleteGame(string name)
        {
            int slot = GetSlotForSave(name);
            if (slot > 0)
            {
                SaveSystem.DeleteSavedGameInSlot(slot);
                directory.slotToSave[slot] = null;
                WriteSaveDirectory();
            }
        }

        public static bool DoesSaveGameExist(string name)
        {
            return GetSlotForSave(name) > 0;
        }

        private static int GetSlotForSave(string name)
        {
            for (int i = 1; i < directory.slotToSave.Length; i++)
            {
                if (directory.slotToSave[i] != null && directory.slotToSave[i].name == name)
                {
                    return i;
                }
            }
            return 0;
        }

        private static int GetEmptySlot()
        {
            for (int i = 1; i < directory.slotToSave.Length; i++)
            {
                if (directory.slotToSave[i] == null)
                {
                    return i;
                }
            }
            return 0;
        }

        private static void WriteSaveDirectory()
        {
            string serialized = Singletons.YamlSerializer.Serialize(directory);
            File.WriteAllText(GetDirectoryPath(), serialized);
        }

        private static SaveDirectory ReadOrCreateSaveDirectory()
        {
            string path = GetDirectoryPath();
            SaveDirectory saveDirectory;
            if (File.Exists(path))
            {
                string serialized = File.ReadAllText(path);
                saveDirectory = Singletons.YamlSerializer.Deserialize<SaveDirectory>(serialized);
            }
            else
            {
                saveDirectory = new SaveDirectory();
                saveDirectory.slotToSave = new Save[MaxSaveGames + 1]; // 0th index empty
            }
            return saveDirectory;
        }

        private static string GetDirectoryPath()
        {
            return Application.persistentDataPath + "/save_directory";
        }
    }

    /// <summary>
    /// SaveDirectory model.
    /// </summary>
    public class SaveDirectory
    {
        public Save[] slotToSave { get; set; }
    }

    /// <summary>
    /// Save model.
    /// </summary>
    public class Save
    {
        public string name { get; set; }
        public DateTime dateCreated { get; set; }
        public DateTime dateLastSaved { get; set; }
    }
Last edited by VoodooDetective on Thu Jan 23, 2020 4:22 pm, edited 2 times in total.
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Named save games

Post by Tony Li »

Thanks for sharing!
Post Reply