{"id":1833,"date":"2023-06-20T08:39:40","date_gmt":"2023-06-20T08:39:40","guid":{"rendered":"http:\/\/nikolapacekvetnic.rs\/?p=1833"},"modified":"2023-06-20T20:33:00","modified_gmt":"2023-06-20T20:33:00","slug":"developing-a-slot-based-save-system-in-unity","status":"publish","type":"post","link":"http:\/\/nikolapacekvetnic.rs\/?p=1833","title":{"rendered":"Developing a &#8220;Slot-Based&#8221; Save System in Unity"},"content":{"rendered":"\n<p>In this article we will take a look at how to implement a &#8220;slot-based&#8221; save system in a Unity project in the way I did it for my currently developed game <a href=\"?page_id=1799\">SODHARA<\/a>.\u00a0A &#8220;slot-based&#8221; system has been around for a long time and is employed in many games, with Super Metroid perhaps being the first that comes to mind.<\/p>\n\n\n\n<h2>1 Overview<\/h2>\n<p>The basic idea is to have a (finite or infinite, in the case of this example it is three) number of slots of which the player selects one at the start of game, after which the game is saved automatically into that particular slot, usually upon quitting the current game. This is the variant covered by this article, although a few variations will be briefly touched upon at the very end.<\/p>\n<p>In order to make this work, the following components are needed:<\/p>\n<ol>\n<li><span style=\"color: #ffffff;\">a save game system<\/span>, which will take care of the actual file writing and reading,<\/li>\n<li><span style=\"color: #ffffff;\">a slot selection menu<\/span>, which displays the current state of available slots and allows any of them to be selected or cleared, and finally<\/li>\n<li><span style=\"color: #ffffff;\">an in-game pause menu<\/span>, which will trigger the save game method upon quitting to main menu.<\/li>\n<\/ol>\n<p>Let us inspect each of the moving parts individually.<\/p>\n\n\n\n<h2>2 Save System Controller<\/h2>\n<p>The save system controller is a component needed by both the UI system as well as the game proper. For that reason, I prefer to create an object which will persist (note that the term <em>persistence<\/em> here is used in a context different to the one related to databases) through scene transitions, and attach the save system controller script to that object, which precisely how I did it in <a href=\"?page_id=1799\">SODHARA<\/a>. In addition, it is logical to make that object a singleton, which yields the <span style=\"color: #ffffff;\"><code>Awake()<\/code><\/span> method for the <span style=\"color: #ffffff;\"><code>SaveSystemCtrl<\/code><\/span> script as given in Listing 1.<\/p>\n<pre><code class=\"language-csharp\">public static SaveSystemCtrl instance;\n\nprivate void Awake()\n{\n    if (instance == null)\n    {\n        instance = this;\n        DontDestroyOnLoad(gameObject);\n    }\n    else\n    {\n        Destroy(gameObject);\n    }\n\n    activeSlotIdx = -1;\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 1. The <span style=\"color: #ffffff;\"><code>Awake()<\/code><\/span> method of the <span style=\"color: #ffffff;\"><code>SaveSystemCtrl<\/code><\/span> persistent singleton class.<\/p>\n<p>In order to justify its name the <span style=\"color: #ffffff;\"><code>SaveSystemCtrl<\/code><\/span> script the methods with which to save, load and clear game information. In addition, it needs the actual save data and for that I recommend writing a dedicated data class. The reason is purely organisational, as it allows one to keep track of what is being saved much more easily and efficiently. An example of one such class is given in Listing 2.<\/p>\n<pre><code class=\"language-csharp\">using UnityEngine;\n\n[System.Serializable]\npublic class SaveData\n{\n    public int slotIdx;\n    public bool isSlotEmpty;\n\n    [Header(\"PLAYER STATE\")]\n    public int playerHealth;\n    public int amountOfCrystals;\n\n    [Header(\"PLAYER POSITION\")]\n    public int sceneIdx;\n    public string sceneName;\n    public Vector3 playerPosition;\n\n    public SaveData()\n    {\n        slotIdx = 0;\n        isSlotEmpty = true;\n\n        playerHealth = 0;\n        amountOfCrystals = 0;\n\n        sceneIdx = 0;\n        sceneName = \"\";\n        playerPosition = Vector3.zero;\n    }\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 2. The <span style=\"color: #ffffff;\"><code>SaveData<\/code><\/span> class keeps track of player&#8217;s health, amount of crystals and position.<\/p>\n<p>The class is as standard as they come, the only part of which that requires a comment being the <span style=\"color: #ffffff;\"><code>[System.Serializable]<\/code><\/span> attribute which enables the display of the data class in Unity&#8217;s inspector. Constructor in this case is a matter of convenience and is not in any way mandatory.<\/p>\n<p>Back to SaveSystemCtrl class &#8211; the first method we will work on is CreateSaveData() which has all the introduction it needs in its name alone and is shown in Listing 3.<\/p>\n<pre><code class=\"language-csharp\">private SaveData CreateSaveData()\n{\n    SaveData data = new SaveData();\n\n    data.slotIdx = activeSlotIdx;\n    data.isSlotEmpty = false;\n\n    data.playerHealth = PlayerHealthController.instance.currentHealth;\n    data.amountOfCrystals = DestructableTracker.instance.amountOfCrystals;\n\n    data.sceneIdx = SceneManager.GetActiveScene().buildIndex;\n    data.sceneName = SceneManager.GetActiveScene().name;\n    data.playerPosition = PlayerController.instance.transform.position;\n\n    return data;\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 3. The <span style=\"color: #ffffff;\"><code>SaveSystemCtrl.CreateSaveData()<\/code><\/span> method.<\/p>\n<p>What this method does is access the relevant player information and use it to fill up an instance of the <span style=\"color: #ffffff;\"><code>SaveData<\/code><\/span>&nbsp;class and then pass it onward for other methods of the <span style=\"color: #ffffff;\"><code>SaveSystemCtrl<\/code><\/span> class to use. In fact, this data is only used by a single method, that one being <span style=\"color: #ffffff;\"><code>Save()<\/code><\/span> as given in the Listing 4.<\/p>\n<pre><code class=\"language-csharp\">public void Save()\n{\n    if (PlayerController.instance == null)\n    {\n        Debug.LogWarning(\"Player not found\");\n        return;\n    }\n\n    string dataPath = $\"{Application.persistentDataPath}\/slot{activeSlotIdx}.save\";\n\n    Debug.Log($\"Saving to ${dataPath}...\");\n\n    var serializer = new XmlSerializer(typeof(SaveData));\n    var stream = new FileStream(dataPath, FileMode.Create);\n    serializer.Serialize(stream, CreateSaveData());\n    stream.Close();\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 4. The <span style=\"color: #ffffff;\"><code>SaveSystemCtrl.Save()<\/code><\/span> method.<\/p>\n<p>The method first checks whether the player exists at all. Next, the save file path is built using the <a href=\"https:\/\/docs.unity3d.com\/ScriptReference\/Application-persistentDataPath.html\"><span style=\"color: #ffffff;\"><code>Application.persistentDataPath<\/code><\/span><\/a> value which contains the path to a persistent data directory. The data we are saving will then be serialised into an <a href=\"https:\/\/www.w3.org\/standards\/xml\/core\">XML format<\/a> and then fed into a file stream in order to write it into a file, and for that reason we are instancing the <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/api\/system.xml.serialization.xmlserializer?view=net-7.0\"><span style=\"color: #ffffff;\"><code>XmlSerializer<\/code><\/span><\/a> and <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/api\/system.io.filestream?view=net-7.0\"><span style=\"color: #ffffff;\"><code>FileStream<\/code><\/span><\/a> classes. After saving the data the stream is closed.<\/p>\n<p>In order to reveal the actual folder to which the <span style=\"color: #ffffff;\"><code>Application.persistantDataPath<\/code><\/span> value points to, one can use the command given in the Listing 5.<\/p>\n<pre><code class=\"language-csharp\">EditorUtility.RevealInFinder(Application.persistentDataPath);<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 5. Revealing the <span style=\"color: #ffffff;\"><code>Application.persistantDataPath<\/code><\/span>&nbsp;folder in Finder or Explorer.<\/p>\n<p>We will now take a look at the <span style=\"color: #ffffff;\"><code>ReadSaveData(int slotIdx)<\/code><\/span> method displayed in the Listing 6.<\/p>\n<pre><code class=\"language-csharp\">public SaveData ReadSaveData(int slotIdx)\n{\n    SaveData data = new SaveData();\n\n    string dataPath = $\"{Application.persistentDataPath}\/slot{slotIdx}.save\";\n\n    if (!File.Exists(dataPath))\n        return data;\n\n    var serializer = new XmlSerializer(typeof(SaveData));\n    var stream = new FileStream(dataPath, FileMode.Open);\n    data = serializer.Deserialize(stream) as SaveData;\n\n    data.isSlotEmpty = false;\n\n    stream.Close();\n\n    return data;\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 6. The <span style=\"color: #ffffff;\"><code>SaveSystemCtrl.ReadSaveData()<\/code><\/span> method.<\/p>\n<p>There isn&#8217;t much to comment on in this case, as it is basically the same procedure as saving the game except for the &#8220;reverse&#8221; direction. Once the save file is read, the data is returned to the caller of the method.<\/p>\n<p>Next up is the <span style=\"color: #ffffff;\"><code>BackupAndClearSaveData(int slotIdx)<\/code><\/span> method. As the save files in my project are fairly small, I decided to not simply clear them on deletion, but back them up in a dedicated folder. The code to do so is given in the Listing 7 and is fairly simple.<\/p>\n<pre><code class=\"language-csharp\">public void BackupAndClearSaveData(int slotIdx)\n{\n    string dataPath = $\"{Application.persistentDataPath}\/slot{slotIdx}.save\";\n\n    if (!File.Exists(dataPath))\n    {\n        Debug.LogWarning(\"File not found\");\n        return;\n    }\n\n    string targetDir = $\"{Application.persistentDataPath}\/deleted\";\n\n    if (!System.IO.Directory.Exists(targetDir))\n        System.IO.Directory.CreateDirectory(targetDir);\n\n    string targetDataPath = $\"{Application.persistentDataPath}\/deleted\/{DateTime.Now.ToString(\"yyyyMMdd_HHmmss\")} slot{slotIdx}.save\";\n\n    System.IO.File.Move(dataPath, targetDataPath);\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 7. The <span style=\"color: #ffffff;\"><code>SaveSystemCtrl.BackupAndClearSaveData(int slotIdx)<\/code><\/span> method.<\/p>\n<p>After checking whether the save file exists we create the path to the backup directory, check if it exists and if not create it, and finally move the &#8220;deleted&#8221; save to the backup dir.<\/p>\n<p>Finally, let&#8217;s have look at the <span style=\"color: #ffffff;\"><code>ApplySavedDataAndClear()<\/code><\/span> method as given in Listing 8.<\/p>\n<pre><code class=\"language-csharp\">public void ApplySavedDataAndClear()\n{\n    StartCoroutine(ApplySavedDataAndClearCoroutine());\n}\n\nprivate IEnumerator ApplySavedDataAndClearCoroutine()\n{\n    yield return new WaitForSeconds(.1f);\n\n    PlayerHealthController.instance.currentHealth = dataToLoad.playerHealth;\n    UIController.instance.UpdateHealth(PlayerHealthController.instance.currentHealth, PlayerHealthController.instance.maxHealth);\n    DestructableTracker.instance.amountOfCrystals = dataToLoad.amountOfCrystals;\n\n    PlayerController.instance.transform.position = dataToLoad.playerPosition;\n    Camera.main.transform.position = new Vector3(\n        dataToLoad.playerPosition.x, dataToLoad.playerPosition.y, Camera.main.transform.position.z);\n\n    shouldLoadData = false;\n    dataToLoad = null;\n\n    GlobalSettingsCtrl.instance.fadeScreenCtrl.StartFadeFromBlack();\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 8. The <span style=\"color: #ffffff;\"><code>SaveSystemCtrl.ApplySavedDataAndClear()<\/code><\/span> method.<\/p>\n<p>Just as <span style=\"color: #ffffff;\"><code>ReadSaveData()<\/code><\/span> is the opposite of <span style=\"color: #ffffff;\"><code>Save()<\/code><\/span>, so is <span style=\"color: #ffffff;\"><code>ApplySavedDataAndClear()<\/code><\/span> basically the opposite of <span style=\"color: #ffffff;\"><code>CreateSaveData()<\/code><\/span>. The <span style=\"color: #ffffff;\"><code>dataToLoad<\/code><\/span> attribute of the <span style=\"color: #ffffff;\"><code>SaveSystemCtrl<\/code><\/span> class is set in advance by one of the other scripts which handles save slot selection in the appropriate menu, which is done in order to persist saved data between scenes by saving it with a persistent object. Other than that there is nothing out of the ordinary, as the player&#8217;s health, crystal amount, position, as well as camera position are read from the saved data and applied. The saved game slot is then cleared and indicator boolean set to <span style=\"color: #ffffff;\"><code>false<\/code><\/span>. The final line activates a fade to black which is of course purely aesthetical and will not be further elaborated upon here.<\/p>\n\n\n\n<h2>3 Slot Selection Menu<\/h2>\n<p>Next up is the slot selection menu. The design is of course up to you, but as an idea you may inspect Image 1 and see how I did it for <a href=\"?page_id=1799\">SODHARA<\/a>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-1863\" src=\"http:\/\/nikolapacekvetnic.rs\/wp-content\/uploads\/2023\/06\/SaveSystemTutorial_Image1-1024x579.png\" alt=\"\" width=\"980\" height=\"554\" srcset=\"http:\/\/nikolapacekvetnic.rs\/wp-content\/uploads\/2023\/06\/SaveSystemTutorial_Image1-1024x579.png 1024w, http:\/\/nikolapacekvetnic.rs\/wp-content\/uploads\/2023\/06\/SaveSystemTutorial_Image1-300x170.png 300w, http:\/\/nikolapacekvetnic.rs\/wp-content\/uploads\/2023\/06\/SaveSystemTutorial_Image1-768x435.png 768w, http:\/\/nikolapacekvetnic.rs\/wp-content\/uploads\/2023\/06\/SaveSystemTutorial_Image1-210x119.png 210w, http:\/\/nikolapacekvetnic.rs\/wp-content\/uploads\/2023\/06\/SaveSystemTutorial_Image1.png 1520w\" sizes=\"auto, (max-width: 980px) 100vw, 980px\" \/><\/p>\n<p style=\"text-align: center;\">Image 1. A slot selection menu design idea.<\/p>\n<p>There are obviously two types of buttons in this menu, those being the Back button and the slot buttons, which also come in two states, i.e. empty (default) and populated. To the right of each slot button is the button used for clearing the slot and I consider these auxiliary elements part of the slot button in the populated state.<\/p>\n<p>Much as it was the case with save data, it would be handy to have a data class to represent the stuff that this slot selection button should display. One such class is shown in Listing 9 and it is, unsurprisingly, quite similar to the <span style=\"color: #ffffff;\"><code>SaveData<\/code><\/span> class we inspected earlier.<\/p>\n<pre><code class=\"language-csharp\">public class SlotSelectionButton : MonoBehaviour\n{\n    [Header(\"FIELDS\")]\n    public GameObject dataEmpty;\n    public GameObject dataPopulated;\n\n    [Header(\"POPULATED DATA\")]\n    public TMP_Text captionHealth;\n    public TMP_Text captionCrystals;\n    public TMP_Text captionArea;\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 9. The <span style=\"color: #ffffff;\"><code>SlotSelectionButton<\/code><\/span> class contains the information displayed by a populated slot button.<\/p>\n<p>As this class is not to be used in the Unity inspector there is no need to serialise it by adding the <span style=\"color: #ffffff;\"><code>[System.Serializable]<\/code><\/span> attribute as we did with <span style=\"color: #ffffff;\"><code>SaveData<\/code><\/span> earlier.<\/p>\n<p>Listing 10 shows the class fields we are using in the <code>SlotSelectionMenu<\/code> class. The buttons are self-explanatory, the <code>mainMenu<\/code> points to the &#8220;upper-level&#8221; menu in relation to the slot selection one, and the <code>saveData<\/code> contain the data read from the three currently available save slots.&nbsp;<\/p>\n<pre><code class=\"language-csharp\">[Header(\"BUTTONS\")]\npublic GameObject btnSlot0;\npublic GameObject btnSlot1;\npublic GameObject btnSlot2;\npublic GameObject btnBack;\n\n[Header(\"MENUS\")]\npublic GameObject menuMain;\n\n[Header(\"OTHER\")]\npublic SaveData[] saveData = new SaveData[3];<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 10. The <span style=\"color: #ffffff;\"><code>SlotSelectionMenu<\/code><\/span> class fields.<\/p>\n<p>The slot selection menu is stored as a game object which is interchangeably activated and deactivated as the player flips between it and the main menu. Therefore, the slot buttons become relevant only when this menu is active and they should be updated each time it is activated. Now, this presents some questions in regards to optimisation, as it is not really necessary to update the slots each time the menu is activated since it is only possible to save while in-game, but for simplicity purposes let&#8217;s do it this way, especially as it does not significantly hinder the performance. In order to track the activation of the object we need Unity&#8217;s <span style=\"color: #ffffff;\"><code>OnEnable<\/code><\/span> function as shown in Listing 11.<\/p>\n<pre><code class=\"language-csharp\">private void OnEnable()\n{\n    PopulateSlotData();\n}\n\nprivate void PopulateSlotData()\n{\n    for (int i = 0; i &lt; 3; i++)\n        PopulateSlotDataByIdx(i);\n}\n\nprivate void PopulateSlotDataByIdx(int slotIdx)\n{\n    SlotSelectionButton currSlot = (slotIdx == 0 ? btnSlot0 : slotIdx == 1 ? btnSlot1 : btnSlot2).GetComponent&lt;SlotSelectionButton&gt;();\n\n    SaveData data = SaveSystemCtrl.instance.ReadSaveData(slotIdx);\n    saveData[slotIdx] = data;\n\n    currSlot.dataEmpty.SetActive(data.isSlotEmpty);\n    currSlot.dataPopulated.SetActive(!data.isSlotEmpty);\n\n    if (data.isSlotEmpty) return;\n\n    currSlot.captionHealth.text = $\"HP {data.playerHealth}\";\n    currSlot.captionCrystals.text = $\"CRYS {data.amountOfCrystals}\";\n    currSlot.captionArea.text = $\"Area: {data.sceneName}\";\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 11. Populating the save game slots upon activating the menu within <span style=\"color: #ffffff;\"><code>SlotSelectionMenu<\/code><\/span> class.<\/p>\n<p>We&#8217;ll concentrate on the <span style=\"color: #ffffff;\"><code>PopulateSlotDataByIdx(int slotIdx)<\/code><\/span> method as the other two are self-explanatory. The relevant button is selected based on the <span style=\"color: #ffffff;\"><code>slotIdx<\/code><\/span> argument &#8211; the buttons could&#8217;ve been saved as an array in which case this would be slightly simpler, but I opted for this route in this particular case. Next, the saved data is read and stored in the <span style=\"color: #ffffff;\"><code>saveData<\/code><\/span> array, after which the button is set up for display. Button&#8217;s default state is empty so that one requires no setup. Based on the state of the <span style=\"color: #ffffff;\"><code>isSlotEmpty<\/code><\/span> field of the <span style=\"color: #ffffff;\"><code>data<\/code><\/span> variable the empty and populated state are activated or deactivated, and&nbsp;then the check to see if there is any data to be loaded is made &#8211; if not the method returns, otherwise the relevant info is fed into the <span style=\"color: #ffffff;\"><code>TMP_Text<\/code><\/span> elements of the button.<\/p>\n<p>Shown in Listing 12 is the <span style=\"color: #ffffff;\"><code>ClearSlot()<\/code><\/span> method which does exactly what it says it does. Again, it is not necessary to reload all the slots after clearing one of them, but in this case it&#8217;s a simple and cheap solution so I opted for that route once again.<\/p>\n<pre><code class=\"language-csharp\">public void ClearSlot(int slotIdx)\n{\n    SaveSystemCtrl.instance.BackupAndClearSaveData(slotIdx);\n    PopulateSlotDataByIdx(slotIdx);\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 12. The <span style=\"color: #ffffff;\"><code>SlotSelectionMenu.ClearSlot()<\/code><\/span> method.<\/p>\n<p>The final element of the <span style=\"color: #ffffff;\"><code>SlotSelectionMenu<\/code><\/span> script is the <span style=\"color: #ffffff;\"><code>StartGame(int slotIdx)<\/code><\/span> method as shown in the Listing 13.<\/p>\n<pre><code class=\"language-csharp\">public void StartGame(int slotIdx)\n{\n    GlobalSettingsCtrl.instance.fadeScreenCtrl.StartFadeToBlack();\n    StartCoroutine(StartGameCoroutine(GlobalSettingsCtrl.instance.fadeScreenCtrl.fadeSpeed, slotIdx));\n}\n\nprivate IEnumerator StartGameCoroutine(float timeToWait, int slotIdx)\n{\n    yield return new WaitForSeconds(timeToWait);\n\n    SaveSystemCtrl.instance.activeSlotIdx = slotIdx;\n\n    SaveSystemCtrl.instance.shouldLoadData = !saveData[slotIdx].isSlotEmpty;\n    SaveSystemCtrl.instance.dataToLoad = saveData[slotIdx].isSlotEmpty ? null : saveData[slotIdx];\n\n    SceneManager.LoadScene(saveData[slotIdx].isSlotEmpty ? menuMain.GetComponent&lt;MainMenu&gt;().newGameScene : saveData[slotIdx].sceneName);\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 13. The <span style=\"color: #ffffff;\"><code>SlotSelectionMenu.StartGame()<\/code><\/span> method.<\/p>\n<p>The reason I use the coroutine is this case is solely in order to make my crossfade thingy, i.e. the <span style=\"color: #ffffff;\"><code>fadeScreenCtrl<\/code><\/span> component, work properly. What is happening basically is that I initiate a fade to black and only once that is complete will I call the <span style=\"color: #ffffff;\"><code>StartGameCoroutine()<\/code><\/span>, so that all the nasty transitions are happening behind the black curtain.<\/p>\n<p>The <span style=\"color: #ffffff;\"><code>activeSlotIdx<\/code><\/span> of the <span style=\"color: #ffffff;\"><code>SaveSystemCtrl<\/code><\/span> class is the currently active slot, i.e. the one to which the game will be saved upon quitting the game via pause menu &#8211; obviously, it must persist through the scene transitions and is therefore kept in a persistent object. Fields <span style=\"color: #ffffff;\"><code>shouldLoadData<\/code><\/span> and <span style=\"color: #ffffff;\"><code>dataToLoad<\/code><\/span> are used for feeding the player the saved data after loading the required scene, which is done in the last line of the method, which loads the first scene in the game if the slot is empty, otherwise it loads the scene contained in the save file.<\/p>\n<p>The last relevant snippet of code for loading the game can be seen in Listing 14.<\/p>\n<pre><code class=\"language-csharp\">private void OnSceneLoaded(Scene scene, LoadSceneMode mode)\n{\n    if (SaveSystemCtrl.instance.shouldLoadData)\n        SaveSystemCtrl.instance.ApplySavedDataAndClear();\n    else\n        GlobalSettingsCtrl.instance.fadeScreenCtrl.StartFadeFromBlack();\n}<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 14. Applying the save data in case the data is present, otherwise activating the crossfade.<\/p>\n<p>Unity&#8217;s <span style=\"color: #ffffff;\"><code>OnSceneLoaded()<\/code><\/span> function is activated once the current scene is completely loaded. At that point we are sure that all the necessary assets are in their place and so we can apply the saved data such as physical position of the player safely in case there is anything to apply. If not, the player is left in its default position and the fade from black is initiated.<\/p>\n\n\n\n<h2>4 In-Game Pause Menu<\/h2>\n<p>In <a href=\"?page_id=1799\">SODHARA<\/a> the game is saved when the player quits the game and returns to main menu. There isn&#8217;t much to discuss here actually, as the only two relevant lines are given in the Listing 15.<\/p>\n<pre><code class=\"language-csharp\">SaveSystemCtrl.instance.Save();\nSaveSystemCtrl.instance.activeSlotIdx = -1;<\/code><\/pre>\n<p style=\"text-align: center;\">Listing 15. Saving the game upon quitting to main menu.<\/p>\n<p>Setting the <span style=\"color: #ffffff;\"><code>activeSlotIdx<\/code><\/span> to <span style=\"color: #ffffff;\"><code>-1<\/code><\/span> is simply a precaution measure and is probably not necessary but, as always with coding, it&#8217;s better to be safe than sorry.<\/p>\n\n\n\n<h2>5 Conclusion<\/h2>\n<p>Once all the pieces are in place your project should now have a fully functional save system in place. Shown in the Image 2 is how it looks like in <a href=\"?page_id=1799\">SODHARA<\/a> <a href=\"https:\/\/nikolavetnic.itch.io\/sodhara\">v0.4.3<\/a>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1870 aligncenter\" src=\"http:\/\/nikolapacekvetnic.rs\/wp-content\/uploads\/2023\/06\/v0.4.3-GIF_SavingTheGame.gif\" alt=\"\" width=\"600\" height=\"339\" \/><\/p>\n<p style=\"text-align: center;\">Image 2. Slot-based save system as implemented in <a href=\"?page_id=1799\">SODHARA<\/a>.<\/p>\n<p>The good thing about this system is that it is fairly simple and robust, yet it allows for variations and can thus be adapted to suit different needs. What immediately comes to mind is greater number of save slots, infinite number of slots even, manual as opposed to automatic save, and even saving to an arbitrary slot which in turn makes it a classical manual save system.<\/p>\n<p>For any questions and comments feel free to contact me via the contact form of the website.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article we will take a look at how to implement a &#8220;slot-based&#8221; save system in a Unity project in the way I did it for my currently developed game SODHARA.\u00a0A &#8220;slot-based&#8221; system has been around for a long time and is employed in many games, with Super Metroid perhaps being the first that [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1417,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55,126,34,134],"tags":[88,136,87,139,138,42,137,135],"class_list":["post-1833","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-code","category-game-dev-code","category-tutorial","category-unity","tag-gamedev","tag-indie","tag-indiedev","tag-metroidvania","tag-save-game","tag-tutorial","tag-ui","tag-unity"],"_links":{"self":[{"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=\/wp\/v2\/posts\/1833","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1833"}],"version-history":[{"count":39,"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=\/wp\/v2\/posts\/1833\/revisions"}],"predecessor-version":[{"id":1971,"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=\/wp\/v2\/posts\/1833\/revisions\/1971"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=\/wp\/v2\/media\/1417"}],"wp:attachment":[{"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1833"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1833"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/nikolapacekvetnic.rs\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1833"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}