Building an adventure game save system in Unity.

Today I’d like to talk about the save system I designed for Bik.

I’ll try to be as simple as possible so you can use this information in any way you need.  Originally I began to implement my own method and would save the information to XML files but it was taking a while and was not very robust, and I wanted the game to come out before the end of my life, so I turned to the Asset store once again.  I use a plugin called Easy Save 2 for actually writing and reading the variables to and from a file.

So writing the variables to a file is no longer the hard part.  The hard part is finding a good and efficient way of actually collecting and handling the saving. Enter C# delegates and events.

Here is an amazing video tutorial on using Events and Delegates in Unity.

Basically, I have a master save script that handles the saving and loading.  If a script wants to save a variable it must subscribe to the save or load event on the master save class and then when it is time to save or load, the master script will tell its subscribers to save or load.    I built in a 3 level ordering system in case certain objects should load before others.
My actual save script is a lot more complex but hopefully these distilled points will point you in the right direction!

 

        //First setup the delegates and events
 	public delegate void SaveType();
	public static event SaveType SaveEarly;
	public static event SaveType SaveLate;
	public static event SaveType SaveRealLate;

	public delegate void LoadType();
	public static event LoadType LoadEarly;
	public static event LoadType LoadLate;
	public static event LoadType LoadRealLate;

//If we want to save or load we can call these methods and it will fire off the events in the order they are placed below.  Check for null otherwise we get an error if there are no subscribers.

//this is my actual save method I currently use.  
public void Save()
{
       //check if the file exists and delete it because its going to get created again
		if(ES2.Exists(saveFile)) ES2.Delete(saveFile);

		//Global Variables Holder.  This save any global variables that need to persist between scenes
		GlobalVariablesHolder.Instance.SaveGlobalVars();

		//Save current Scene State
		AO.controlcenter.GetComponent().SaveSceneState();

		//Registered Objects
		if (SaveEarly != null) SaveEarly();
		if (SaveLate != null) SaveLate();
		if (SaveRealLate != null) SaveRealLate();

		//Current Level
		ES2.Save(Application.loadedLevelName, saveFile+"?tag=CurrentLevel");

		//Inventory
		ES2.Save(AO.inventory.GetComponent().inventory, saveFile+"?tag=inventory");

		//Timer
		ES2.Save(timer, saveFile+"?tag=timer");

		//MouseTarget
		ES2.Save(AO.mouseTarget.transform.position, saveFile+"?tag=mouseTarget");

		Debug.Log("saved");	

}

//loading is a lot more complicated so I'll just show you a few examples
IEnumerator Load()
{

        //current scene state - this happens AFTER we load correct scene.  We now will load the state that the scene was in when saved
	SceneStatesController statesCon = AO.controlcenter.GetComponent();
	statesCon.RestoreSceneState();

	//we now yield the script while the state is loaded.
	while(SceneStatesController.stateLoading) yield return null; //wait for state to load to continue

	//now load any specific variables to override current scene state

        //Global Variables holder
	GlobalVariablesHolder.Instance.RestoreGlobalVars();

        //load the mouse target
        AO.mouseTarget.transform.position = ES2.Load(saveFile+"?tag=mouseTarget");	

        //fire off load events for specific items and objects
        if (LoadEarly != null) LoadEarly();
	if (LoadLate != null) LoadLate();
	if (LoadRealLate != null) LoadRealLate();
}

and here is what the part of a script would look like that is subscribed to the saving or loading events.

//This is used to subscribe to the event.  We put it in the OnEnable and OnDisable so it does not subscribe unless the object is active and if it is ever destroyed or deactivated it will unsubscribe itself.
 void OnEnable(){
		if (!lateRestore){
                         //the "Save" after += is the LOCAL method that will be called whenever the SaveEarly event is fired on the SaveSystemV1 class
			SaveSystemV1.SaveEarly += Save;
			SaveSystemV1.LoadEarly += Restore;
		}
		else {
			SaveSystemV1.SaveLate += Save;
			SaveSystemV1.LoadLate += Restore;
		}
	}

	void OnDisable(){
		if(!lateRestore){
			SaveSystemV1.SaveEarly -= Save;	
			SaveSystemV1.LoadEarly -= Restore;
		}
		else {
			SaveSystemV1.SaveLate -= Save;
			SaveSystemV1.LoadLate -= Restore;
		}
	}

        void Save()
        {
//say I want to save if a collider is on or off I can use this
ES2.Save(collider.enabled, SaveSystemV1.Instance.saveFile+"?tag="+gameObject.name+"collider");
        }

        void Load()
        {
//and to load if the collider was on or off I use this
collider.enabled = ES2.Load(SaveSystemV1.Instance.saveFile+"?tag="+gameObject.name+"collider");
        }

And that is the basic idea for the save system. It incorporates nicely with my state system for level states and I can expand it with ease to work with any object in the game I need to simply by dragging a generic save script I created! Any questions or if you want more specifics please feel free to leave a comment!

This entry was posted in DevBlog. Bookmark the permalink.

5 Responses to Building an adventure game save system in Unity.

Leave a Reply

Your email address will not be published. Required fields are marked *