Search code examples
c#unity-game-enginesavesystem

Unity 3D 2018.1 - Save


I started doing it a week ago, a simple build game, and now I want a "Save-System". The problem is that whatever I've tried so far, I could not do it. Specifically, I do not know if it would have worked because it would have to stored a 100x100 size ground, plus trees, stones, and constructions. The game randomly generates the world at the start of each run.

Any idea what can I do?


Solution

  • PlayerPrefs and json serialization will be your friends for a relatively simple save system. But in order to do this, you need to ensure that you are only using the supported PlayerPrefs types: int, float or string. That doesn't provide a lot of wiggle room, so the best way to take advantage of those is to use json, since json is represented as a string. For how to use Unity's built in json serializer read here.

    What can you expect with this setup? Well let's take the following setup:

    using System;
    using UnityEngine;
    
    [Serializable]
    public class PlayerStats
    {
        public int Health;
        public int Energy;
        public Vector3 Position;
    
        public static string Serialize(PlayerStats playerStats)
        {
            if (playerStats == null)
                return "";
    
            return JsonUtility.ToJson(playerStats);
        }
    
        public static PlayerStats Deserialize(string json)
        {
            if (string.IsNullOrEmpty(json))
                return new PlayerStats();
    
            return JsonUtility.FromJson<PlayerStats>(json);
        }
    }
    
    public static class GameManager
    {
        public const string PLAYER_STATS_SAVE_KEY = "PLAYER_STATS";
    
        public static PlayerStats Player;
    
        public static void Save()
        {
            string json = PlayerStats.Serialize(Player);
            PlayerPrefs.SetString(PLAYER_STATS_SAVE_KEY, json);
        }
    
        public static void Load()
        {
            if (!PlayerPrefs.HasKey(PLAYER_STATS_SAVE_KEY))
            {
                Player = new PlayerStats();
            }
            else
            {
                string json = PlayerPrefs.GetString(PLAYER_STATS_SAVE_KEY);
                Player = PlayerStats.Deserialize(json);
            }
        }
    }
    

    Here you have 2 classes - PlayerStats and GameManager. PlayerStats will act as the container for the data you want to serialize (and later save and load). So you throw the [Serializable] attribute on it. Add the fields for the data you want to save: Health, Energy and Position. Then create some static methods for Serializing and Deserializing the PlayerStats instance into a Json string. When we run the Serialize() method on the class, we might get a string that looks like this:

    {
      "Health": 10,
      "Energy": 10,
      "Position": {
        "x": 1,
        "y": 1,
        "z": 1
      }
    }
    

    That means we can save it in PlayerPrefs now since we've gotten all that data converted to a string. That's where the GameManager class comes in. Let's assume you're storing your PlayerStats in a static field in this class. When the player clicks the save button, you would just run GameManager.Save() and it would use the PlayerStats.Serialize() method to get the json of the current PlayerStats. Then using PlayerPrefs, it would store that string at a specific key. The next time you want to load those player stats back, you can just call GameManager.Load() and it will retrieve the saved json from PlayerPrefs, deserialize it into a PlayerStats instance and assign that to your global PlayerStats field.

    As for your world generation, I would highly recommend switching to a seeded random generator. This will allow you to generate a random seed which can be saved and reliably used to regenerate the same random data. This can be done with the System.Random(int32) (docs here) class. So instead of saving all of the world data, you just save the seed and use that to regenerate the world.