Issue saving between scenes with portals

I see, then we should be able to implement a fix by disabling the collider of the portal just after you enter it (alternatively, you could disable the collider on the Player).

//in portal script....
        private void OnTriggerEnter(Collider other) {
            if (other.tag == "Player") {
                GetComponent<Collider>().enabled = false; //add this and test again .
                StartCoroutine(Transition());
            }
        }

I changed my Portal trigger to disable the portal trigger, I still saw the same result so I disabled the player collider as well and still get the issue.

        private void OnTriggerEnter(Collider other) {
            if (other.tag == "Player") {
                GetComponent<Collider>().enabled = false;
                other.GetComponent<Collider>().enabled = false;
                print("Player has entered portal " + gameObject.name); 
                StartCoroutine(Transition());
            }
        }

I did notice that as it was processing the portal the scene name at the top of the hierarchy tab showed Sandbox2, but when it finished it is actually showing the original scene name, although in the scene view it is showing the player in Sandbox2.

This image shows the player before he enters the first portal and the sandbox scene in the hierarchy.

This image shows after going through the portal in the scene view into sandbox2, but it still shows the original scene information in the hierarchy panel (and all the extra portals still in the do not delete section)

Gotcha … the next place to check is inside your saving system script. It sounds like you have a save/load loop happening in there. When you get a chance, post your SavingSystem.cs file.

Hi,
Please find below my SavingSystem script.

