Creating an Automatic Save Structure

User avatar
Joel
Moodkie Staff
Posts: 3041
Joined: Wed Nov 07, 2012 10:32 pm

Creating an Automatic Save Structure

Post by Joel »

Creating an Automatic Save Structure

In this example we will create a series of scripts which will attempt to automatically save and load the Transform of objects in the scene. It can also be added to so it saves other supported types.

See below for the attached Unity Package containing the files. Requires Easy Save 2.

Notes
  • This functionality may be built in to a future release of Easy Save.
  • As this is a complete solution which includes saving, loading, creating, destroying objects, and Game Logic, this is considered a complex example. However, all of the Easy Save specific code can be found in the UniqueSaveManager class and is quite simple.
  • This example has been written with simplicity in mind. There may be more efficient ways to code it.
  • You could also modify it so that it's more automated, but we've not implemented this as it makes the example clearer.
  • In the example we refer to two types of objects: Scene Objects and Created Objects
  • - Scene Objects are those that are added to the scene in the editor.
  • - Created Objects are objects which are instantiated at runtime.
How to Use
  1. Add a UniqueObjectManager and UniqueSaveManager script to the scene.
  2. Add a UniqueID script to each of the prefabs you want to create at runtime and set the UniqueID's 'prefabName' field to the name of this prefab.
  3. Add the prefabs to the UniqueObjectManager's 'prefab' field.
  4. Add a UniqueID script to all of the scene objects you want to save, or might have child objects which need saving.
  5. Add these scene objects to the scene objects array in the UniqueObjectManager
How it Works
  1. When the application starts, each of the UniqueID objects assign themselves a UniqueID number to allow us to uniquely identify each object.
  2. During runtime, we use UniqueObjectManager.InstantiatePrefab(string prefabName) to create objects, and UniqueObjectManager.DestroyObject(GameObject go) to destroy them.
  3. When the application is quit, it saves the Scene Objects and Created Objects to separate files, noting how many of each are being saved. It also saves the UniqueID number of each object's parent, if it has a parent. For created objects it must also save it's prefab name.
  4. When the application is restarted, and it finds that there is data to load, it loads Scene Objects first. It's important that we load them first as created objects might use one of them as a parent.
  5. It then finds out how many Created Objects there are, and then attempts to Instantiate them using their prefab name.
  6. Once instantiated, it can then load data into that object.
  7. If the created object has a parent, it will find it with the given UniqueID and then set that to it's parent.

wyliam
Posts: 1
Joined: Tue Feb 04, 2014 3:14 am

Re: Creating an Automatic Save Structure

Post by wyliam »

FYI - The package had an error in it, running it as is (Unity 4.3.1f1).
Changing Awake() to Start() on the UniqueSaveManager.cs gave the SceneObjectThree scene object time to register itself. As a sub-object, it's Awake() didn't run before the manager tried to run Load(), and error'd out there.

User avatar
Joel
Moodkie Staff
Posts: 3041
Joined: Wed Nov 07, 2012 10:32 pm

Re: Creating an Automatic Save Structure

Post by Joel »

Thanks for the heads-up, it's much appreciated. We'll get this fixed and updated today.

All the best,
Joel

dl_stuidos
Posts: 1
Joined: Thu Apr 03, 2014 8:39 pm

Re: Creating an Automatic Save Structure

Post by dl_stuidos »

Just a heads up, the Awake(), Start() error is still there as of most recent version.

User avatar
Joel
Moodkie Staff
Posts: 3041
Joined: Wed Nov 07, 2012 10:32 pm

Re: Creating an Automatic Save Structure

Post by Joel »

Hi there,

Edit: Turns out that the fixes were pushed through to our Development project, but not the Release project. It will be fixed in v2.38 (the next version, available next week).

All the best,
Joel

Dozer
Posts: 1
Joined: Tue May 06, 2014 1:15 am

Re: Creating an Automatic Save Structure

Post by Dozer »

Has this been fixed? I just downloaded the package that was linked in the first thread, and in UniqueSaveManager, I see that the comment says "We use Awake()...", but it's actually using the Start() function.

User avatar
Joel
Moodkie Staff
Posts: 3041
Joined: Wed Nov 07, 2012 10:32 pm

Re: Creating an Automatic Save Structure

Post by Joel »

Hi there,

The example is meant to use the Start() method, so it should work fine. We must have just forgot to change the comment ... apologies!

All the best,
Joel

Shockwolf
Posts: 14
Joined: Thu Feb 09, 2017 7:09 am
Location: Western Australia

Re: Saving, Loading and Instantiating Prefabs

Post by Shockwolf »

Hi Joel,

Your whole example for "Creating an Automatic Save Structure" works great until it comes to manually saving and loading instead of On Start loading and OnApplicationQuit method. I shall explain the problem I am having and hopefully you can help me out, please...

