Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

[SOLVED] Advanced game save

Discussion in 'Scripting' started by Juhas0, Oct 6, 2016.

  1. Juhas0

    Juhas0

    Joined:
    Oct 4, 2016
    Posts:
    8
    Hi, I watched tutorials about saving game and all of them used just a simple serializable class. This is enought for simple projects. But what if we have couple od scenes and many interactive objects like for example doors? I would like to save door state for every door (open / closed). So what is the most proper way to do that?

    Do I have to create my own saving mechanism? Or can I still use serializable class? What about other scenes? Do they also need to be saved? And one more thing - binary compatibility. Assume that we've got a game and people play it and make saves. Now, something changes in a patch. This change influences save file structure. So, can I still use a serializable class in some way, or do I have to create my own dedicated load/save mechanism with all these fancy stuff like file version?
     
  2. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    Saving games is a pretty large topic on its own and i don't think there is any 1 size fits all answer. Each project is going to require its own way of doing things. Patches ruining save games is something is a problem and many games have had that issues. Some patches could even have methods to update save games from old to new based on version numbers.

    You might need to get clever with your solutions. Want to save the state of every door? Well create two arrays in parrallel that stores the Door's ID and its state
     
  3. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    1,065
    A simple and single serializable class should be enough to do pretty much any save you want. Saving is simply storing data, even using only strings will suffice, it should be accessible regardless of scene. If you need a number to represent every door's save state, so be it, you'll just have to create that many in your save file, or that many save files depending on how you plan it, and access them from the scripts which need it.

    Unless your game world is persistent, there should not be much data to save. If your game world is not persistent, you can use static variables to save the state of your doors and have that data be transferable between scenes, but not between game sessions since it's not actual saving, it's sort of just "saving" within a single play session.
     
  4. jimroberts

    jimroberts

    Joined:
    Sep 4, 2014
    Posts:
    560
    I use a custom ISerializerData type to handle all of my serialization. My primary binary implementation of ISerializerData adds a new variable by converting the value to a byte array. The byte array is then stored as a data pair with the data type(for verification during deserialization) in a dictionary using the name as the key.
    Code (csharp):
    1. public interface ISerializerData
    2. {
    3.    void Serialize(ISerializable serializable);//indirection used for "Type" verification
    4.    void Deserialize(ISerializable serializable);//indirection used for "Type" verification
    5.  
    6.    void Add(string name, Guid value);
    7.    void Add(string name, DateTime value);
    8.    void Add(string name, string value);
    9.    void Add(string name, char value);
    10.    void Add(string name, byte value);
    11.    void Add(string name, sbyte value);
    12.    void Add(string name, short value);
    13.    void Add(string name, ushort value);
    14.    void Add(string name, int value);
    15.    void Add(string name, uint value);
    16.    void Add(string name, long value);
    17.    void Add(string name, ulong value);
    18.    void Add(string name, bool value);
    19.    void Add(string name, float value);
    20.    void Add(string name, double value);
    21.    void Add(string name, Vector2 value);
    22.    void Add(string name, Vector3 value);
    23.    void Add(string name, Quaternion value);
    24.    void Add(string name, Color value);
    25.  
    26.    Guid GetGuid(string name);
    27.    DateTime GetDateTime(string name);
    28.    string GetString(string name);
    29.    char GetChar(string name);
    30.    byte GetByte(string name);
    31.    sbyte GetSByte(string name);
    32.    short GetShort(string name);
    33.    ushort GetUShort(string name);
    34.    int GetInt(string name);
    35.    uint GetUInt(string name);
    36.    long GetLong(string name);
    37.    ulong GetULong(string name);
    38.    bool GetBool(string name);
    39.    float GetFloat(string name);
    40.    double GetDouble(string name);
    41.    Vector2 GetVector2(string name);
    42.    Vector3 GetVector3(string name);
    43.    Quaternion GetQuaternion(string name);
    44.    Color GetColor(string name);
    45. }
    I then have another interface for ISerializable which I implement on all of my serializable scripts.
    Code (csharp):
    1. public interface ISerializable
    2. {
    3.    void Serialize(ISerializerData data);
    4.    void Deserialize(ISerializerData data);
    5. }
    This gives me complete control over versioning and allows me to easily pick and choose what should be saved.
    Code (csharp):
    1. public class SomeBehaviour : MonoBehaviour, ISerializable
    2. {
    3.    private const int Version = 1;
    4.  
    5.    public void Serialize(ISerializerData data)
    6.    {
    7.      data.Add("Version", Version);
    8.    }
    9.  
    10.    public void Deserialize(ISerializerData data)
    11.    {
    12.      int version = data.GetInt("Version");
    13.      switch (version)
    14.      {
    15.        case 1:
    16.          //deserialize version 1
    17.          break;
    18.        case 2:
    19.          //deserialize version 2
    20.          break;
    21.        default:
    22.          throw new System.NotImplementedException("Deserialization for version<" + version + "> of " + this.GetType().FullName + " is not implemented!");
    23.      }
    24.    }
    25. }
    You can then use an ISerializerData implementation on any ISerializable object.
    Code (csharp):
    1. SomeBehaviour myBehaviour = GetComponent<SomeBehaviour>();
    2.  
    3. MyISerializerDataImpl data = new MyISerializerDataImpl();
    4.  
    5. data.Serialize(myBehaviour);
    6. data.Deserialize(myBehaviour);
    7.  
    8. //or if you don't care about Type verification
    9. myBehaviour.Serialize(data);
    10. myBehaviour.Deserialize(data);
    The final steps are reading and writing the ISerializerData implementation to disk which should be incredibly easy for anyone to implement...

    P.S. It's effectively the same as using the BinaryFormatter and the C# ISerializable interface without the need for the "special" constructor.
     
    Last edited: Oct 7, 2016
    Stardog and Juhas0 like this.
  5. Juhas0

    Juhas0

    Joined:
    Oct 4, 2016
    Posts:
    8
    Jimroberts, I was thinking about such solution. I have implemented something similar in one of my desktop apps. You have convinced me that I need to create my own mechanism.