{"id":176,"date":"2016-11-27T16:34:54","date_gmt":"2016-11-27T21:34:54","guid":{"rendered":"http:\/\/naplandgames.com\/blog\/?p=176"},"modified":"2018-12-18T09:04:56","modified_gmt":"2018-12-18T14:04:56","slug":"saving-data-in-unity-3d-serialization-for-beginners","status":"publish","type":"post","link":"https:\/\/naplandgames.com\/blog\/2016\/11\/27\/saving-data-in-unity-3d-serialization-for-beginners\/","title":{"rendered":"Saving Data in Unity 3D (Serialization for Beginners)"},"content":{"rendered":"<p><a href=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/serialize_me.png\" rel=\"lightbox[176]\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-213 size-full alignright\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/serialize_me.png\" width=\"204\" height=\"204\" srcset=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/serialize_me.png 212w, https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/serialize_me-150x150.png 150w\" sizes=\"(max-width: 204px) 100vw, 204px\" \/><\/a>With games we almost always need to save some data: scores, inventory, progress, preferences, etc. There are many ways to do this in Unity. In this article I&#8217;ll take you through a few different ways to save data and hopefully encourage some good habits when structuring your applications for saved data.<\/p>\n<p>There are two main levels of persistency for data:<\/p>\n<ul>\n<li><strong>Session Data\u00a0<\/strong>(data doesn&#8217;t necessarily need to be remembered the next time you load up the application): This is usually done via static variables and is used for only values that matter for the current session. Most commonly this is for health, score, lives, active power-ups, etc.<\/li>\n<li><strong>Application Data<\/strong> (data that must be remembered if the application is closed): This is done by saving data to a file or possibly a database on a server.<\/li>\n<\/ul>\n<p>What often becomes confusing with Unity is that there are instances of classes that are disposed when a new scene is loaded. So if you have a GameObject with a PlayerData script attached to it, then when you load a new scene it with another GameObject with a PlayerData script, it is not the same\u00a0<strong>instance<\/strong> of the first script. All of its values are set back to whatever was assigned in the inspector or script by default. So we need to make use of data persistence techniques.<\/p>\n<p>When I design Unity applications I try to stick to the following rules:<\/p>\n<ul>\n<li>Is it an application preference (music volume, mute, skip tutorial messages)? If yes, then save using Unity&#8217;s PlayerPrefs class.<\/li>\n<li>Is it a value that must be available when we reload the application (player progress, inventory items, IAP)? If yes, then save to a file using some serialization method (binary, JSON, XML, etc).<\/li>\n<li>Otherwise it is likely only a variable that needs to be known throughout this session and can simply be stored in a static variable (Session Data).<\/li>\n<\/ul>\n<p>Let&#8217;s take a look at these various methods and see how to implement them. I won&#8217;t be explaining the use of static variables to store session data because it is literally just having some class with public static variables that you set from other classes. The main disadvantage of this is that the use of these variables can be spread throughout your code, so try to use these sparingly.<\/p>\n<h2>Part I &#8211; PlayerPrefs<\/h2>\n<p>As inferred by the name of the class, these are the Player Preferences (not the person playing the game, but the Unity Player Application). As I mentioned earlier this should really be for storing things like music volume, should the app be muted, should we skip tutorial messages, quality settings (though if you use the application launcher then this is already managed by the Unity player). Let&#8217;s examine a simple use-case of this: muting all sounds and setting the master volume.<\/p>\n<h3>Scene Setup<\/h3>\n<p>To start, let&#8217;s setup our scene. The scene consists of a UI canvas with a panel, and that panel contains a slider for our volume setting and a toggle for our mute setting. I&#8217;ve also put in a button that reloads the scene and an AudioSource so that we can test the persistence of the settings. The AudioSource just plays a clip on awake and is set to looping. We can hear it restart when we reload the scene, but the volume or mute setting is automatically reapplied. Here&#8217;s a screenshot of all the important parts of the scene:<\/p>\n<figure id=\"attachment_200\" aria-describedby=\"caption-attachment-200\" style=\"width: 1024px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/playerpresf_example_scene.png\" rel=\"lightbox[176]\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-200 size-large\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/playerpresf_example_scene-1024x543.png\" alt=\"playerpresf_example_scene\" width=\"1024\" height=\"543\" srcset=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/playerpresf_example_scene-1024x543.png 1024w, https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/playerpresf_example_scene-300x159.png 300w, https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/playerpresf_example_scene-768x407.png 768w, https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/playerpresf_example_scene.png 1908w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption id=\"caption-attachment-200\" class=\"wp-caption-text\">(click for full size)<\/figcaption><\/figure>\n<p>You may notice on the Volume Slider and the Mute Toggle that there are script components. We&#8217;ll get to those momentarily. First let&#8217;s talk about the class that handles the saving, loading, and applying of PlayerPrefs, the <span class=\"lang:c# decode:true crayon-inline \">PlayerPrefsHandler<\/span>\u00a0.<\/p>\n<h4>PlayerPrefsHandler.cs<\/h4>\n<p>This class is responsible for saving, loading, and applying PlayerPrefs. The <a href=\"https:\/\/docs.unity3d.com\/ScriptReference\/PlayerPrefs.html\" target=\"_blank\" rel=\"noopener\">PlayerPrefs class<\/a> is part of the Unity API. It has a bunch of handy static methods that can be used to save and restore data (integers, floats, and strings). Each preference is stored with a key, the key is a string that is used to look up a value. For example, for the mute setting I&#8217;ll be using a key, &#8220;mute&#8221; and for volume I&#8217;ll be using &#8220;volume&#8221;. The keys and their values actually get stored in either a file or in the Windows Registry (you can view them with regedit.exe, the Windows Registry Editor). So, they&#8217;re pretty accessible to your users and easy to modify. Therefore, they aren&#8217;t a good idea to use for sensitive data like scores, player stats, xp, etc. But they&#8217;re perfect for storing things like mute and volume settings. Unity also stores some data here like window dimensions and graphics quality level. I typically have a class like the PlayerPrefsHandler that deals with saving, loading, and applying all preferences. Even though this class is meant to handle all of this, oftentimes we&#8217;ll find ourselves accessing the preferences via other classes. You should try to avoid this, but in case you have to do it then it&#8217;s very important to keep all of the prefs keys in a single script and make them constant. This way you can use Intellisense to fill in the key&#8217;s variable name for you instead of trying (and failing) to remember the actual string. It also makes changing the key string easy because you only have to do it in one place. The code for this class is below and it is heavily commented so that you know what&#8217;s happening every step of the way:<\/p>\n<pre class=\"lang:c# decode:true \" title=\"PlayerPrefsHandler.cs\">using UnityEngine;\r\n\r\n\/\/\/ &lt;summary&gt;\r\n\/\/\/ Handles the saving, recalling, and applying of all PlayerPrefs for the application.\r\n\/\/\/ &lt;\/summary&gt;\r\npublic class PlayerPrefsHandler\r\n{\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Storing the PlayerPrefs keys in constants is a good practice!\r\n\t\/\/\/ This saves you from having to make multiple changes in your code should you change the key value,\r\n\t\/\/\/ allows you to make use of Intellisense for typing out the key (intead of mistyping the actual string),\r\n\t\/\/\/ and since it is public and const you can access it anywhere without needing an instance of this class\r\n\t\/\/\/ (i.e. by typing PlayerPrefsHandler.MUTE_INT).\r\n\t\/\/\/ I like to append my PlayerPrefs keys with the type of the pref (i.e. _INT, _STR, _F)\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t#region PlayerPrefs keys\r\n\tpublic const string MUTE_INT = \"mute\";\r\n\tpublic const string VOLUME_F = \"volume\";\r\n\t#endregion\r\n\r\n\tprivate const bool DEBUG_ON = true;\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ This method should call all other methods that will apply saved or default preferences.\r\n\t\/\/\/ We should call this as soon as possible when loading our application.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\tpublic void RestorePreferences()\r\n\t{\r\n\t\tSetMuted(GetIsMuted());\r\n\t\tSetVolume(GetVolume());\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Sets the AudioListener to be (un)muted and saves the value to player prefs.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=\"muted\"&gt;Whether we should mute or not.&lt;\/param&gt;\r\n\tpublic void SetMuted(bool muted)\r\n\t{\r\n\t\t\/\/ Set the MUTE_INT key to 1 if muted, 0 if not muted\r\n\t\tPlayerPrefs.SetInt(MUTE_INT, muted ? 1 : 0);\r\n\r\n\t\t\/\/ Pausing the AudioListener will disable all sounds.\r\n\t\tAudioListener.pause = muted;\r\n\r\n\t\tif (DEBUG_ON)\r\n\t\t\tDebug.LogFormat(\"SetMuted({0})\", muted);\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Reads from PlayerPrefs to tell us if we should mute or not.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;returns&gt;Whether the MUTE_INT pref has been set to 1 or not.&lt;\/returns&gt;\r\n\tpublic bool GetIsMuted()\r\n\t{\r\n\t\t\/\/ If the value of the MUTE_INT key is 1 then sound is muted, otherwise it is not muted.\r\n\t\t\/\/ The default value of the MUTE_INT key is 0 (i.e. not muted).\r\n\t\treturn PlayerPrefs.GetInt(MUTE_INT, 0) == 1;\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Sets the volume on the AudioListener and saves the value to PlayerPrefs.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=\"volume\"&gt;A value between 0 and 1&lt;\/param&gt;\r\n\tpublic void SetVolume(float volume)\r\n\t{\r\n\t\t\/\/ Prevent values less than 0 and greater than 1 from\r\n\t\t\/\/ being stored in the PlayerPrefs (AudioListener.volume expects a value between 0 and 1).\r\n\t\tvolume = Mathf.Clamp(volume, 0, 1);\r\n\r\n\t\tPlayerPrefs.SetFloat(VOLUME_F, volume);\r\n\t\tAudioListener.volume = volume;\r\n\t}\r\n\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Retrieves the stored or default (1) volume from PlayerPrefs\r\n\t\/\/\/ and ensures it is no less than 0 and no greater than 1\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;returns&gt;The volume setting between 0 and 1&lt;\/returns&gt;\r\n\tpublic float GetVolume()\r\n\t{\r\n\t\treturn Mathf.Clamp(PlayerPrefs.GetFloat(VOLUME_F, 1), 0, 1);\r\n\t}\r\n}\r\n<\/pre>\n<p>You may notice that this class does not inherit from MonoBehaviour. There&#8217;s really no reason to, and although this class is responsible for handling the player prefs it is actually loaded and ran from a different class which\u00a0<em>is<\/em> a MonoBehaviour class. This class is called DataService.<\/p>\n<h4>DataService.cs<\/h4>\n<p>The DataService class is what is called a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Singleton_pattern\" target=\"_blank\" rel=\"noopener\">Singleton<\/a>. A Singleton is basically a single instance of a class. Only one instance should ever exist. The Instance is accessed statically (i.e. <span class=\"lang:c# decode:true crayon-inline \">DataService.Instance<\/span>\u00a0) instead of by creating an instance and passing that reference around. They tend to make lie a lot easier and are used often in games. This Singleton is also derived from MonoBehaviour so it can be attached to a game object which makes it available to other game objects in the Hierarchy so we can assign onClick events and the like. Finally, the game object it is attached to is marked <span class=\"lang:c# decode:true crayon-inline \">DontDestroyOnLoad<\/span>\u00a0, so that Unity will keep it when other scenes are loaded. Beware though! Singletons have gotten a bad name because they are often abused and referenced too frequently throughout code. When this happens the code isn&#8217;t extensible and modifications can be more time consuming later on. Singletons are also not easy to extend. You&#8217;ll want to use the conventional &#8220;Instance&#8221; member to access the subclass, but you that won&#8217;t give you access to any members of the subclass, just the parent class (not very helpful). So use them sparingly.<\/p>\n<p>The code below is heavily commented as usual, but here&#8217;s a quick breakdown of what&#8217;s happening: Anytime the Instance is accessed the class will ensure that the Instance exists (it&#8217;s self-loading or lazy loading). When the MonoBehaviour Awake method is called we ensure that it is the one and only singleton in our scene. This allows us some leniency when we forget that we attached the singleton to an object in a scene (sometimes done during testing). If another instance of this class exists then it is destroyed. The Instance, though, is set as DontDestroyOnLoad, this makes it so that the game object it is attached to doesn&#8217;t get destroyed when a new scene is loaded and ensures we&#8217;re working with that initial and only instance.<\/p>\n<p>In our Awake method is where we also create an instance of the PlayerPrefsHandler class and restore\/apply the preferences that were saved. We also do this whenever another scene is loaded so that the preferences are applied to the new objects in the new scene (like muting the AudioListener).<\/p>\n<pre class=\"lang:c# decode:true\" title=\"DataService.cs\">using UnityEngine;\r\n\r\nnamespace EX1\r\n{\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Singleton - There should only ever be one DataService and it should persist\r\n\t\/\/\/ between scene loads.\r\n\t\/\/\/ This class is responsible for loading\/saving data.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\tpublic class DataService : MonoBehaviour\r\n\t{\r\n\t\tprivate static DataService _instance = null;\r\n\t\tpublic static DataService Instance\r\n\t\t{\r\n\t\t\tget\r\n\t\t\t{\r\n\t\t\t\t\/\/ If the instance of this class doesn't exist\r\n\t\t\t\tif (_instance == null)\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/ Check the scene for a Game Object with this class\r\n\t\t\t\t\t_instance = FindObjectOfType&lt;DataService&gt;();\r\n\r\n\t\t\t\t\t\/\/ If none is found in the scene then create a new Game Object\r\n\t\t\t\t\t\/\/ and add this class to it.\r\n\t\t\t\t\tif (_instance == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tGameObject go = new GameObject(typeof(DataService).ToString());\r\n\t\t\t\t\t\t_instance = go.AddComponent&lt;DataService&gt;();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn _instance;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tpublic PlayerPrefsHandler prefs { get; private set; }\r\n\r\n\t\t\/\/ When the scene first runs ensure that there is only one\r\n\t\t\/\/ instance of this class. This allows us to add it to any scene and \r\n\t\t\/\/ not conflict with any pre-existing instance from a previous scene.\r\n\t\tprivate void Awake()\r\n\t\t{\r\n\t\t\tif (Instance != this)\r\n\t\t\t{\r\n\t\t\t\tDestroy(this);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tDontDestroyOnLoad(gameObject);\r\n\r\n\t\t\t\tprefs = new PlayerPrefsHandler();\r\n\t\t\t\tprefs.RestorePreferences();\r\n\t\t\t\t\/\/ In Unity 5.4 OnLevelWasLoaded has been deprecated and the action\r\n\t\t\t\t\/\/ now occurs through this callback.\r\n#if UNITY_5_4_OR_NEWER\r\n\t\t\t\tSceneManager.sceneLoaded += OnLevelWasLoaded;\r\n#endif\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ Ensure that the player preferences are applied to the new scene.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\t\/\/ In Unity 5.4 OnLevelWasLoaded has been deprecated and the action\r\n\t\t\/\/ now occurs through 'SceneManager.sceneLoaded' callback.\r\n\t\tvoid OnLevelWasLoaded()\r\n\t\t{\r\n\t\t\tprefs.RestorePreferences();\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>Note that this class is wrapped in a namespace (EX1). This is because I&#8217;m using the same class name in the second part of this tutorial and I don&#8217;t want them to conflict. Our next step will be to set up some UI elements to control our settings and then test it out.<\/p>\n<h4>UI Elements<\/h4>\n<p>Most often, the user will be making changes to settings like mute and volume via UI elements. In this tutorial we&#8217;re using a UI Toggle to control muting and a UI Slider to control volume. I&#8217;ve created a script for each of these controls that gets attached directly to the game object that has the correct UI component (i.e. one for toggle, one for slider). There&#8217;s no other setup needed in the inspector as these scripts automatically handle everything for us. I&#8217;ve commented the MuteToggleHandler class liberally, but not the VolumeSliderHandler since they only differ in one minor aspect. Both have a Start method that gets the appropriate component from the game object we&#8217;re attached to, then is uses the <span class=\"lang:c# decode:true crayon-inline \">DataService.Instance<\/span>\u00a0 to get access to an instance of the PlayerPrefs class and set the appropriate setting, and finally we set up a callback, or listener, to the UI control&#8217;s onValueChanged delegate. In the <span class=\"lang:c# decode:true crayon-inline \">onValueChanged.AddListener<\/span>\u00a0 method I&#8217;m inputting a parameter that is actually a method. It&#8217;s a special type of method called a lambda. This is used often in JavaScript and they come in really handy. <a href=\"https:\/\/www.dotnetperls.com\/lambda\" target=\"_blank\" rel=\"noopener\">Check out this link for more info<\/a>. Simply attach the MuteToggleHandler class to the UI Toggle for muting and attach the VolumeSliderHandler to the UI Slider. The scripts do the rest!<\/p>\n<div id='gallery-1' class='gallery galleryid-176 gallery-columns-2 gallery-size-thumbnail'><figure class='gallery-item'>\n\t\t\t<div class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/volSlider.png' rel=\"lightbox[176]\"><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/volSlider-150x150.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/div><\/figure><figure class='gallery-item'>\n\t\t\t<div class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/muteToggle.png' rel=\"lightbox[176]\"><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/muteToggle-150x150.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/div><\/figure>\n\t\t<\/div>\n\n<pre class=\"lang:c# decode:true\" title=\"MuteToggleHandler.cs\">using UnityEngine;\r\nusing UnityEngine.UI;\r\nusing DataService = EX1.DataService;\r\n\/\/ We'll switch to this in part 2\r\n\/\/using DataService = EX2.DataService;\r\n\r\n\/\/\/ &lt;summary&gt;\r\n\/\/\/ Handles the initial setting of the UI Toggle value\r\n\/\/\/ and assigns the onValueChanged listener to the UI Toggle component.\r\n\/\/\/ &lt;\/summary&gt;\r\n\/\/ RequireComponent ensures that when we can only add this component to a UI Toggle\r\n\/\/ It also ensures that when we attempt to GetComponent&lt;Toggle&gt; that it exists.\r\n[RequireComponent(typeof(Toggle))]\r\npublic class MuteToggleHandler : MonoBehaviour\r\n{\r\n\tvoid Start()\r\n\t{\r\n\t\t\/\/ Get the reference to the attached toggle component.\r\n\t\tToggle toggle = GetComponent&lt;Toggle&gt;();\r\n\r\n\t\t\/\/ Set the initial value that was stored in player prefs.\r\n\t\ttoggle.isOn = DataService.Instance.prefs.GetIsMuted();\r\n\r\n\t\t\/\/ Set up the onValueChanged listener\r\n\t\t\/\/ This is done here instead of in the inspector for a few reasons:\r\n\t\t\/\/\t- DataService contains the PlayerPrefsHandler reference and Unity won't let us\r\n\t\t\/\/\t  access that through the inspector.\r\n\t\t\/\/\t- DataService is a singleton and may or may not be in the scene so we can't always\r\n\t\t\/\/\t  assign it to via the inspector.\r\n\t\t\/\/\t- This makes the script completely self-contained. No other script needs access to this script.\r\n\t\t\/\/ The only fallback is that this class is not extensible, but it really doesn't need to be.\r\n\t\ttoggle.onValueChanged.AddListener(\r\n\t\t\t(bool value) =&gt; \r\n\t\t\t{\r\n\t\t\t\tDataService.Instance.prefs.SetMuted(value);\r\n\t\t\t});\r\n\t}\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"lang:c# decode:true \" title=\"VolumeSliderHandler.cs\">using UnityEngine;\r\nusing UnityEngine.UI;\r\nusing DataService = EX1.DataService;\r\n\/\/ We'll switch to this in part 2\r\n\/\/using DataService = EX2.DataService;\r\n\r\n[RequireComponent(typeof(Slider))]\r\npublic class VolumeSliderHandler : MonoBehaviour\r\n{\r\n\tvoid Start()\r\n\t{\r\n\t\tSlider slider = GetComponent&lt;Slider&gt;();\r\n\t\tslider.value = DataService.Instance.prefs.GetVolume();\r\n\t\tslider.onValueChanged.AddListener(\r\n\t\t\t(float value) =&gt;\r\n\t\t\t{\r\n\t\t\t\tDataService.Instance.prefs.SetVolume(value);\r\n\t\t\t});\r\n\t}\r\n}<\/pre>\n<p>As you can see from the VolumeSliderHandler class, these two scripts are quite short and simple. As the values of the controls are changed the player prefs are saved. Sweet!<\/p>\n<p>You can test this out now if you like. I&#8217;d suggest adding an AudioSource in your scene so you can hear the effect of the changes and reload the scene to test out that the saving of the settings is persistent between runs of the application as well as levels. In my project I set up a button that reloads the current level, that part is optional, but it&#8217;s easy to do and is good practice.<\/p>\n<h4>ClearPlayerPrefs.cs<\/h4>\n<p>There&#8217;s one last part to all of this, the ClearPlayerPrefs class. I use this script in every project I have. It&#8217;s super simple, but really handy when testing things out. It just clears the PlayerPrefs file\/registry entries. Perfect for when you mess things up \ud83d\ude42 . This script is an Editor script and must go into a folder named &#8220;Editor&#8221;. This tells Unity that it shouldn&#8217;t be included in the build and that it is only modifying the Unity Editor. It actually adds a menu item to the Edit menu. Super handy.<\/p>\n<pre class=\"lang:c# decode:true \" title=\"Editor\/ClearPlayerPrefs.cs\">using UnityEngine;\r\nusing UnityEditor;\r\n\r\npublic class ClearPlayerPrefs : Editor\r\n{\r\n\t[MenuItem(\"Edit\/Clear All PlayerPrefs\")]\r\n\tstatic void ClearAll()\r\n\t{\r\n\t\tPlayerPrefs.DeleteAll();\r\n\t}\r\n}<\/pre>\n<p>That&#8217;s really it for handling player preferences. There are a few classes involved, but with this sort of structure you should rarely go wrong. The only issue here is that PlayerPrefs is not really a good place to store data that you want to prevent players from modifying, like save files.<\/p>\n<h2>Part II &#8211; Data Serialization<\/h2>\n<p>We often save our data to files for a variety of factors. PlayerPrefs is a widely know location and easy to tinker with. In the case of Windows machines, you have limits on how much data can be stored in a registry entry. Also, during development it is often easier for the devs to examine a file with an easy-to-read format (like JSON or XML) then we turn on encryption before we release the game. So PlayerPrefs just doesn&#8217;t tend to cut it.<\/p>\n<p>In the example here I will be using JSON (pronounced JAY-son) serialization to store save data. I won&#8217;t be diving into any encryption methods as they&#8217;re beyond the scope of this tutorial (and there are tons of tutorials out there on the subject). JSON is a highly popular format. It&#8217;s relatively easy for a human to read, it is frequently used for data transfer over the internet, and Unity&#8217;s API now contains a utility to (de)serialize JSON strings.<\/p>\n<p>Serialization takes the public variables in a class and writes them to a format like JSON. For example, if you had a class like this:<\/p>\n<pre class=\"lang:c# decode:true \">public ForSerial\u00a0\r\n{\r\n    public int myInt = 10;\r\n}<\/pre>\n<p>It&#8217;s JSON representation would be the variable name followed by it&#8217;s value like so:<\/p>\n<pre class=\"lang:c# decode:true\">{\"myInt\" : 10}<\/pre>\n<p>The awesome thing about this is that it can be read right back into the class&#8217;s variables. Very cool (I wish I had understood this when making my first game!). Just about every programming language has a library that will serialize and deserialize JSON. So you could send this data up to your server and read it with PHP, or send it to a Java app and read it there with relative ease. It is used very commonly in REST APIs which are everywhere (Twitter, Facebook, Google APIs, etc)! So learn how to deal with JSON objects. You&#8217;ll find them everywhere.<\/p>\n<h4>SaveData.cs<\/h4>\n<p><a href=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/save-icon.png\" rel=\"lightbox[176]\"><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-210\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/save-icon.png\" alt=\"save-icon\" width=\"128\" height=\"128\" \/><\/a>We begin with our class that will store the player&#8217;s progress and stats. This is the class that we will serialize and write to file. It will store the number of coins, health, lives, power ups, and the last level (scene) the player was in. It is responsible for being able to save an instance of itself to a file via the <span class=\"lang:c# decode:true crayon-inline \">WriteToFile()<\/span>\u00a0 method. It has a static method that can create a new instance of the SaveData class from a file (or a default instance if the file doesn&#8217;t exist). It also has a method to check if an instance has only the default values (i.e. nothing has been written to it yet). And finally, a <span class=\"lang:c# decode:true crayon-inline \">ToString()<\/span>\u00a0 method so that we can get a human-friendly string representation of the instance when we need it.<\/p>\n<p>For saving and loading the file I am using the simplest methods available: <span class=\"lang:c# decode:true crayon-inline \">File.WriteAllText() <\/span>\u00a0and <span class=\"lang:c# decode:true crayon-inline \">File.ReadAllText()<\/span>\u00a0. These are part of the <span class=\"lang:c# decode:true crayon-inline \">System.IO<\/span>\u00a0 namespace and just blankly read the entire contents of a file to a string or write a single string to a file. To (de)serialize the class to a JSON object I&#8217;m making use of Unity&#8217;s built-in JsonUtility class.<\/p>\n<p>The code is quite short and the majority of the code is dealing with default values for the class. I&#8217;ve commented it up for you to get a better understanding.<\/p>\n<pre class=\"lang:c# decode:true\" title=\"SaveData.cs\">using UnityEngine;\r\nusing System.IO; \/\/ Required fro reading\/writing to files.\r\nusing System.Collections.Generic; \/\/ Used for Lists\r\n\r\n\/\/\/ &lt;summary&gt;\r\n\/\/\/ The different types of powerups a player can have.\r\n\/\/\/ &lt;\/summary&gt;\r\npublic enum PowerUp\r\n{\r\n\tFireballs,\r\n\tDoubleJump\r\n}\r\n\r\n\/\/\/ &lt;summary&gt;\r\n\/\/\/ Responsible for:\r\n\/\/\/ - Maintaining the stats for a player and their progress\r\n\/\/\/ - Writing this data to a file.\r\n\/\/\/ - Reading this data from a file.\r\n\/\/\/ &lt;\/summary&gt;\r\npublic class SaveData\r\n{\r\n\t#region Defaults\r\n\tpublic const string DEFAULT_LEVEL = \"level1\";\r\n\tprivate const int DEFAULT_COINS = 0;\r\n\tprivate const int DEFAULT_HEALTH = 100;\r\n\tprivate const int DEFAULT_LIVES = 3;\r\n\t#endregion\r\n\r\n\t\/\/ We initialize all of the stats to be default values.\r\n\tpublic int coins = DEFAULT_COINS;\r\n\tpublic int health = DEFAULT_HEALTH;\r\n\tpublic int lives = DEFAULT_LIVES;\r\n\tpublic List&lt;PowerUp&gt; powerUps = new List&lt;PowerUp&gt;();\r\n\tpublic string lastLevel = DEFAULT_LEVEL;\r\n\r\n\tconst bool DEBUG_ON = true;\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Writes the instance of this class to the specified file in JSON format.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=\"filePath\"&gt;The file name and full path to write to.&lt;\/param&gt;\r\n\tpublic void WriteToFile(string filePath)\r\n\t{\r\n\t\t\/\/ Convert the instance ('this') of this class to a JSON string with \"pretty print\" (nice indenting).\r\n\t\tstring json = JsonUtility.ToJson(this, true);\r\n\r\n\t\t\/\/ Write that JSON string to the specified file.\r\n\t\tFile.WriteAllText(filePath, json);\r\n\r\n\t\t\/\/ Tell us what we just wrote if DEBUG_ON is on.\r\n\t\tif (DEBUG_ON)\r\n\t\t\tDebug.LogFormat(\"WriteToFile({0}) -- data:\\n{1}\", filePath, json);\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Returns a new SaveData object read from the data in the specified file.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=\"filePath\"&gt;The file to attempt to read from.&lt;\/param&gt;\r\n\tpublic static SaveData ReadFromFile(string filePath)\r\n\t{\r\n\t\t\/\/ If the file doesn't exist then just return the default object.\r\n\t\tif (!File.Exists(filePath))\r\n\t\t{\r\n\t\t\tDebug.LogErrorFormat(\"ReadFromFile({0}) -- file not found, returning new object\", filePath);\r\n\t\t\treturn new SaveData();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t\/\/ If the file does exist then read the entire file to a string.\r\n\t\t\tstring contents = File.ReadAllText(filePath);\r\n\r\n\t\t\t\/\/ If debug is on then tell us the file we read and its contents.\r\n\t\t\tif (DEBUG_ON)\r\n\t\t\t\tDebug.LogFormat(\"ReadFromFile({0})\\ncontents:\\n{1}\", filePath, contents);\r\n\r\n\t\t\t\/\/ If it happens that the file is somehow empty then tell us and return a new SaveData object.\r\n\t\t\tif (string.IsNullOrEmpty(contents))\r\n\t\t\t{\r\n\t\t\t\tDebug.LogErrorFormat(\"File: '{0}' is empty. Returning default SaveData\");\r\n\t\t\t\treturn new SaveData();\r\n\t\t\t}\r\n\r\n\t\t\t\/\/ Otherwise we can just use JsonUtility to convert the string to a new SaveData object.\r\n\t\t\treturn JsonUtility.FromJson&lt;SaveData&gt;(contents);\r\n\t\t}\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ This is used to check if the SaveData object is the same as the default.\r\n\t\/\/\/ i.e. it hasn't been written to yet.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\tpublic bool IsDefault()\r\n\t{\r\n\t\treturn (\r\n\t\t\tcoins == DEFAULT_COINS &amp;&amp;\r\n\t\t\thealth == DEFAULT_HEALTH &amp;&amp;\r\n\t\t\tlives == DEFAULT_LIVES &amp;&amp;\r\n\t\t\tlastLevel == DEFAULT_LEVEL &amp;&amp;\r\n\t\t\tpowerUps.Count == 0);\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ A friendly string representation of this object.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\tpublic override string ToString()\r\n\t{\r\n\t\tstring[] powerUpsStrings = new string[powerUps.Count];\r\n\t\tfor (int i = 0; i &lt; powerUps.Count; i++)\r\n\t\t{\r\n\t\t\tpowerUpsStrings[i] = powerUps[i].ToString();\r\n\t\t}\r\n\r\n\t\treturn string.Format(\r\n\t\t\t\"coins: {0}\\nhealth: {1}\\nlives: {2}\\npowerUps: {3}\\nlastLevel: {4}\",\r\n\t\t\tcoins, \r\n\t\t\thealth, \r\n\t\t\tlives, \r\n\t\t\t\"[\" + string.Join(\",\", powerUpsStrings) + \"]\",\r\n\t\t\tlastLevel\r\n\t\t\t);\r\n\t}\r\n}\r\n\r\n<\/pre>\n<p>Next up we&#8217;ll integrate this into our DataService class.<\/p>\n<h4>DataService.cs (EX2)<\/h4>\n<p>In the second part of our DataService class we&#8217;ll want to make it responsible for handling the loading and saving of our player data files. Also, it&#8217;s a bit boring to have a single save data file. Most games allow us to have multiple save file slots, so we&#8217;ll set that up too.<\/p>\n<p>The new code (added on to the original DataService class) starts in the <span class=\"lang:c# decode:true crayon-inline \">OnLevelWasLoaded<\/span>\u00a0 method where the comments start. Take a look a the code for a moment then read on for some further explanation.<\/p>\n<pre class=\"lang:c# decode:true \" title=\"DataService.cs (EX2)\">using UnityEngine;\r\nusing System.IO;\r\nusing UnityEngine.SceneManagement;\r\n\r\nnamespace EX2\r\n{\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ This class is responsible for loading\/saving data.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\tpublic class DataService : MonoBehaviour\r\n\t{\r\n\t\tprivate static DataService _instance = null;\r\n\t\tpublic static DataService Instance\r\n\t\t{\r\n\t\t\tget\r\n\t\t\t{\r\n\t\t\t\tif (_instance == null)\r\n\t\t\t\t{\r\n\t\t\t\t\t_instance = FindObjectOfType&lt;DataService&gt;();\r\n\r\n\t\t\t\t\tif (_instance == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tGameObject go = new GameObject(typeof(DataService).ToString());\r\n\t\t\t\t\t\t_instance = go.AddComponent&lt;DataService&gt;();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn _instance;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tpublic PlayerPrefsHandler prefs { get; private set; }\r\n\r\n\t\tprivate void Awake()\r\n\t\t{\r\n\t\t\tif (Instance != this)\r\n\t\t\t{\r\n\t\t\t\tDestroy(this);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tDontDestroyOnLoad(gameObject);\r\n\r\n\t\t\t\tprefs = new PlayerPrefsHandler();\r\n\t\t\t\tprefs.RestorePreferences();\r\n\r\n#if UNITY_5_4_OR_NEWER\r\n\t\t\t\tSceneManager.sceneLoaded += OnLevelWasLoaded;\r\n#endif\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvoid OnLevelWasLoaded(Scene scene, LoadSceneMode mode)\r\n\t\t{\r\n\t\t\tOnLevelWasLoaded();\r\n\t\t}\r\n\r\n\t\tvoid OnLevelWasLoaded()\r\n\t\t{\r\n\t\t\tprefs.RestorePreferences();\r\n\r\n\t\t\t\/\/ If we haven't loaded any SaveData yet then load it. \r\n\t\t\t\/\/ This also sets the currentProfile number.\r\n\t\t\tif (SaveData == null)\r\n\t\t\t\tLoadSaveData();\r\n\r\n\t\t\t\/\/ Set the player's progress if this is not the main menu scene.\r\n\t\t\t\/\/ In my project this is scene 0 and scene 1\r\n\t\t\tScene activeScene = SceneManager.GetActiveScene();\r\n\t\t\tif (activeScene.buildIndex &gt; 1)\r\n\t\t\t{\r\n\t\t\t\tSaveData.lastLevel = activeScene.path.Replace(\"Assets\/\", \"\").Replace(\".unity\", \"\");\r\n\t\t\t}\r\n\r\n\t\t\t\/\/ Write the save data to file, saving the player's stats and progress.\r\n\t\t\tWriteSaveData();\r\n\t\t}\r\n\r\n\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ The currently loaded Save Data.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\tpublic SaveData SaveData { get; private set; }\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ Use this to prevent reloading the data when a new scene loads.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\tbool isDataLoaded = false;\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ Store the currently loaded profile number here.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\tpublic int currentlyLoadedProfileNumber { get; private set; }\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ The maximum number of profiles we'll allow our users to have.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\tpublic const int MAX_NUMBER_OF_PROFILES = 3;\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ Loads the save data for a specific profile number. \r\n\t\t\/\/\/ This will eventually be called from a button.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"profileNumber\"&gt;(Optional) the profile number to load, \r\n\t\t\/\/\/ omit to automatically load the first profile found.&lt;\/param&gt;\r\n\t\tpublic void LoadSaveData(int profileNumber = 0)\r\n\t\t{\r\n\t\t\tif (isDataLoaded &amp;&amp; profileNumber == currentlyLoadedProfileNumber)\r\n\t\t\t\treturn;\r\n\r\n\t\t\t\/\/ Automatically load the first available profile.\r\n\t\t\tif (profileNumber &lt;= 0)\r\n\t\t\t{\r\n\t\t\t\t\/\/ We iterate through the possible profile numbers in case one with a lower number\r\n\t\t\t\t\/\/ no longer exists.\r\n\t\t\t\tfor (int i = 1; i &lt;= MAX_NUMBER_OF_PROFILES; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (File.Exists(GetSaveDataFilePath(i)))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t\/\/ Once the file is found, load it from the calculated file name.\r\n\t\t\t\t\t\tSaveData = SaveData.ReadFromFile(GetSaveDataFilePath(i));\r\n\t\t\t\t\t\t\/\/ And set the current profile number for later use when we save.\r\n\t\t\t\t\t\tcurrentlyLoadedProfileNumber = i;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t\/\/ If the profileNumber parameter is supplied then we'll look to see if that exists.\r\n\t\t\t\tif (File.Exists(GetSaveDataFilePath(profileNumber)))\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/ If the file exists then load the SaveData from the calculated file name.\r\n\t\t\t\t\tSaveData = SaveData.ReadFromFile(GetSaveDataFilePath(profileNumber));\r\n\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/ Otherwise just return a new\r\n\t\t\t\t\tSaveData = new SaveData();\r\n\t\t\t\t}\r\n\r\n\t\t\t\t\/\/ And set the current profile number for later use when we save.\r\n\t\t\t\tcurrentlyLoadedProfileNumber = profileNumber;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ The base name of our save data files.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\tprivate const string SAVE_DATA_FILE_NAME_BASE = \"savedata\";\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ The extension of our save data files.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\tprivate const string SAVE_DATA_FILE_EXTENSION = \".txt\";\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ The directory our save data files will be stored in. \r\n\t\t\/\/\/ This is done through a getter because we're calling to a non-constant member (Application.dataPath)\r\n\t\t\/\/\/ to construct this.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\tprivate string SAVE_DATA_DIRECTORY { get { return Application.dataPath + \"\/saves\/\"; } }\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ The full path and file name for our SaveData file.\r\n\t\t\/\/\/ ex: 'c:\\projectdirectory\\assets\\saves\\savedata1.txt'\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"profileNumber\"&gt;The number profile to load (must be greater than 0).&lt;\/param&gt;\r\n\t\tpublic string GetSaveDataFilePath(int profileNumber)\r\n\t\t{\r\n\t\t\t\/\/ If the profile number is less than 1 then throw an exception.\r\n\t\t\tif (profileNumber &lt; 1)\r\n\t\t\t\tthrow new System.ArgumentException(\"profileNumber must be greater than 1. Was: \" + profileNumber);\r\n\r\n\t\t\t\/\/ Ensure that the directory exists.\r\n\t\t\tif (!Directory.Exists(SAVE_DATA_DIRECTORY))\r\n\t\t\t\tDirectory.CreateDirectory(SAVE_DATA_DIRECTORY);\r\n\r\n\t\t\t\/\/ Construct the string representation of the directory + file name.\r\n\t\t\treturn SAVE_DATA_DIRECTORY + SAVE_DATA_FILE_NAME_BASE + profileNumber.ToString() + SAVE_DATA_FILE_EXTENSION;\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;\r\n\t\t\/\/\/ Writes the save data to file.\r\n\t\t\/\/\/ &lt;\/summary&gt;\r\n\t\tpublic void WriteSaveData()\r\n\t\t{\r\n\t\t\t\/\/ If for some accidental reason we forgot to assign a profile number,\r\n\t\t\t\/\/ then check to see if there is any unused profile number (i.e. a file doesn't exist for it). \r\n\t\t\tif (currentlyLoadedProfileNumber &lt;= 0)\r\n\t\t\t{\r\n\t\t\t\tfor (int i = 1; i &lt;= MAX_NUMBER_OF_PROFILES; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (!File.Exists(GetSaveDataFilePath(i)))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcurrentlyLoadedProfileNumber = i;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t\/\/ If we couldn't find an empty profile then throw an exception because something went very wrong.\r\n\t\t\tif (currentlyLoadedProfileNumber &lt;= 0)\r\n\t\t\t{\r\n\t\t\t\tthrow new System.Exception(\"Cannot WriteSaveData. No available profiles and currentlyLoadedProfile = 0\");\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t\/\/ Otherwise save the SaveData to file.\r\n\r\n\t\t\t\t\/\/ If the save data doesn't exist yet, \r\n\t\t\t\t\/\/ then create a new default save data.\r\n\t\t\t\tif (SaveData == null)\r\n\t\t\t\t\tSaveData = new SaveData();\r\n\r\n\t\t\t\t\/\/ Finally save it to th file using the constructed path + file name\r\n\t\t\t\tSaveData.WriteToFile(GetSaveDataFilePath(currentlyLoadedProfileNumber));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n<\/pre>\n<p>The first additions we have are in the <span class=\"lang:c# decode:true crayon-inline \">OnLevelWasLoaded<\/span>\u00a0 method. This method is called every time a scene is loaded. It ensures that we have a SaveData object loaded into memory and it then ensures it is written to file. This way when a new scene is loaded the SaveData is automatically saved to a file. Oftentimes we may also want to <span class=\"lang:c# decode:true crayon-inline \">WriteSaveData<\/span>\u00a0 when the player reaches a checkpoint, level end, or some other meaningful time in our games.<\/p>\n<p>The <span class=\"lang:c# decode:true crayon-inline \">LoadSaveData<\/span>\u00a0 method has an optional parameter (profile number). If we don&#8217;t use this parameter then the method will look for the first existing save data file and load it. If we do use the parameter then we will load the data from file if the file exists. If the file doesn&#8217;t exist we simply create a new <span class=\"lang:c# decode:true crayon-inline \">SaveData<\/span>\u00a0 object. In all cases we set the <span class=\"lang:c# decode:true crayon-inline \">currentlyLoadedProfileNumber<\/span>\u00a0 so that we can reference it later when saving the <span class=\"lang:c# decode:true crayon-inline \">SaveData<\/span>\u00a0 object to file.<\/p>\n<p>Next up are some constants (<a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/w86s7x04.aspx#Anchor_0\" target=\"_blank\" rel=\"noopener\">and a getter<\/a>) that will help us construct the full path and file name of the save data file. That full path is constructed via the <span class=\"lang:c# decode:true crayon-inline \">GetSaveDataFilePath<\/span>\u00a0 method which expects a parameter indicating the profile number to load. This method simply ensures the directory exists and returns that constructed file path and file name.<\/p>\n<p>Finally, we have the <span class=\"lang:c# decode:true crayon-inline \">WriteSaveData<\/span>\u00a0 method. This first attempts to ensure that we have a valid <span class=\"lang:c# decode:true crayon-inline \">profileNumber<\/span>\u00a0 and will write the file to disk.<\/p>\n<p>Since <span class=\"lang:c# decode:true crayon-inline \">DataService<\/span>\u00a0 is a singleton we can call any of these methods without creating a new instance of the class or using something like <span class=\"lang:c# decode:true crayon-inline \">GameObject.FindObjectOfType<\/span>\u00a0. All we have to do is type something like this: <span class=\"lang:c# decode:true crayon-inline\">DataService.Instance.WriteSaveData()<\/span>\u00a0 and we&#8217;re good to go.<\/p>\n<p>When you save the data it will go into a directory called &#8220;saves&#8221; that&#8217;s in your Asset folder (you might need to refresh the project window for it to show up). You should examine the contents of those files as changes happen. Since we&#8217;re saving them with a txt extension, Unity will show the contents in the inspector window (you may need to refresh the Project window). The contents should look like this:<\/p>\n<pre class=\"lang:c# highlight:0 decode:true\">{\r\n    \"coins\": 0,\r\n    \"health\": 100,\r\n    \"lives\": 3,\r\n    \"powerUps\": [\r\n        0,\r\n        1\r\n    ],\r\n    \"lastLevel\": \"level1\"\r\n}<\/pre>\n<p>Next up we&#8217;re going to add a class\u00a0to handle loading of the profiles from buttons.<\/p>\n<h4>SaveSlotButtonHandler.cs<\/h4>\n<p>We&#8217;ll use LoadSaveData on some buttons to load a profile and start a level with this next bit of code. First, simply add 3 buttons to your scene. Then add this class to your project:<\/p>\n<pre class=\"lang:c# decode:true\" title=\"SaveSlotButtonHandler.cs\">using UnityEngine;\r\nusing UnityEngine.UI;\r\nusing System.IO;\r\nusing UnityEngine.SceneManagement;\r\nusing EX2;\r\n\r\n\/\/\/ &lt;summary&gt;\r\n\/\/\/ Attach this to any game object. I like to attach it to the canvas containing the buttons that will\r\n\/\/\/ load profiles. That way it's in a 'logical' place and easy to find.\r\n\/\/\/ &lt;\/summary&gt;\r\npublic class SaveSlotButtonHandler : MonoBehaviour\r\n{\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Assign each of the button labels here. They should be in order of their appearance (top to bottom).\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\tpublic Text[] buttonLabels;\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ This is the text that will display when \r\n\t\/\/\/ &lt;\/summary&gt;\r\n\tprivate const string EMPTY_SLOT = \"New Game\";\r\n\tprivate const string USED_SLOT = \"Load Save \";\r\n\r\n\tvoid Start()\r\n\t{\r\n\t\tSetButtonLabels();\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Sets the label on each button to indicate whether we're loading an empty slot or\r\n\t\/\/\/ loading an actual profile.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\tvoid SetButtonLabels()\r\n\t{\r\n\t\tif (buttonLabels.Length != DataService.MAX_NUMBER_OF_PROFILES)\r\n\t\t{\r\n\t\t\tDebug.LogError(\r\n\t\t\t\t\"Incorrect number of button labels. Must be exactly \" +\r\n\t\t\t\tDataService.MAX_NUMBER_OF_PROFILES);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t\/\/ For every possible profile number.\r\n\t\t\tfor (int i = 0; i &lt; DataService.MAX_NUMBER_OF_PROFILES; i++)\r\n\t\t\t{\r\n\t\t\t\t\/\/ If the profile file exists,\r\n\t\t\t\t\/\/ Then set the label to say the profile exists (i.e. 'Load Save 1')\r\n\t\t\t\tif (File.Exists(DataService.Instance.GetSaveDataFilePath(i + 1)))\r\n\t\t\t\t{\r\n\t\t\t\t\tbuttonLabels[i].text = USED_SLOT + (i + 1).ToString();\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/ Otherwise set the label to just say 'New Game\" indicating it is an empty slot.\r\n\t\t\t\t\tbuttonLabels[i].text = EMPTY_SLOT;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t\/\/ This should be assigned to each button via the inspector.\r\n\t\/\/ The parameter in the inspector's on click event will be 1,2, or 3\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Called from the OnClick methods for buttons.\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=\"profileNumber\"&gt;&lt;\/param&gt;\r\n\tpublic void LoadGame(int profileNumber)\r\n\t{\r\n\t\t\/\/ Load the save data file\r\n\t\tDataService.Instance.LoadSaveData(profileNumber);\r\n\t\t\/\/ Load the last level the player was in\r\n\t\tSceneManager.LoadScene(DataService.Instance.SaveData.lastLevel);\r\n\t}\r\n}\r\n\r\n<\/pre>\n<p>The code is commented heavily and there&#8217;s really nothing to explain outside of the comments other than what was explained above. The next thing we&#8217;ll want to do is set up some way to test our SaveData.<\/p>\n<h4>TestSaveData.cs<\/h4>\n<p><a href=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/images.png\" rel=\"lightbox[176]\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright wp-image-208 size-thumbnail\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/images-150x150.png\" alt=\"images\" width=\"150\" height=\"150\" \/><\/a>This class&#8217;s sole purpose is to just test out our save data file. It will allow us to view the currently loaded SaveData object instance and press some keyboard keys\u00a0to give the player coins, give\/take health, give\/take lives, give\/take power ups, and load the next level. You should also examine the contents of the savedata{n}.txt files to see that when they change on disk (hint: When is WriteToFile running during the application?).<\/p>\n<pre class=\"lang:c# decode:true \" title=\"TestSaveData.cs\">using UnityEngine;\r\nusing UnityEngine.SceneManagement;\r\nusing EX2;\r\n\r\npublic class TestSaveData : MonoBehaviour\r\n{\r\n\tprivate static TestSaveData _instance;\r\n\tvoid Awake()\r\n\t{\r\n\t\t\/\/ Making this object persist through the scenes so I only have to add it to one.\r\n\t\tif (_instance == null)\r\n\t\t{\r\n\t\t\tgameObject.name = \"[TSD instance]\";\r\n\t\t\tDontDestroyOnLoad(gameObject);\r\n\t\t\t_instance = this;\r\n\t\t}\r\n\r\n\t\tif (this != _instance)\r\n\t\t\tDestroy(gameObject);\r\n\t}\r\n\r\n\r\n\tvoid Update()\r\n\t{\r\n\t\tif (DataService.Instance.SaveData == null)\r\n\t\t\treturn;\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.Alpha1))\r\n\t\t\tDataService.Instance.SaveData.coins++;\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.Alpha2))\r\n\t\t\tDataService.Instance.SaveData.coins--;\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.Q))\r\n\t\t\tDataService.Instance.SaveData.health++;\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.W))\r\n\t\t\tDataService.Instance.SaveData.health--;\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.A))\r\n\t\t\tDataService.Instance.SaveData.lives++;\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.S))\r\n\t\t\tDataService.Instance.SaveData.lives--;\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.F))\r\n\t\t{\r\n\t\t\tif (DataService.Instance.SaveData.powerUps.Contains(PowerUp.Fireballs))\r\n\t\t\t\tDataService.Instance.SaveData.powerUps.Remove(PowerUp.Fireballs);\r\n\t\t\telse\r\n\t\t\t\tDataService.Instance.SaveData.powerUps.Add(PowerUp.Fireballs);\r\n\t\t}\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.D))\r\n\t\t{\r\n\t\t\tif (DataService.Instance.SaveData.powerUps.Contains(PowerUp.DoubleJump))\r\n\t\t\t\tDataService.Instance.SaveData.powerUps.Remove(PowerUp.DoubleJump);\r\n\t\t\telse\r\n\t\t\t\tDataService.Instance.SaveData.powerUps.Add(PowerUp.DoubleJump);\r\n\t\t}\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.Return))\r\n\t\t{\r\n\t\t\tDataService.Instance.WriteSaveData();\r\n\t\t}\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.Space))\r\n\t\t{\r\n\t\t\tint currentSceneIndex = SceneManager.GetActiveScene().buildIndex;\r\n\t\t\tSceneManager.LoadScene(currentSceneIndex + 1);\r\n\t\t}\r\n\r\n\t\tif (Input.GetKeyDown(KeyCode.M))\r\n\t\t\tSceneManager.LoadScene(1);\r\n\t}\r\n\r\n\tvoid OnGUI()\r\n\t{\r\n\t\tstring saveData = \"NOT LOADED YET\";\r\n\t\tif (DataService.Instance.SaveData != null)\r\n\t\t\tsaveData = DataService.Instance.SaveData.ToString();\r\n\r\n\t\tstring debug = string.Format(\r\n\t\t\t\"Currently Loaded Profile number: {0}\\n\" + \r\n\t\t\t\"SaveData: \\n{1}\\n\\n\" + \r\n\t\t\t\"Press 1\/2 to inc\/dec coins\\n\" +\r\n\t\t\t\"Press Q\/W to inc\/dec health\\n\" + \r\n\t\t\t\"Press A\/S to inc\/dec life\\n\" + \r\n\t\t\t\"Press F to add\/remove Fireballs powerup\\n\" +\r\n\t\t\t\"Press D to add\/remove DoubleJump powerup\\n\" + \r\n\t\t\t\"Press ENTER to save\\n\" + \r\n\t\t\t\"Press SPACE to load next level\\n\" + \r\n\t\t\t\"Press M for menu\",\r\n\t\t\tDataService.Instance.currentlyLoadedProfileNumber,\r\n\t\t\tsaveData\r\n\t\t\t);\r\n\r\n\t\t\r\n\t\tGUI.Label(new Rect(0, 0, Screen.width, Screen.height), debug);\r\n\t}\r\n\r\n\r\n}\r\n<\/pre>\n<p>After you&#8217;ve added all of this code to your project, test it out by following the on-screen instructions provided by TestSaveData.cs. Examine the changes to the save files, get a feel for when and how the data is loaded. And most of all, enjoy the magic of (de)serialization!<\/p>\n<h2>Next Steps<\/h2>\n<p><a href=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/xml-json.jpg\" rel=\"lightbox[176]\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-207 alignright\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/xml-json-300x105.jpg\" alt=\"xml-json\" width=\"300\" height=\"105\" srcset=\"https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/xml-json-300x105.jpg 300w, https:\/\/naplandgames.com\/blog\/wp-content\/uploads\/2016\/11\/xml-json.jpg 600w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a>Now that you&#8217;ve had an introduction on saving data to file with JSON, take a look at some other serialization methods like .NET&#8217;s <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.io.binarywriter(v=vs.110).aspx\" target=\"_blank\" rel=\"noopener\">BinaryWriter <\/a>or <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/58a18dwa(v=vs.110).aspx\">XmlSerializer<\/a>. Neither is as neat and easy to use as a Unity&#8217;s Json Utility, but they have their merits. BinaryWriter will be harder for end-users to tinker with, it is the most compact, and the easiest to encrypt, but it is the most difficult to transfer to other applications. XML is probably the easiest for a human to read, easy to transfer between applications, but it is the least compact and most complex to write code for. JSON is a good middle-of the road serialization method. It&#8217;s not too hard for a human to read, it&#8217;s fairly compact, it&#8217;s not too difficult to encrypt, and it is easily read by other applications. I&#8217;d also suggest exploring some JSON readers such as <a href=\"https:\/\/sourceforge.net\/projects\/nppjsonviewer\/\" target=\"_blank\" rel=\"noopener\">JSON Reader for Notepad++<\/a>. Spend some time playing around with each of these methods to get a feel for them.<\/p>\n<p>You can get the full project with source from this repository on Github:<\/p>\n<p><a href=\"https:\/\/github.com\/Naphier\/Unity-Data_Management-Examples\/releases\/tag\/v1.0\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/Naphier\/Unity-Data_Management-Examples\/releases\/tag\/v1.0<\/a><\/p>\n<p>If you&#8217;re interested in diving deeper and need some one-on-one help, <a href=\"http:\/\/www.naplandgames.com\/contact.php\">give me a shout<\/a>! I&#8217;m a Unity Certified Developer and I give private lessons in a variety of programming topics.<\/p>\n<p>As always, thanks for reading and don&#8217;t forget to subscribe to my email list to hear about new blog posts!<\/p>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_176\" class=\"pvc_stats all  \" data-element-id=\"176\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"excerpt":{"rendered":"<p>With games we almost always need to save some data: scores, inventory, progress, preferences, etc. There are many ways to do this in Unity. In this article I&#8217;ll take you through a few different ways to save data and hopefully encourage some good habits when structuring your applications for saved data. There are two main levels of persistency for data: Session Data\u00a0(data doesn&#8217;t necessarily need to be remembered the next time you load up the application): This is usually done via static variables and is used for only values that matter for the current session. Most commonly this is for&#8230;<\/p>\n<div class=\"more-link-wrapper\"><a class=\"more-link\" href=\"https:\/\/naplandgames.com\/blog\/2016\/11\/27\/saving-data-in-unity-3d-serialization-for-beginners\/\">Continue reading<span class=\"screen-reader-text\">Saving Data in Unity 3D (Serialization for Beginners)<\/span><\/a><\/div>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_176\" class=\"pvc_stats all  \" data-element-id=\"176\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/naplandgames.com\/blog\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"author":1,"featured_media":213,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[41,38,44,40,43,42,35,36,37,39,8,13,7],"a3_pvc":{"activated":true,"total_views":31948,"today_views":0},"_links":{"self":[{"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/posts\/176"}],"collection":[{"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/comments?post=176"}],"version-history":[{"count":14,"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/posts\/176\/revisions"}],"predecessor-version":[{"id":319,"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/posts\/176\/revisions\/319"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/media\/213"}],"wp:attachment":[{"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/media?parent=176"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/categories?post=176"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/naplandgames.com\/blog\/wp-json\/wp\/v2\/tags?post=176"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}