I don't want automatic saves and loads, so I have changed the methods to something that is called manually. So OnStart and OnApplicationQuit are no more. I have replaced them with LoadUniqueObjects() and SaveUniqueObjects(). LoadUniqueObjects() is called by my ProfileMenuManagement script when a player chooses to load a saved game and SaveUniqueObjects() is called by my ProfileMenuManagement script when the player pauses/presses the escape key.

This works fine once. However after loading up the saved game and attempting to save it again, it throws an error and will not save anymore prefabs.

The error in question is:
MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
UnityEngine.GameObject.GetComponent[ES2UniqueID] () (at C:/buildslave/unity/build/artifacts/generated/common/runtime/GameObjectBindings.gen.cs:35)
UniqueSaveManager.SaveObject (UnityEngine.GameObject obj, Int32 i, System.String file) (at Assets/Easy Save 2/Examples/Creating an Automatic Save Structure/UniqueSaveManager.cs:99)
UniqueSaveManager.Save () (at Assets/Easy Save 2/Examples/Creating an Automatic Save Structure/UniqueSaveManager.cs:89)
UniqueSaveManager.SaveUniqueObjects () (at Assets/Easy Save 2/Examples/Creating an Automatic Save Structure/UniqueSaveManager.cs:28)
ProfileMenuManagement.SaveProfile1 () (at Assets/_FallenAngel/Scripts/MAIN/ProfileMenuManagement.cs:338)
ProfileMenuManagement.Update () (at Assets/_FallenAngel/Scripts/MAIN/ProfileMenuManagement.cs:116)
The line of code it is complaining about is this:

Code: Select all

// Let's get the UniqueID object, as we'll need this.
		ES2UniqueID uID = obj.GetComponent<ES2UniqueID>();
And for reference, here is the UniqueSaveManager Script with all of the changes I have made:

Code: Select all

using UnityEngine;
using System.Collections;

/*
 * This class will handle the saving and loading of data.
 */
public class UniqueSaveManager : MonoBehaviour 
{
    // For instancing this Script so that others can communicate with it.
    public static UniqueSaveManager Instance;
    
    //Player Profile Reference.
    public string activeProfile;
    
    
    public string sceneObjectFile = "sceneObjectsFile.txt";
	public string createdObjectFile = "createdObjectsFile.txt";

   
    void Awake()
    { 
        Instance = this;
    }

	//This is where we save certain aspects of our instantiated prefabs.
	public void SaveUniqueObjects()
	{		
		Save();
	}
	
	/* We also save on application pause in iOS, as OnAppicationQuit isn't always called */
#if UNITY_IPHONE && !UNITY_EDITOR
	public void OnApplicationPause()
	{
		Save();
	}
#endif
	
	/*
	 * This is where we'll load our data.
	 * 
	 */
	public void LoadUniqueObjects()
	{
        /*Myk: This gets the Active Player Profile number from the Profile Menu Manager and converts 
        it to a String to be used when Saving and Loading Player Based Data*/
        activeProfile = GameObject.Find("_Managers").GetComponent<ProfileMenuManagement>().activeProfile.ToString();

		// If there's scene object data to load
        if (ES2.Exists("Profile" + activeProfile + "/" + sceneObjectFile))
		{
			// Get how many scene objects we need to load, and then try to load each.
			// We load scene objects first as created objects may be children of them.
            int sceneObjectCount = ES2.Load<int>("Profile" + activeProfile + "/" + sceneObjectFile + "?tag=sceneObjectCount");
			
			for(int i=0; i<sceneObjectCount; i++)
                LoadObject(i, "Profile" + activeProfile + "/" + sceneObjectFile);
		}
		
		// If there's created objects to load
        if (ES2.Exists("Profile" + activeProfile + "/" + createdObjectFile))
		{
			// Get how many created objects we need to load, and then try to load each.
            int createdObjectCount = ES2.Load<int>("Profile" + activeProfile + "/" + createdObjectFile + "?tag=createdObjectCount");
			
			for(int i=0; i<createdObjectCount; i++)
                LoadObject(i, "Profile" + activeProfile + "/" + createdObjectFile);
		}
	}
	
	private void Save()
	{
        /*Myk: This gets the Active Player Profile number from the Profile Menu Manager and converts 
        it to a String to be used when Saving and Loading Player Based Data*/
        activeProfile = GameObject.Find("_Managers").GetComponent<ProfileMenuManagement>().activeProfile.ToString();
		
        // Save how many scene objects we're saving so we know how many to load.
        ES2.Save(UniqueObjectManager.SceneObjects.Length, "Profile" + activeProfile + "/" + sceneObjectFile + "?tag=sceneObjectCount");
		
		// Iterate over each scene object
		for(int i=0; i<UniqueObjectManager.SceneObjects.Length; i++)
            SaveObject(UniqueObjectManager.SceneObjects[i], i, "Profile" + activeProfile + "/" + sceneObjectFile);
		
		// Save how many created objects we're saving so we know how many to load.
        ES2.Save(UniqueObjectManager.CreatedObjects.Count, "Profile" + activeProfile + "/" + createdObjectFile + "?tag=createdObjectCount");
		
		// Iterate over each created object
		for(int i=0; i<UniqueObjectManager.CreatedObjects.Count; i++)
            SaveObject(UniqueObjectManager.CreatedObjects[i], i, "Profile" + activeProfile + "/" + createdObjectFile);
	}
	