/// <summary>
        /// Will load the last scene that was saved and restore the state. This
        /// must be run as a coroutine.
        /// </summary>
        /// <param name="saveFile">The save file to consult for loading.</param>
        public IEnumerator LoadLastScene(string saveFile)
        {
            Dictionary<string, object> state = LoadFile(saveFile);
            int buildIndex = SceneManager.GetActiveScene().buildIndex;
            if (state.ContainsKey("lastSceneBuildIndex"))
            {
                buildIndex = (int)state["lastSceneBuildIndex"];
            }
            yield return SceneManager.LoadSceneAsync(buildIndex);
            RestoreState(state);
        }

        /// <summary>
        /// Save the current scene to the provided save file.
        /// </summary>
        public void Save(string saveFile)
        {
            Dictionary<string, object> state = LoadFile(saveFile);
            CaptureState(state);
            SaveFile(saveFile, state);
        }

        /// <summary>
        /// Delete the state in the given save file.
        /// </summary>
        public void Delete(string saveFile)
        {
            File.Delete(GetPathFromSaveFile(saveFile));
        }

        // PRIVATE

        private void Load(string saveFile)
        {
            RestoreState(LoadFile(saveFile));
        }

        private Dictionary<string, object> LoadFile(string saveFile)
        {
            string path = GetPathFromSaveFile(saveFile);
            if (!File.Exists(path))
            {
                return new Dictionary<string, object>();
            }
            using (FileStream stream = File.Open(path, FileMode.Open))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                return (Dictionary<string, object>)formatter.Deserialize(stream);
            }
        }

        private void SaveFile(string saveFile, object state)
        {
            string path = GetPathFromSaveFile(saveFile);
            print("Saving to " + path);
            using (FileStream stream = File.Open(path, FileMode.Create))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, state);
            }
        }

        private void CaptureState(Dictionary<string, object> state)
        {
            foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
            {
                state[saveable.GetUniqueIdentifier()] = saveable.CaptureState();
            }

            state["lastSceneBuildIndex"] = SceneManager.GetActiveScene().buildIndex;
        }

        private void RestoreState(Dictionary<string, object> state)
        {
            foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>())
            {
                string id = saveable.GetUniqueIdentifier();
                if (state.ContainsKey(id))
                {
                    saveable.RestoreState(state[id]);
                }
            }
        }

        private string GetPathFromSaveFile(string saveFile)
        {
            return Path.Combine(Application.persistentDataPath, saveFile + ".sav");
        }```

I noticed something when comparing to my version of portal. The second time I grab hold of the PlayerController and disable it happens AFTER calling Load(). Try moving the second set of these two lines:

PlayerController newPlayerController = GameObject.FindWithTag("Player").GetComponent<PlayerController>();
newPlayerController.enabled = false;

after the line:

savingWrapper.Load();

for me, it looks like this:

  1. Enter portal
  2. Disable player control on player in current scene
  3. Fade out / Save Data in the current scene
  4. Load the new scene
  5. Load the save data for that new scene
  6. Disable player control on player in new scene

Although, now that I think about it, I don’t think it will make a difference since you are waiting for the new scene to load before grabbing the player, and the player in the old scene should already be destroyed.

The next thing to look at is savingWrapper.Load(). Which method in the saving system is it calling?

Hi,
Here is my SavingWrapper class.

public class SavingWrapper : MonoBehaviour {
        const string defaultSaveFile = "save";

        [SerializeField] float fadeInTime = 0.2f;

        private void Awake() {
            StartCoroutine(LoadLastScene());
        }

        private IEnumerator LoadLastScene() {
            yield return GetComponent<SavingSystem>().LoadLastScene(defaultSaveFile);
            Fader fader = FindObjectOfType<Fader>();
            fader.FadeOutImmediate();
            yield return fader.FadeIn(fadeInTime);
        }

        private void Update() {
            if (Input.GetKeyDown(KeyCode.S)) {
                Save();
            }

            if (Input.GetKeyDown(KeyCode.L)) {
                Load();
            }

            if (Input.GetKeyDown(KeyCode.Delete)) {
                Delete();
            }
        }

        public void Load() {
            StartCoroutine(GetComponent<SavingSystem>().LoadLastScene(defaultSaveFile));
        }

        public void Save() {
            GetComponent<SavingSystem>().Save(defaultSaveFile);
        }

        public void Delete() {
            GetComponent<SavingSystem>().Delete(defaultSaveFile);
        }
    }

Ok, we’re close to solving your problem. savingWrapper.Load() is essentially calling SavingSystem.LoadLastScene() and inside that method, your are doing SceneManager.LoadSceneAsync([your old scene build index here]). One sec, and I think I can help you fix it… just need to review my setup real quick.

Ok, make these changes if you would please. In SavingSystem.cs change your Load() to this:

        public IEnumerator Load(string saveFile)
        {
            int buildIndex = SceneManager.GetActiveScene().buildIndex;
            yield return SceneManager.LoadSceneAsync(buildIndex);
            RestoreGameState(LoadFile(saveFile));
        }

in SavingWrapper.cs change Load()

        public IEnumerator Load()
        {
            yield return GetComponent<SavingSystem>().Load(defaultSaveFile);
        }

and lastly, in Portal.cs, change:
savingWrapper.Load(); to

yield return savingWrapper.Load();

…that should do it. Let me know the result!

1 Like

That’s awesome, that fixed it :grinning:

Thanks so much for spending so much time to help out.

Glad I could be of help! :slight_smile:

Bookmarking this, as I wrote my saving system differently, and never encountered this bug, but see people asking often as the enounter it. Good job working through this, Seth!

1 Like

I went ahead and forked the Course Repo from Github, and the bug exists in the final version Sam put out as well.

@sampattuzzi note

What exactly are the repro steps in the Github version?

I’ll email you the diff when I get home.
(I don’t have access to my Unity projects at work)

Using Origin/Master dated 29 January 2020
Walk from sandbox to sandbox2
The scene will try to load bouncing between sandbox and sandbox2 several times before settling down
Walk from sandbox2 to sandbox
It all gets crazy from there… it appears to try to load sandbox several times, like it’s caught in a loop. Sometimes, it settles down and leaves the game stranded in the middle of nowhere unplayable… other times, it continues forever.



These changes fixed it…
Clearly I should have grabbed that Git course while it was on sale… it doesn’t seem to want to let me revert to the commit I made when I patched this.

Hope that helps,
-Brian

2 Likes

Oh no! What a silly bug. Easiest thing is to revert the change that caused the issue in the first place:

I will be sure to patch this.

2 Likes

Oh wow, this works perfectly. Without changing the SavingSystem.Load() to an IEnumerator and adding those two lines, animation states with no exit get stuck, such as enemy death. When I’ve been fixing this in the past, I ended up making multiple methods to handle the different load types, but this consolidates it nicely into the original methods.

Thanks for the solution!

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms