Forum rules - please read before posting.

How to save custom lists that contains scriptableobjects?

Hi,
I have a question about how saving lists and scriptableobject references would work? In my game my player gameobject has the CharacterParty script which handles which characters are in my party. See image: https://imgur.com/4hbaCGZ

ill try to keep my code short and simple but here is what CharacterParty script looks like

    public class CharacterParty : MonoBehaviour
    {
        [SerializeField]List<Character> characters;

        //Property for list of all characters
        public List<Character> Characters
        {
            get
            {
                return characters;
            }
        }

    }

The Character script: This is where the stats exist like hp, level etc:
[System.Serializable]
public class Character
{

    [SerializeField] CharacterBase _base;
    [SerializeField] int level;

    //reference to our CharacterBase(ScriptableObject) class that we can use as our base
    public CharacterBase CharacterBase {
        get
        {
            return _base;
        }

    }
    public int Level {
        get
        {
            return level;
        }
    }

    public int HP { get; set; }

    public int Stamina { get; set; }

    //list for all the abilities the character has
    public List<Ability> Abilities { get; set; }
}

My CharacterBase class is a scriptableobject and this is what it looks like:

[CreateAssetMenu(fileName = "Character", menuName = "Character/Create new character")]
public class CharacterBase : ScriptableObject
{
    //Base Variables
    [SerializeField] string name;

    [TextArea]
    [SerializeField] string description;

    [SerializeField] Sprite frontSprite;
    [SerializeField] Sprite backSprite;

    //Properties
    public string Name
    {
        get { return name; }

    }
    public string Description
    {
        get { return description; }

    }
    public Sprite FrontSprite
    {
        get { return frontSprite; }

    }
    public Sprite BackSprite
    {
        get { return backSprite; }

    }

}

I've been reading https://adventurecreator.org/tutorials/saving-custom-scene-data and https://adventurecreator.org/tutorials/saving-custom-global-data . I tried the custom scene data method by creating my own RememberParty script but i can't seem to get it to work, I tried saving only the HP for the first index character in party and that works but I want it to save the characterbase references, hp, level and all the stats etc for each character in my party.

I would really appreciate it if you could help me with this, I just need to save my characterparty so when I save and load it will have the correct data in the party.