	/*
	 * Saves an Object
	 * 'i' is the number of the object we are saving.
	 */
	private void SaveObject(GameObject obj, int i, string file)
	{
		// Let's get the UniqueID object, as we'll need this.
		ES2UniqueID uID = obj.GetComponent<ES2UniqueID>();
		

		 //Note that we're appending the 'i' to the end of the path so that
		 //we know which object each piece of data belongs to.
		ES2.Save(uID.id, file+"?tag=uniqueID"+i);
		ES2.Save(uID.prefabName, file+"?tag=prefabName"+i);
		// Save whether the GameObject this UniqueID is attached to is active or not.
		#if UNITY_3_5
		ES2.Save(uID.gameObject.active, file+"?tag=active"+i);
		#else
		ES2.Save(uID.gameObject.activeSelf, file+"?tag=active"+i);
		#endif
		
		
		// You could add many more components here, inlcuding custom components.
		// For simplicity, we're only going to save the Transform component.
		Transform t = obj.GetComponent<Transform>();
		if(t != null)
		{
			ES2.Save(t, file+"?tag=transform"+i);
			// We'll also save the UniqueID of the parent object here, or -1
			// string if it doesn't have a parent.
			ES2UniqueID parentuID = ES2UniqueID.FindUniqueID(t.parent);
			if(parentuID == null)
				ES2.Save(-1, file+"?tag=parentID"+i);
			else
				ES2.Save(parentuID.id, file+"?tag=parentID"+i);
		}
	}
	
	/*
	 * Loads an Object
	 * 'i' is the number of the object we are loading.
	 */
	private void LoadObject(int i, string file)
	{
		int uniqueID = ES2.Load<int>(file+"?tag=uniqueID"+i);
		string prefabName = ES2.Load<string>(file+"?tag=prefabName"+i);
			
		// Create or get an object based on our loaded id and prefabName
		GameObject loadObject;
		// If our prefab name is blank, we're loading a scene object.
		if(prefabName == "")
			loadObject = ES2UniqueID.FindTransform(uniqueID).gameObject;
		else 
			loadObject = UniqueObjectManager.InstantiatePrefab(prefabName);
		
		// Load whether this GameObject is active or not.
		#if UNITY_3_5
		loadObject.active = ES2.Load<bool>(file+"?tag=active"+i);
		#else
		loadObject.SetActive(ES2.Load<bool>(file+"?tag=active"+i));
		#endif
		
		Transform t = loadObject.GetComponent<Transform>();
		if(t != null)
		{
			// Auto-assigning Load is the best way to load Components.
			ES2.Load<Transform>(file+"?tag=transform"+i, t);
			// Now we'll get the parent object, if any.
			int parentuID = ES2.Load<int>(file+"?tag=parentID"+i);
			Transform parent = ES2UniqueID.FindTransform(parentuID);
			if(parent != null)
				t.parent = parent;
		}
	}
}
Thanks,

Kind Regards,

Mike
Regards, SW.

User avatar
Joel
Moodkie Staff
Posts: 3041
Joined: Wed Nov 07, 2012 10:32 pm

Re: Creating an Automatic Save Structure

Post by Joel »

Hi Mike,

This error will occur because one of the GameObjects in the UniqueObjectManager.SceneObjects or UniqueObjectManager.CreatedObjects list has been destroyed.

Before saving you will need to remove any destroyed GameObjects from these Lists.

All the best,
Joel
Joel @ Moodkie Interactive
Twitter - Unity

Shockwolf
Posts: 14
Joined: Thu Feb 09, 2017 7:09 am
Location: Western Australia

Re: Creating an Automatic Save Structure

Post by Shockwolf »

Joel wrote:Hi Mike,

This error will occur because one of the GameObjects in the UniqueObjectManager.SceneObjects or UniqueObjectManager.CreatedObjects list has been destroyed.

Before saving you will need to remove any destroyed GameObjects from these Lists.

All the best,
Joel
That was my first thought, however strangely nothing changes. Nothing is being deleted, but this error still occurs. Sometimes when testing I add objects or I simply leave it the same and it still returns the error. And if this is really because a GameObject hasn't been removed from the list, how come it works when your script saves via application exit and loads on start? I haven't changed anything apart from when it loads and saves.
Regards, SW.

Post Reply