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. Dismiss Notice

Saving and loading a ton of variables, c#

Discussion in 'Scripting' started by Collin__Patrick, Apr 23, 2016.

  1. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    I am creating a game similar to fire emblem. I have a script that I attached to all of my player prefabs that I can edit all of the variables in in inspector. I want to be able to save and load the variables such as stat points and level and assign them to the correct entity of the "CharacterSheet" script. there are going to be about 12 variables to be saved per character and about 40 characters. So in total I need to save and load up to 500 variables and load them to the correct script.

    Character sheet script for reference:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class CharacterSheet : Character{
    5.  
    6.     public string characterClass;
    7.  
    8.     public int strength
    9.     {
    10.         get { return CurrentClass.Strength + baseStrength; }
    11.     }
    12.     public int magic
    13.     {
    14.         get { return CurrentClass.Magic + baseMagic; }
    15.     }
    16.     public int speed
    17.     {
    18.         get { return CurrentClass.Speed; }
    19.     }
    20.     public int agility
    21.     {
    22.         get { return CurrentClass.Agility + baseAgility; }
    23.     }
    24.     public int defense
    25.     {
    26.         get { return CurrentClass.Defense + baseDefense; }
    27.     }
    28.     public int luck
    29.     {
    30.         get { return CurrentClass.Luck + baseLuck; }
    31.     }
    32.     public int health
    33.     {
    34.         get { return CurrentClass.Health + baseHealth; }
    35.     }
    36.  
    37.  
    38.     void Start(){
    39.         if (characterClass == "FighterClass") {
    40.             CurrentClass = new FighterClass ();
    41.         } else if (characterClass == "MageClass") {
    42.  
    43.         } else if (characterClass == "ArcherClass") {
    44.  
    45.         } else if (characterClass == "SpearmanClass") {
    46.  
    47.         }
    48.  
    49.         //current level and exp
    50.         level = 1;
    51.         exp = 0;
    52.         neededExp = 100;
    53.  
    54.         //Testing if variables are being assigned and working
    55.         Debug.Log (characterName);
    56.         Debug.Log (CurrentClass.ClassName);
    57.         Debug.Log (CurrentClass.ClassDescription);
    58.         Debug.Log (CurrentClass.ClassWeaponType);
    59.         Debug.Log (CurrentClass.ClassStrength);
    60.         Debug.Log (CurrentClass.ClassWeakness);
    61.         Debug.Log (strength);
    62.         Debug.Log (magic);
    63.         Debug.Log (speed);
    64.         Debug.Log (agility);
    65.         Debug.Log (defense);
    66.         Debug.Log (luck);
    67.         Debug.Log (health);
    68.  
    69.         AddExp (10000);
    70.  
    71.     }
    72.     //Calculates and determins if this character levels up and how many times they do when exp is given.
    73.     public static void AddExp(int EXP){
    74.         exp += EXP;
    75.         Debug.Log ( " gained " + EXP + " EXP!");
    76.         while (exp >= neededExp) {
    77.             level ++;
    78.             Debug.Log(" leveled up to " + level);
    79.             exp -= neededExp;
    80.             neededExp += 50;
    81.         }
    82.         Debug.Log (exp);
    83.         Debug.Log (neededExp);
    84.     }
    85.  
    86. }
    What the inspector looks like:
    Screenshot 2016-04-23 10.29.20.png
     
  2. Ian094

    Ian094

    Joined:
    Jun 20, 2013
    Posts:
    1,548
    Have you taken a look at the BinaryFormatter? I suppose it would work well here.

    Check out the Unity tutorial here.
     
  3. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    Further to the above - you could use the XmlSerializer. Bang all your "CharacterSheets" into a collection and use the xmlserializer to save the whole thing.
     
  4. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    Currently I am trying to do BinaryFormatter but I am not sure as to how to grab and assign variable to the correct entity of the script between saving and loading. I cant just say "playerdata.exp = charactersheet.exp" for saving and "charactersheet.exp = playerdata.exp" for loading. Becasue there will be almost 40 different entity's of "charactersheet", it will be very hard to reference each of the 40 prefabs and save/load up to 12 variables to each prefab.
     
  5. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    How could I? all of the character sheets are named "characterSheet" and all I do is attach it to a object and assign variables in the inspector.
     
  6. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    Why can't you mark the class with Serializable property? That's what you would use with the BinaryFormatter
     
  7. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    How would that work though. let me give an example. I have 3 characters with the CharacterSheet script on them and they all have different values stored in each variable(Example: Character 1 HP: 5, Character 2 HP: 7, Character 3 HP: 11) If I put [Serializable] at the top of the CharacterSheet script, would it save all 3 variables and load them to their respective entity of that script or will it jumble all the numbers up and apply the same variable to all the scripts(Example: Character 1 HP: 5, Character 2 HP: 5, Character 3 HP: 5) .
     
  8. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    If you want to go down the xml route - put your characters into a List<T> and call the below to save to disk.

    Code (CSharp):
    1. public void SaveCharacters(List<CharacterSheet> characters, string filename)
    2. {
    3.     using (StreamWriter writer = new StreamWriter(Path.Combine(Application.persistentDataPath, filename + ".xml")))
    4.     {
    5.         XmlSerializer serialiser = new XmlSerializer(typeof(List<CharacterSheet>));
    6.  
    7.         serialiser.Serialize(writer, characters);
    8.     }
    9. }
     
  9. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    So I would just put all of my prefabs in a list then save it and load them from the saved list whenever I need them?
     
  10. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    Yeah just save the scripts assigned to them
     
  11. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    Okay I have the code but where to I put the prefabs? do I need to create another list that appears in the inspector?
     
  12. astrokoala

    astrokoala

    Joined:
    Mar 23, 2016
    Posts:
    22
    those are Models in MVP pattern, (Object that holds data configuration and no logic)

    luckily, Unity has a built-in class called "Scriptable Object",
     
  13. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    Also I am getting an error:
    InvalidOperationException: To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. UnityEngine.Transform does not implement Add(System.Object).
     
  14. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    When you re-instantiate an entity from a save file, you'll be loading up the saved character sheet data back into the entity, as long as that's how it was serialized to the file. I'd like to know some reasons why you'd get "jumbled" numbers. One way I can think of is if you use a Dictionary or Set or something, since the order can't be guaranteed. In this case, you should have at least some unique identifier for the character and its associated character sheet.
     
  15. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    There is a string where I assign the characters name in the inspector. would that work as an identifier?
     
  16. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    As long as it's unique. Why not? One reason I can think that they might not be unique is NPCs. If you have a generic NPC, you could have them numbered or something. NPC01, NPC02, etc. The key is to make them unique. You'll hear that a lot when identifying your item data. Unique Identifiers and Globally Unique Identifiers (guids), etc. This allows you to, well, identify any item in your game. This is especially useful when saving/loading. Because you can't save references to file. Well, you can, but they won't mean anything when you deserialize it. But you can save ids to reference an entity to its various data that its related to.

    Oh, and one more step to be more pro, is it's better to store unique ids as integers, rather than names. Storing names will eat up memory and disk usage, and is more error prone if the serialization process resolves the strings to variable length encoding. Better to serialize ids, and then map the ids to names in a different object.
     
  17. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    Presumable CharacterSheet inherits from monobehaviour at some point - you don't want to serialise the whole unity gameobject as it'll be massive. Instead create a model (just a class) that contains the properties you need to save.
     
  18. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    Alright. So any suggestions on how to ID things? If I am using Integers, how would I be able to tell the difference between a character, NPC, Enemy, Weapon, and item? Also how would I keep track of what has what ID?
     
  19. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    So I would have to change how I inherited things?

    Code (CSharp):
    1. public abstract class Character{
    2.  
    3.     public ICharacterClass CurrentClass;
    4.  
    5.     public string characterName;
    6.    
    7.     public static int level;
    8.     public static int exp;
    9.     public static int neededExp;
    10.  
    11.     public int baseStrength;
    12.     public int baseMagic;
    13.     public string baseSpeed = "DO NOT CHANGE";
    14.     public int baseAgility;
    15.     public int baseDefense;
    16.     public int baseLuck;
    17.     public int baseHealth;
    18. }
    Code (CSharp):
    1. public class CharacterSheet : Character{
    2.  
    3.     public string characterClass;
    4.  
    5.     public static bool alive = true;
    6.  
    7.     public static bool characterActive = false;
    8.  
    9.     public int strength
    10.     {
    11.         get { return CurrentClass.Strength + baseStrength; }
    12.     }
    13.     public int magic
    14.     {
    15.         get { return CurrentClass.Magic + baseMagic; }
    16.     }
    17.     public int speed
    18.     {
    19.         get { return CurrentClass.Speed; }
    20.     }
    21.     public int agility
    22.     {
    23.         get { return CurrentClass.Agility + baseAgility; }
    24.     }
    25.     public int defense
    26.     {
    27.         get { return CurrentClass.Defense + baseDefense; }
    28.     }
    29.     public int luck
    30.     {
    31.         get { return CurrentClass.Luck + baseLuck; }
    32.     }
    33.     public int health
    34.     {
    35.         get { return CurrentClass.Health + baseHealth; }
    36.     }
    37.  
    38.  
    39.     void Start(){
    40.         if (characterClass == "FighterClass") {
    41.             CurrentClass = new FighterClass ();
    42.         } else if (characterClass == "MageClass") {
    43.  
    44.         } else if (characterClass == "ArcherClass") {
    45.  
    46.         } else if (characterClass == "SpearmanClass") {
    47.  
    48.         }
    49.    
    50.         //current level and exp
    51.         level = 1;
    52.         exp = 0;
    53.         neededExp = 100;
    54.  
    55.         //Testing if variables are being assigned and working
    56.         Debug.Log (characterName);
    57.         Debug.Log (CurrentClass.ClassName);
    58.         Debug.Log (CurrentClass.ClassDescription);
    59.         Debug.Log (CurrentClass.ClassWeaponType);
    60.         Debug.Log (CurrentClass.ClassStrength);
    61.         Debug.Log (CurrentClass.ClassWeakness);
    62.         Debug.Log (strength);
    63.         Debug.Log (magic);
    64.         Debug.Log (speed);
    65.         Debug.Log (agility);
    66.         Debug.Log (defense);
    67.         Debug.Log (luck);
    68.         Debug.Log (health);
    69.  
    70.         AddExp (10000);
    71.  
    72.     }
    73.     //Calculates and determins if this character levels up and how many times they do when exp is given.
    74.     public static void AddExp(int EXP){
    75.         exp += EXP;
    76.         Debug.Log ( " gained " + EXP + " EXP!");
    77.         while (exp >= neededExp) {
    78.             level ++;
    79.             Debug.Log(" leveled up to " + level);
    80.             exp -= neededExp;
    81.             neededExp += 50;
    82.         }
    83.         Debug.Log (exp);
    84.         Debug.Log (neededExp);
    85.     }
    86.  
    87. }
     
  20. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    Just make another object which contains the properties you need to save, then when you want to save make an instance of it and copy values from CharacterSheet to it.
     
  21. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    This all loops back to my original problem, how? I must first find a way to to get and set values both ways but with several instances of the same script, this can become a problem. How would I know that every variable is being saved and loaded and how would I apply getters and setters to variables that have unique values for each instance of the same script?
     
  22. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    Basically you'd get back a list of "model objects" when you loaded - you'd use these to create / modify your character sheets. Rather than creating them in the designer you could create them programmatically - which would just be a new character sheet for each model object. If you don't want to do that then you need to have a unique id on each character sheet so that you can map the models to them.
     
  23. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    Here a quick example of using a character manager game object to look after all your character sheets.

    Code (CSharp):
    1. public class CharacterManager : MonoBehaviour
    2. {
    3.     private List<GameObject> _characters;
    4.  
    5.     public void Start()
    6.     {
    7.         _characters = new List<GameObject>();
    8.    
    9.         // if you want to create them programatically
    10.  
    11.         for (int i = 0; i < HowManyYouWant; i++)
    12.         {
    13.             // Create the character prefab
    14.             _characters.Add(Instantiate(CharacterPrefab));
    15.        
    16.             // Maybe set some properties on it
    17.             var character = _characters.Last().GetComponent<CharacterSheet>();
    18.             character.strength = 10;
    19.             character.defense = 20;
    20.         }
    21.    
    22.         // or if you want to get them from the scene
    23.    
    24.         _characters.add(GameObject.Find("Character1"));
    25.         _characters.add(GameObject.Find("Character2"));
    26.     }
    27.  
    28.     public void Save()
    29.     {
    30.         // Loop through the character sheets and create a model for each of them
    31.         List<CharacterModel> toSave = _characters.Select(x => new CharacterModel() { strength = x.GetComponent<CharacterSheet>().strength }).ToList();
    32.    
    33.         // save above list to file
    34.     }
    35.  
    36.     public void Load()
    37.     {
    38.         var loadedCharcters // load from xml file
    39.    
    40.         // loop through _characters and find the matching loadedCharcters item and set properties
    41.     }
    42. }
     
  24. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    is there a way that I could group all of the variables in each script in its own container that is marked with an ID then just save/load from matching containers and script ID's? So pretty much I would set an ID in the inspector and when I instantiate a character it would grab whatever container has the same ID and apply the variables inside that container.
     
  25. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    Here is an Example of what I am saying:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class TestContainers : MonoBehaviour {
    5.  
    6.     public static string[] names = new string[10];
    7.     public static int[] health = new int[names.Length];
    8.  
    9.     public OtherTest otherTest;
    10.  
    11.     public void SaveData(int ID, GameObject CharacterObject){
    12.         otherTest = CharacterObject.GetComponent<OtherTest> ();
    13.         health [ID] = otherTest.characterHealth;
    14.     }
    15.  
    16.     public void LoadData(int ID, GameObject CharacterObject){
    17.         otherTest = CharacterObject.GetComponent<OtherTest> ();
    18.         otherTest.characterHealth = health [ID];
    19.  
    20.     }
    21. }
    22.  
    23. public class OtherTest{
    24.     public GameObject CharacterObject;
    25.     public int characterID = 1;
    26.  
    27.     public int characterHealth;
    28.  
    29.     void Awake(){
    30.         TestContainers.LoadData (characterID, CharacterObject);
    31.     }
    32.  
    33.     public bool save = false;
    34.     void Update(){
    35.         if (save == true) {
    36.             TestContainers.SaveData(characterID, CharacterObject);
    37.             save = false;
    38.         }
    39.     }
    40. }
    I know that this could use some more tweaking, but in theory would this work if I saved the "health" array assuming OtherTest would function as a charactersheet and I can type the ID in the inspector?
     
    Last edited: Apr 25, 2016
  26. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    There's no reason why you couldn't but its not a very good way - you've best just saving a model that reflects the charactersheet
     
  27. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    I ma getting a lot of errors. Am I missing any library's besides Generic?
     
  28. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    Errors in the above code? It probably won't compile - just wrote it as an example.
     
  29. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    Ok. I don't think "var" is part of c#, I could be wrong. also there are a lot of commands you were using for the generic list that do not work. Could you possibly have mixed some javaScript in there?
     
  30. ClaytonOne

    ClaytonOne

    Joined:
    Sep 5, 2015
    Posts:
    89
    var is part of c#, you need to include system.linq to get the list extensions.
     
  31. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    var is for inferred typed variables... for example you can say:

    Code (csharp):
    1.  
    2. var obj = new GameObject("Foo");
    3.  
    And the compiler will infer that the variable 'obj' is supposed to be type 'GameObject' and stick it in there.

    You can't just willy nilly use var, there has to be an inference.

    Code (csharp):
    1.  
    2. var obj; //error, compiler can't infer type
    3. obj = new GameObject("Foo");
    4.  
    In newer versions of .Net there is 'dynamic' which does allow something like that... but the version used in Unity does not support it.

    As for Jackal16's code, the use of var appears to be place-holder, a sample to demonstrate that something should be there. Don't just copy-paste code and expect it to "just work"... read the code, understand the code, and understand when we show you example code on the forums, it's not the "working solution" it's a "guidance towards a working solution".
     
  32. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    I am having trouble understanding what is happening in the save method. Could you please explain it to me?
     
  33. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    After further experimenting I came up with this, I think this might be a good solution but I keep getting an "Index out of range" error whenever I try testing the save method. I am not sure why because I filled the gameobject list in the inspector and I fill the CharacterSheet list before I try writing it to a file.

    Code (CSharp):
    1. public class CharacterManager : MonoBehaviour
    2. {
    3.     public static CharacterManager characterManager;
    4.  
    5.     void Awake () {
    6.         if (characterManager == null) {
    7.             characterManager = this;
    8.             DontDestroyOnLoad (gameObject);
    9.         }else if(characterManager != this){
    10.             Destroy(gameObject);
    11.         }
    12.     }
    13.  
    14.     public List<GameObject> CharactersSaveAndLoad = new List<GameObject>(1);
    15.     private List<CharacterSheet> CharacterSheetsSaveAndLoad = new List<CharacterSheet>(1);
    16.  
    17.     public void GetSheets(){
    18.         for (int i = 0; i < 10; i++) {
    19.             CharacterSheetsSaveAndLoad[i] = CharactersSaveAndLoad[i].GetComponent<CharacterSheet>();
    20.         }
    21.     }
    22.     public void Save(){
    23.         BinaryFormatter bf = new BinaryFormatter ();
    24.         FileStream file = File.Create (Application.persistentDataPath + "/playerInfo.dat");
    25.  
    26.         GetSheets ();
    27.         CharacterData data = new CharacterData ();
    28.  
    29.         data.Sheets = CharacterSheetsSaveAndLoad;
    30.      
    31.         bf.Serialize (file, data);
    32.         file.Close();
    33.         Debug.Log ("Save Compleated");
    34.      
    35.     }
    36. }
    37.  
    38. public class CharacterData{
    39.     public List<CharacterSheet> Sheets ;
    40. }
     
  34. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    http://docs.unity3d.com/ScriptReference/JsonUtility.html

    Code (CSharp):
    1. using UnityEngine;
    2. [Serializable]
    3. public class PlayerInfo
    4. {
    5.     public string name;
    6.     public int lives;
    7.     public float health;
    8.     public static PlayerInfo CreateFromJSON(string jsonString)
    9.     {
    10.         return JsonUtility.FromJson<PlayerInfo>(jsonString);
    11.     }
    12.     public void Load(string savedData)
    13.     {
    14.         return JsonUtility.FromJsonOverwrite(savedData, this);
    15.     }
    16.     public string SaveToString()
    17.     {
    18.         return JsonUtility.ToJson(this);
    19.     }
    20.     // Given JSON input:
    21.     // {"name":"Dr Charles","lives":3,"health":0.8}
    22.     // this example will return a PlayerInfo object with
    23.     // name == "Dr Charles", lives == 3, and health == 0.8f.
    24. }
     
  35. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    Some context would be helpful.
     
  36. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    you can use this new JSONUtility class to transform most serializable classes into a string which can easily be saved in user prefs or to file or cloud

    not binary but easier it seems
     
  37. Collin__Patrick

    Collin__Patrick

    Joined:
    May 20, 2015
    Posts:
    188
    I would prefer to have things in binary.