Comments

  • edited April 2021

    And this is my RememberParty script for AC that I was experimenting with. This only saves the hp for the first character but I don't know how saving the whole lists would work with all the data like hp, level, and scriptableobject ref etc.

    namespace AC
    {
        //To save CharacterParty we gotta use that type?
        [RequireComponent(typeof(CharacterParty))]
        public class RememberParty : Remember
        {
            public override string SaveData()
            {
                CharacterParty cParty = GetComponent<CharacterParty>();
    
                PartyData partyData = new PartyData();
    
                partyData.hp = cParty.Characters[0].HP;
                partyData.objectID = constantID;
    
                return Serializer.SaveScriptData<PartyData>(partyData);
            }
    
            public override void LoadData(string stringData)
            {
                PartyData data = Serializer.LoadScriptData<PartyData>(stringData);
                if (data == null) return;
    
                CharacterParty cParty = GetComponent<CharacterParty>();
                cParty.Characters[0].HP = data.hp;
    
            }
        }
    
    
        [System.Serializable]
        public class PartyData : RememberData
        {
            public int hp;
    
            public PartyData() { }
        }
    
    }
    
  • edited April 2021

    The actual data that gets saved (i.e. the variables in a RememberData subclass) need to be basic value types - int, string, float, etc. You couldn't serialize e.g. an array or List in a RememberData subclass.

    One easy way that may work: if you make your CharacterParty a regular class (not Mono), you could then use JsonUtility.ToString to serialize into a string.

    Otherwise:

    AC frequently has to store such data types, however, and it does so by condensing data into a string, separated by a common character, e.g. ",".

    For example, let's say we had an int[] array with values 3, 6 and 8. This could be condensed into a single string:

    3,6,8
    

    When loading, the string could then be split by the "," character to re-form the array.

    Since the data is ultimately saved as a string, you're not limited to only combining data of the same type. You could, for example, store a character's Name, HP and Level together:

    John,65,14
    

    In your case, though, you need to be able to store multiple sets of this data. This would mean using another separator (let's say "|") to separate the various Character data. For example:

    John,65,14|Dave,14,32|Mary,53,24
    

    Things are also complicated still by the presence of your "Abilities" List, so this too would need its own separator:

    John,65,14,Fire~Ice|Dave,14,32,Shock~Ice|Mary,53,24,Fire
    

    (Saving the name, HP, Level and Abilities of three characters)

    What you'd need to do is start at the bottom-level of your data set (in this case, Abilities), and serialize that as a string. Just with one character at first, you want a function that converts an Abilities list into e.g.:

    Fire~Ice
    

    Then, move one level up, and write a function in your Character script that generates a string that combines all data, e.g.:

    John,65,14,Fire~Ice
    

    Then, move to the top level (CharacterParty), and write a function that calls this function for each character and combines the results into the final string:

    John,65,14,Fire~Ice|Dave,14,32,Shock~Ice|Mary,53,24,Fire
    

    Deserializing this data would then be a case of going backwards. I'd recommend just focusing on serializing the data in the above way first.

    For an example on AC doing this, see the "buttonStates" string in RememberHotspot, which records the enabled states of all Buttons associated with a given Hotspot.

  • edited April 2021

    @ChrisIceBox I'm gonna give those methods a try thanks. I have also gotten another asset to help with the saving and its called the Easy Save (https://assetstore.unity.com/packages/tools/input-management/easy-save-the-complete-save-data-serialization-asset-768) and I was wondering if I can use my method of saving using Easy Save on AC?

    This is my Easy Save Functions:

        //SAVE PARTY
        public void SaveParty()
        {
            SaveCharacterReferences();
            SaveCharacterStats();
        }
        //LOAD PARTY
        public void LoadParty()
        {
            LoadCharacterReferences();
            LoadCharacterStats();
        }
        //STATS
        public void SaveCharacterStats()
        {
            for (int i = 0; i < characters.Count; i++)
            {
                //STATS
                ES3.Save("characterHP" + i, characters[i].HP);
                int valuedebug = characters[i].HP;
                Debug.Log(characters[i].CharacterBase.Name + ": " + valuedebug);
            }
        }
        public void LoadCharacterStats()
        {
            for (int i = 0; i < characters.Count; i++)
            {
                //STATS
                characters[i].HP = ES3.Load("characterHP" + i, 0);
            }
        }
        //SAVING REFERENCES TO CharacterBase SCRIPTABLEOBJECT
        public void SaveCharacterReferences()
        {
            ES3.Save("party", characters);
        }
        //LOADING REFERENCES TO CharacterBase SCRIPTABLEOBJECT
        public void LoadCharacterReferences()
        {
            characters = ES3.Load("party", characters);
    
            for (int i = 0; i < characters.Count; i++)
            {
                //If there is an empty CharacterBase on one of the lists then remove that entire list since it means there is no character there
                if (characters[i].CharacterBase == null)
                {
                    characters.RemoveAt(i);
                }
            }
            //important to reinitialize the characters after changes such as adding, removing or other changes with character
            foreach (var character in characters)
            {
                character.Init();
            }
        }
    

    In that example I am able to save everything, right now its character reference and hp as an example but I can easily add other stuff to it. So basically is there a way I can somehow combine it with AC's Save? so that it will call my SaveParty() and LoadParty() methods

  • edited April 2021

    To incorporate another asset's serialization into the AC save file, its data would still need to be condensed into a string value type that can be stored in the RememberData subclass.

    Otherwise, you'd be looking at having to save two separate save files (AC and EasySave) per save-game, and load them together - so things'll get a lot more complicated.

    I'd recommend trying the Json method I first mentioned, but personally I prefer full control over the data format so I'd go with the second myself.

  • edited April 2021

    Yeah I would also like to try the second method you mentioned because I feel like removng the mono from CharacterParty would complicate other things that I would need to change.

    Would you say something like this is the correct way to go?

    //Variables: Character Name & HP stat TEST
        public void StringBuilderTest()
        {
            System.Text.StringBuilder myString = new System.Text.StringBuilder();
    
            for (int i = 0; i < characters.Count; i++)
            {
                //if its not the last string then put "," after it
                if (i < characters.Count - 1)
                {
                    myString.Append(characters[i].CharacterBase.Name + ",");
                    myString.Append(characters[i].HP + ",");
                }
                //if its the last string then don't put "," at the end
                else
                {
                    myString.Append(characters[i].CharacterBase.Name + "," + characters[i].HP);
                }
    
            }
    
            Debug.Log(myString);
            //RESULT: "Andy,135,Chris,120,Nathan,105"
        }
    

    Would something like this be used for RememberParty script or a global string? Lets say I have the full string made. What would the next step be in how you use that string to save and load etc would work?

    Thanks so much for you help

  • I feel like removng the mono from CharacterParty would complicate other things that I would need to change.

    It shouldn't be so complicated - you still have a Mono on your GameObject, just with a reference to CharacterParty inside it, i.e.:

    public CharacterParty characterParty;
    

    Then just reference this new Mono script's characterParty variable instead of the Mono script itself.

    Would you say something like this is the correct way to go?

    You'll need to use a different separator for each type of data. For example, separate a single character's stats with ",", and separate each character with "|":

    Andy,135|Chris,120|Nathan,105
    

    This is so that, when it comes to loading, it's clear what each separator refers to.

    Would something like this be used for RememberParty script or a global string?

    It can be either, but if you already have a RememberParty script it's best to go with that - at least for now. With the data converted into a string, you can then have it update a "dataString" string variable in your PartyData class.

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.