Saving the world between scenes

Hello forums,

I am currently building a 2D mobile game where you travel upwards through a world and land on objects that take you to a new scene where you engage in combat.

I’m having a problem dealing with persistence between scenes. I have found the Persistence tutorial very helpful for better organising my player stats, but I also want to save the world as you fight monsters. What would be ideal is that, when you finish combat, you return to the world with the player, camera and background in the same positions as when you began combat.

Any and all help would be very much appreciated!

Ferrets

I would add DoNotDestroyOnLoad script to all game objects which require the persistence between scenes or to avoid attaching the DoNotDestroyOnLoad script to every single object you need to persist you might just parent all these objects under one game object having attached the DoNotDestroyOnLoad script. Then every game object in the parent object would be persisted between scenes.

Have you considered using Files to save the transform location of each of these objects to, and then pulling the data from the files each time you return to the scene? Saving to file is a fairly important skill, and I would highly recommend you watch Unity’s video on Persistent Data. It may be fairly long (just under an hour), but the skills carry over to pretty much everything you do. It’ll help out if you don’t want these items to persist during the fighting scene, but then can bring back the data values for as many items as you wish and re-assign them.

Link to Tutorial:
https://www.youtube.com/watch?v=J6FfcJpbPXE

EDIT: Ah, I notice this is the exact video you linked in your comment… I don’t follow your question then. The solution is to grab the transforms of each of the items you want to persist, store those variables in their own class, then save said class to a file (as explained how to do in the video). Once you leave the battle scene and load back into the world, pull that data from the file and set the transform values (of the player, camera and background) to the stored values.

One trick is to put your combat area in the same scene as your world, but organized under different empty GameObjects and positioned away from each other, such as:

  • World

  • World player avatar

  • World camera

  • World environment, etc.

  • Combat Area

  • Combat player avatar

  • Combat camera

  • Combat environment, etc.

When the player engages in combat, deactivate World and activate Combat Area. When the combat is done, deactivate Combat Area and activate World. If your world player avatar has components that track the player’s stats, you can copy those stats to the combat player avatar before combat and then copy them back after combat. This way you don’t have to worry about changing scenes.

If you don’t want to do that, you could generalize the persistence tutorial using something like this:

Create a static C# class that’s not a MonoBehaviour. For example:

SaveManager.cs

public static class SaveManager {
    public delegate void SaveRestoreDelegate();
    public static event SaveRestoreDelegate Save = delegate{};
    public static event SaveRestoreDelegate Restore = delegate {};
}

You could pretty this up syntactically, and also actually test it (as I’m just typing it into this reply untested), but the idea is up there.

Then create a base save/restore component:

AbstractSave.cs

using UnityEngine;

public abstract class AbstractSave : MonoBehaviour {
    public string playerPrefsKey; // assign unique value

    protected abstract void OnSave();
    protected abstract void OnRestore();

    protected virtual void OnEnable() {
        if (string.IsNullOrEmpty(playerPrefsKey)) playerPrefsKey = name;
        SaveManager.Save += OnSave;
        SaveManager.Restore += OnRestore;
    }
    protected virtual void OnDisable() {
        SaveManager.Save -= OnSave;
        SaveManager.Restore -= OnRestore;
    }
}

And then write subclasses for different types of data:

SavePosition.cs

using UnityEngine;

public class SavePosition : AbstractSave {

    protected override void OnSave() {
        PlayerPrefs.SetFloat(playerPrefsKey + ".x", transform.position.x);
        PlayerPrefs.SetFloat(playerPrefsKey + ".y", transform.position.y);
    }
    protected override void OnRestore() {
        transform.position = new Vector3(
            PlayerPrefs.GetFloat(playerPrefsKey + ".x", transform.position.x),
            PlayerPrefs.GetFloat(playerPrefsKey + ".y", transform.position.y),
            transform.position.z);
    }
}

If you add SavePosition to a GameObject, it will save and restore its (x,y) position.

Finally, before changing scenes, call SaveManager.Save(). After changing scenes, call SaveManager.Restore(). For example, to switch to the Combat scene:

SaveManager.Save();
SceneManager.LoadScene("Combat");
SaveManager.Restore();

When combat is over, switch back to the World scene:

SaveManager.Save();
SceneManager.LoadScene("World");
SaveManager.Restore();
2 Likes

As mentioned above, DontDestroyOnLoad is probably the easiest you could do here.

Now, if you need really complex things, like picking up an item, dropping it, then loading the scene again and have the item at the same location, then it will be more tricky.

I would a TransformSaver script or similar, and put an array of Transforms. Then make a Save and Load function that saves and loads the information of all the transforms into a .json or .xml file.

public class TransformSaver : MonoBehaviour {
    string saveDirectory = "MySaveFolder/";
    List<Transform> transformToSave = new List<Transform>();

    void Awake() {
        DontDestroyOnLoad();
    }

    public void Save() {
        // Save list into file here
    }

    public void Load() {
        // Load list from file here
    }
}

I know this is a pretty basic answer, but I hope it helps at least a little :slight_smile:

PS: I typed all the code on my phone, sorry if there are any typos.

(Personal opinion/experience incoming…)

As DontDestroyOnLoad has been mentioned several times, I’d really like to point of that a sloppy usage ( like “just attach it to everything that I need in another scene”) can cause quite some headaches and heavy bugs. You literally ask the game to mess up if you use this for every little thing.

I used to work on a university project when some people decided to have an initialization scene. And this has been introduced without even telling everyone (what a fun if everyone works on his own module with a different understanding of the whole set up). In the end, you were asked to write reset methods that rebuild the original, initialization state for particular parts of some scene, even for particular scripts under certain conditions. This totally messed up the code structure and introduced so many dirty hacks.

This being said, I’d really recommend to only use it when you know what you’re doing or avoid it at all. Pure game state data can be transferred in many different ways, almost all of them being better than the DontDestroyOnLoad approach (my opinion - you may have another).

3 Likes