Save levels gameobjects when closing game/leaving scene

Hello,

I didn’t know how to search it on the website so I’m just opening a new question.

I have a level with a lot of different objects etc that you can push around.

When you have pushed around some objects and you close the game/app and when you start it again next time I want all the objects that you pushed to be where you pushed them last time etc, how can I safe all the information of the objects when you close the scene or when you close the game completely?

I’m sorry if I disappoint you, but there are no simple solutions for this rather common feature. It will all involve handling Fields and Values of class instances to some degree. As a start, look into using BinaryFormatters to write serialized data to a file and load and deserialize that data. From there, try to think about how a gameObject can be saved in regards to position and rotation. You need a “Container” style class which holds the Transform values in a way that they can be serialized (Vector3 Type can’t be serialized, for example, to a conversion is needed). You also need a unique ID for each gameObject so the correct values can be assigned to the correct object after loading, as well as a way to find out how which prefab to instantiate for that object upon loading.

So, let’s start with the Container class:

using UnityEngine;
using System.Collections;

[System.Serializable]
public class SceneObject {

	public string name;
	public int id;
	public bool dontLoad;
	public float posX;
	public float posY;
	public float posZ;
	public float rotX;
	public float rotY;
	public float rotZ;
	public float rotW;
}

We also need a MonoBehaviour script on every gameobject that should be saved:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ObjectIdentity : MonoBehaviour
{
	public int id = -1;
	public bool dontLoad = false;

	public void SetID() {

		id = 0;

		List<int> takenIDs = new List<int>();

		ObjectIdentity[] obj = GameObject.FindObjectsOfType(typeof (ObjectIdentity)) as ObjectIdentity[];
		foreach (ObjectIdentity o in obj) {

			if(o.transform.gameObject != gameObject) {
				takenIDs.Add (o.id);
				//Debug.Log("taken ID added: " + o.id);
			}

		}

		if(takenIDs.Count > 0) {
			for(id = 0; id < takenIDs.Count; id++) {
				if(takenIDs.Contains(id) == false) {
					break;
				}
			}
		}


		//Debug.Log("New Object ID: " + id);

	}

Every time a gameobject with an Identifier script is created, the SetID needs to be called. It’s use ful to create an Editor Extension to call the function during Edit mode if the scene is edited in Edit mode as opposed to runtime.

Next, we need a class that can hold our whole scene, including a List of instances of the class above, thereby storing our gameobjects in serializable form:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class Game { //don't need ": Monobehaviour" because we are not attaching it to a game object

	public string savegameName;

	public List<SceneObject> sceneObjects = new List<SceneObject>();
		
}

Now, we need a function on one of our MonoBehaviour scripts that collects the gameobjects in the scene and adds them to a new instance of the Game class, and then calling anothe rfunction that does the actual saving.

public void SaveGame() {

		Game newGame = new Game();



		object[] obj = GameObject.FindObjectsOfType(typeof (GameObject));
		foreach (object o in obj) {
			GameObject g = (GameObject) o;
			
			ObjectIdentity idScript = g.GetComponent<ObjectIdentity>();
			if (idScript != null) {

				SceneObject newObject = new SceneObject();
				newObject.name = g.name;
				newObject.id = idScript.id;

				newObject.dontLoad = idScript.dontLoad;

				newObject.posX = g.transform.position.x;
				newObject.posY = g.transform.position.y;
				newObject.posZ = g.transform.position.z;

				newObject.rotX = g.transform.rotation.x;
				newObject.rotY = g.transform.rotation.y;
				newObject.rotZ = g.transform.rotation.z;
				newObject.rotW = g.transform.rotation.w;
                       }

		}


		newGame.savegameName = "blubb";
		SaveLoad.Save(newGame);
	}

Now for the writing and reading of files itself, here the BinaryFormatter comes into play:

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

public static class SaveLoad {

	public static Game loadedGame = new Game();
	public static List<Game> savedGames = new List<Game>();

	//it's static so we can call it from anywhere
	public static void Save(Game saveGame) {
		//SaveLoad.savedGames.Add(Game.current);
		
		BinaryFormatter bf = new BinaryFormatter();
		
                string path = "C:/Test Savegames/";
                //path = Application.persistentDataPath is a string, so if you wanted you can put that into debug.log if you want to know where save games are located
		FileStream file = File.Create (path + saveGame.savegameName + ".gd"); //you can call it anything you want, but the Directory must be present
		bf.Serialize(file, saveGame);
		file.Close();
		Debug.Log("Saved Game: " + saveGame.savegameName);

	}	
	
	public static void Load(string gameToLoad) {
                string path = "C:/Test Savegames/";
		if(File.Exists(path + gameToLoad + ".gd")) {
			BinaryFormatter bf = new BinaryFormatter();
			FileStream file = File.Open(path + gameToLoad + ".gd", FileMode.Open);
			loadedGame = (Game)bf.Deserialize(file);
			file.Close();
			Debug.Log("Loaded Game: " + loadedGame.savegameName);
		}
	}


}

To load, we need another function similar to SaveGame(). We also need a Dictionary so we can instantiate a gameobject based on it’s name. Ther are other ways to do this, of course, such as having the ObjectIdentifierscript keep the path and name of it’s prefab.

public Dictionary<string,GameObject> PrefabDict;

void Start() {
     PrefabDict = new Dictionary<string,GameObject>();

		GameObject[] objects = Resources.LoadAll<GameObject>("Prefabs");//or whereever prefabs are stored.
		foreach (var o in objects) {
			if(PrefabDict.ContainsKey(o.name) == false) {
				PrefabDict.Add (o.name, o);
			}

		}
}

public void LoadGame() {

		ClearScene();
		
		SaveLoad.Load("blubb");

		Game loadedGame = SaveLoad.loadedGame;


		int x = 0;
		int y = 0;
		int z = 0;

		foreach(SceneObject loadedObject in loadedGame.sceneObjects) {

			if(loadedObject.dontLoad == false) {
				GameObject goCur = Instantiate(PrefabDict[loadedObject.name], new Vector3(loadedObject.posX, loadedObject.posY,loadedObject.posZ), new Quaternion(loadedObject.rotX,loadedObject.rotY,loadedObject.rotZ,loadedObject.rotW)) as GameObject;
				
				goCur.name = loadedObject.name;

				if(goCur.GetComponent<ObjectIdentity>() == false) {
					ObjectIdentity oi = goCur.AddComponent<ObjectIdentity>();
				}

				ObjectIdentity idScript = goCur.GetComponent<ObjectIdentity>();
				
				idScript.id = loadedObject.id;

				//Debug.Log("GameObject loaded: " + goCur.name);
			}
		}

	}

	public void ClearScene() {

		object[] obj = GameObject.FindObjectsOfType(typeof (GameObject));
		foreach (object o in obj) {
			GameObject g = (GameObject) o;
			
			var destroyScript = g.GetComponent<DontDestroy>();
			if (destroyScript == null || destroyScript != null && destroyScript.dontDestroy == false)
			{
				Destroy (g);
			}
		}
	}

Any questions? Didn’t think so :wink:
This would be the , in my opinion, most basic way of saving and loading gameobject (only their position and rotation, of course). Keeping track of Components is much, much more difficult.

Hii…

Have a look at this tutorial.

Tutorial Link . This may help you. Thanks.

Hello! I found a simpler way to keep the level the player has reached. I have created 7 levels. If the player wants to start from the first level, I have indicated with a message that this is done through the menu by pressing the Quit the game button.

public class MainMenu: MonoBehavior
{
   public void QuitGame ()
   {
       PlayerPrefs.DeleteKey ("Levels");
       Application.Quit ();
   }
}

This is the script attached to the game menu!

I will now show the script for saving the level of the game.

public void Start()
{
    int counterLevels = PlayerPrefs.GetInt("Levels");

    if (counterLevels > 0 && SceneManager.GetSceneByName("Level 1").isLoaded)
    {
        SceneManager.LoadScene("Level  " + (counterLevels + 1));
    }
}

public void Update()
{
    foreach (Enemy enemy in _enemies)
    {
        if (enemy != null)
        {
           string _nameCurrentScene = SceneManager.GetActiveScene().name;
           SceneManager.LoadScene(_nameCurrentScene);
         return;
        }
    }
    int counterLevels = PlayerPrefs.GetInt("Levels");
    PlayerPrefs.SetInt("Levels", counterLevels + 1);

    SceneManager.LoadScene(counterLevels + 1);

}