Saving/Loading using lists and gameobjects

Hi everyone,

I’m trying to implement a save system for my game. All single elements for the player are working as you would expect but I am also trying to save a group of AI that can be a number of different sizes. I though a list would fine but I seem to be struggling with the correct method. Im trying to save the ai’s position from the gameobject transform, the save may be working but the load function does not so I cannot test.

Thanks for any help.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

public class GameControl : MonoBehaviour
{
    public static GameControl control;
    public Transform playerPosition;
    public static float health;
    public static float food;
    public static float dryness = 100;
    public static float energy;
    public static float playposX;
    public static float playposY;
    public static float playposZ;
    public List<float> AIposX;
    public List<float> AIposY;
    public List<float> AIposZ;
    public List<float> AIAnxiety;
    public List<float> AINosiness;
    public List<float> AIExperience;
    public List<float> AIHunger;
    public List<bool> AIMother;
    public List<bool> CanMante;
    public string SaveFileName;
      
    // Use this for initialization
    void Awake ()
    {     
        if (control == null)
        {
            DontDestroyOnLoad(gameObject);
            control = this;
        }
    else if(control != this)
        {
            Destroy(gameObject);
        }
        SaveFileName = "/PlayerSave.dat";
      
    }

    //void Update()
    //{
     
    //    //Debug.Log("SavePos " + data.playerPosition.position);
     
    //}
  

    public void Save()
    {      
        Debug.Log("Save");
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.persistentDataPath+SaveFileName);

        SaveData data = new SaveData();
        data.health = health;
        data.food = food;
        data.energy = energy;
        data.dryness = dryness;
        data.score = GameOver.score;
        data.playposX = playerPosition.position.x;
        data.playposY = playerPosition.position.y;
        data.playposZ = playerPosition.position.z;
        data.AIposX.Add(1);
        Debug.Log("dataX " + data.AIposX);
     

        foreach (GameObject go in TroopStats.troopList)
        {
            Debug.Log("GOX " + go.transform.position.x);          
            data.AIposX.Add(go.transform.position.x);        
            data.AIposY.Add(go.transform.position.y);
            data.AIposZ.Add(go.transform.position.z);
            Debug.Log("dataINX " + data.AIposX);
        }

        //data.playerPosition.position = playerPosition.position;  // should be a class with all data to save so as we add to it this method satys the same.
        //Debug.Log("SavePos " + data.health);
        Debug.Log("Health " + data.health);
        bf.Serialize(file, data);
        file.Close();
        Debug.Log("Save complete");

    }

    // Update is called once per frame
    public void Load ()
    {
        Debug.Log("Load");
        if (File.Exists(Application.persistentDataPath + SaveFileName))
        {
            Debug.Log("file exists Load");
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + SaveFileName, FileMode.Open);
            SaveData data = (SaveData)bf.Deserialize(file);
            file.Close();
            health = data.health;
            energy = data.energy;
            dryness = data.dryness;
            food = data.food;
            GameOver.score = data.score;
            playerPosition.position = new Vector3 (data.playposX, data.playposY, data.playposZ);
          
            foreach (GameObject go in TroopStats.troopList)
            {
                Debug.Log("GOX " + go.transform.position.x);
                //go.transform.position = new Vector3(data.AIposX, data.AIposX, data.AIposX);
              

                Debug.Log("dataINX " + data.AIposX);
            }

        }
    }
    //Call this method before calling Save (only call save from here as last method so all data is upto date before saving/pushing to the cloud.
    //public void RefreshStats()
    //{

    //    Save();
    //}

 
}


[Serializable]
class SaveData
{
    // data to be saved. split into to types, player and AI.
    //player data will be position, all required vitals, Durability(health) food,dryness,energy.  Number of troopmembers and number of babies. Score.  Time alive.
    //AI data required foreach troopmember and babies. ICE vitals Anxiety,Experience, if motherhood for females, position on map,
  
    public float health;
    public float food;
    public float dryness;
    public float energy;
    public float playposX;
    public float playposY;
    public float playposZ;
    public int score;

    //Data below for each member in troop and each troop member.
  
    public List<float> AIposX = new List<float>();
    public List<float> AIposY = new List<float>();
    public List<float> AIposZ = new List<float>();
    public List<float> AIAnxiety = new List<float>();
    public List<float> AINosiness = new List<float>();
    public List<float> AIExperience = new List<float>();
    public List<float> AIHunger = new List<float>();
    public List<bool> AIMother = new List<bool>();
    public List<bool> CanMante = new List<bool>();
 
}

Hi AndyNeoman

I’m intrigued by your assertion that you cannot test. Have you stepped through the save code inside the debugger? Did it work or not? Or are you actually getting compiler errors somewhere?

1 Like

HI there, I’m getting a null reference on the load() method line 76. I imagine i’m using the list incorrectly because it’s not something I’ve used much before. No compiler errors though.

EDIT Fixed with the code below. Not sure it’s the best way to do it so if anyone has advice/tops for best methods working with lists id be glad to listen.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

public class GameControl : MonoBehaviour
{
    public static GameControl control;
    public Transform playerPosition;
    public static float health;
    public static float food;
    public static float dryness = 100;
    public static float energy;
    public static float playposX;
    public static float playposY;
    public static float playposZ;
    public List<float> AIposX;
    public List<float> AIposY;
    public List<float> AIposZ;
    public List<float> AIAnxiety;
    public List<float> AINosiness;
    public List<float> AIExperience;
    public List<float> AIHunger;
    public List<bool> AIMother;
    public List<bool> CanMate;
    public string SaveFileName;
       
    // Use this for initialization
    void Awake ()
    {      
        if (control == null)
        {
            DontDestroyOnLoad(gameObject);
            control = this;
        }
    else if(control != this)
        {
            Destroy(gameObject);
        }
        SaveFileName = "/PlayerSave.dat";       
    }
    //void Update()
    //{      
    //    //Debug.Log("SavePos " + data.playerPosition.position);      
    //}   

    public void Save()
    {       
        Debug.Log("Save");
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.persistentDataPath+SaveFileName);

        SaveData data = new SaveData();
        data.health = health;
        data.food = food;
        data.energy = energy;
        data.dryness = dryness;
        data.score = GameOver.score;
        data.playposX = playerPosition.position.x;
        data.playposY = playerPosition.position.y;
        data.playposZ = playerPosition.position.z;      
        Debug.Log("dataX " + data.AIposX);
      

        foreach (GameObject go in TroopStats.troopArray)
        {
            Debug.Log("GOX " + go.transform.position.x);           
            data.AIposX.Add(go.transform.position.x);         
            data.AIposY.Add(go.transform.position.y);
            data.AIposZ.Add(go.transform.position.z);           
            Debug.Log("dataINX " + data.AIposX.ToString());
            Debug.Log("GOX SAVE " + go.transform.position);
        }

        //data.playerPosition.position = playerPosition.position;  // should be a class with all data to save so as we add to it this method satys the same.
        //Debug.Log("SavePos " + data.health);
        Debug.Log("Health " + data.health);
        bf.Serialize(file, data);
        file.Close();
        Debug.Log("Save complete");

    }

    // Update is called once per frame
    public void Load ()
    {
        Debug.Log("Load");
        if (File.Exists(Application.persistentDataPath + SaveFileName))
        {
            Debug.Log("file exists Load");
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + SaveFileName, FileMode.Open);
            SaveData data = (SaveData)bf.Deserialize(file);
            file.Close();
            health = data.health;
            energy = data.energy;
            dryness = data.dryness;
            food = data.food;
            GameOver.score = data.score;
            playerPosition.position = new Vector3 (data.playposX, data.playposY, data.playposZ);
           
           foreach(GameObject go in TroopStats.troopArray)
            {

                for (int i = 0; i < TroopStats.troopArray.Length; i++)
                {
                    float[] xpos = data.AIposX.ToArray();
                    float[] ypos = data.AIposY.ToArray();
                    float[] zpos = data.AIposZ.ToArray();
                    Debug.Log("xpos " + xpos[i]);
                    go.transform.position = new Vector3(xpos[i], ypos[i], zpos[i]);
                    Debug.Log("GOX load " + go.transform.position);
                }
                //go.transform.position = new Vector3(data.AIposX, data.AIposX, data.AIposX);
           
            }

        }
    }
    //Call this method before calling Save (only call save from here as last method so all data is upto date before saving/pushing to the cloud.
    //public void RefreshStats()
    //{

    //    Save();
    //}

  
}


[Serializable]
class SaveData
{
    // data to be saved. split into to types, player and AI.
    //player data will be position, all required vitals, Durability(health) food,dryness,energy.  Number of troopmembers and number of babies. Score.  Time alive.
    //AI data required foreach troopmember and babies. ICE vitals Anxiety,Experience, if motherhood for females, position on map,
   
    public float health;
    public float food;
    public float dryness;
    public float energy;
    public float playposX;
    public float playposY;
    public float playposZ;
    public int score;

    //Ddat below for each gorrila in troop and each troop member.
   
    public List<float> AIposX = new List<float>();
    public List<float> AIposY = new List<float>();
    public List<float> AIposZ = new List<float>();   
    public List<float> AIAnxiety = new List<float>();
    public List<float> AINosiness = new List<float>();
    public List<float> AIExperience = new List<float>();
    public List<float> AIHunger = new List<float>();
    public List<bool> AIMother = new List<bool>();
    public List<bool> CanMate = new List<bool>();
 
}

Well played for fixing it. :slight_smile:

It’s a little unclear to me how it is working though. In the Save() method, you have a single for loop that references TroopStats that I cannot see defined in the code:
foreach (GameObject go in TroopStats.troopArray)

However, in the Load() method, there is a double loop happening, why is this? :

foreach(GameObject go in TroopStats.troopArray)
{
    for (int i = 0; i < TroopStats.troopArray.Length; i++)

Also, assuming the double loop is necessary, you do not want to constantly be calling ToArray(). Try this instead:

float[] xpos = data.AIposX.ToArray();
float[] ypos = data.AIposY.ToArray();
float[] zpos = data.AIposZ.ToArray();

foreach(GameObject go in TroopStats.troopArray)
{
    for (int i = 0; i < TroopStats.troopArray.Length; i++)
    {
        go.transform.position = new Vector3(xpos[i], ypos[i], zpos[i]);
    }
}
1 Like

Thanks for tips!

The trooparray is a static array on another script.

The load was a problem because the array of game objects are being accessed but they themselves are not saved, just the transform position which being a vector 3 cannot be serialized so I had to loop though game objects and the transform.positions too. I am sure there is a more elegant method (i will add your suggestion too) but i’m just it works fr now and it only runs when saving/loading so should not impact performance in game.