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

Save system

Discussion in 'Scripting' started by jseco181, Aug 2, 2022.

  1. jseco181

    jseco181

    Joined:
    May 20, 2022
    Posts:
    2
    Hello,

    I'm developing a 2D game similar to RimWorld or Prison Architect and I'm struggling with the Save System.

    Each character in the map has a script that is in charge of executing the tasks they have to do. This is the simplified version of the script:

    Code (CSharp):
    1. public class WorkerTaskAI : TaskAI
    2. {
    3.     private void Update()
    4.     {
    5.         switch (state)
    6.         {
    7.             case State.WaitingForNextTask:
    8.                 waitingTimer -= Time.deltaTime;
    9.                 if (waitingTimer <= 0)
    10.                 {
    11.                     float waitingTimerMax = UnityEngine.Random.Range(.5f, 3f); // 200ms
    12.                     waitingTimer = waitingTimerMax;
    13.                     RequestNextTask();
    14.                 }
    15.                 break;
    16.             case State.ExecutingTask:
    17.                 break;
    18.         }
    19.     }
    20.  
    21.     private void RequestNextTask()
    22.     {
    23.         TaskBase task = taskSystem.RequestNextTask(Constants.WORKER);
    24.         if (task == null)
    25.         {
    26.             state = State.ExecutingTask;
    27.             ExecuteTask_Wander();
    28.         }
    29.         else
    30.         {
    31.  
    32.             state = State.ExecutingTask;
    33.  
    34.             if (task is TaskWorker.TakeBrick)
    35.             {
    36.                 ExecuteTask_TakeBrick(task as TaskWorker.TakeBrick);
    37.                 return;
    38.             }
    39.         }
    40.     }
    41.    
    42.     private void ExecuteTask_TakeBrick(TaskWorker.TakeBrick takeBrickTask)
    43.     {
    44.         iTasks.TakeMaterialFromTransportTruck(takeBrickTask.structureGO.GetComponent<MaterialData>(), () =>
    45.         {
    46.             iTasks.DeployMaterial(takeBrickTask.structureGO.GetComponent<MaterialData>(), () =>
    47.             {
    48.                 state = State.WaitingForNextTask;
    49.             }
    50.             );
    51.         }
    52.         );
    53.     }
    54. }
    55.  
    The problem is that I need to save exactly the state of this class when I have saved and load it exactly the same, because if it was in any of the intermediate steps of the task, how can I load it at that exact point?

    I have seen ways to save the position of a GameObject and data like that, but that is not enough for me, I need something more sophisticated and I can't think how to implement it.

    Can someone help me?

    Thank you very much,
    Jorge.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,965
    For something like these games, you want to be VERY sure to have all your data in a very well-structured easy-to-serialize container from the start.

    Everything about the Unity side should be pure presentation and user intent capture, such that no state is persisted in MonoBehaviours beyond state that can be instantly synchronized with the main data.

    More generally, here are some Load/Save steps:

    https://forum.unity.com/threads/save-system-questions.930366/#post-6087384

    An excellent discussion of loading/saving in Unity3D by Xarbrough:

    https://forum.unity.com/threads/save-system.1232301/#post-7872586

    When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls
    new
    to make one, it cannot make the native engine portion of the object.

    Instead you must first create the MonoBehaviour using AddComponent<T>() on a GameObject instance, or use ScriptableObject.CreateInstance<T>() to make your SO, then use the appropriate JSON "populate object" call to fill in its public fields.

    If you want to use PlayerPrefs to save your game, it's always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

    https://gist.github.com/kurtdekker/7db0500da01c3eb2a7ac8040198ce7f6

    Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
     
    Last edited: Aug 2, 2022
    GuirieSanchez likes this.
  3. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    317
    I wrote a blog about this 7 years ago exactly on this subject. You can read it here:
    ManorLord: Serialization and Unity (manorlordgame.blogspot.com)

    Back then I was still using the BinarySerializer and since moved to JSON but all the code is still exactly the same aside from the actual save file to disk function.
     
    Kurt-Dekker and GuirieSanchez like this.
  4. jseco181

    jseco181

    Joined:
    May 20, 2022
    Posts:
    2

    How crazy, I was reading your blog and it was like reading myself these days. I literally followed the same reasoning to fix things just like yours, I'm still in the middle of the process, but I'm glad to know that there is light at the end of the tunnel :).

    With the ideas and code I've seen in your blog I think I'll be able to continue working on it.

    Thank you very much for sharing your experience.
     
    Kurt-Dekker likes this.