PlayerController destroyed

When i try to enter a new scene the ui flickers and i end up somewhere else on the map. The inventory is cleared of any items. i get the error: the object of type playercontroller has been destroyed but you are still trying to access it. when i check my player in the next scene he has a playercontroller on him. The error points to the portal script where we enable the playercontroller again. Both scenes has player as the unique idetifier.

MissingReferenceException: The object of type ‘PlayerController’ 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.
RPG.SceneManagement.Portal+d__8.MoveNext () (at Assets/Scripts/SceneManagement/Portal.cs:98)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at C:/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)

The first time we reference the PlayerController, we’re getting a reference to the player in the scene we’re leaving and disabling it.
After the next scene is loaded, that PlayerController no longer exists. That means that we need to locate the new PlayerController in the newly loaded scene before we access it again.
Make sure that after the scene is loaded, you do a new search for the PlayerController before accessing it.

I think my code is the same as sams. Heres the portal code, sry for all the comments :slight_smile:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.AI;
using RPG.Control;

namespace RPG.SceneManagement
{
    public class Portal : MonoBehaviour
    {
        // set it to -1 so we get an error if we have not set it up in unity.
        [SerializeField] int sceneToLoad = -1;
        [SerializeField] Transform spawnPoint;
        [SerializeField] float fadeOutTime = 0.5f;
        [SerializeField] float fadeInTime = 0.5f;
        [SerializeField] float fadeWaitTime = 1f;
        // set letters on our portals so we spawn at the correct one.
        enum DestinationIdentifier
        {
            A,B,C,D,E
        }
        [SerializeField] DestinationIdentifier destination;

        private void OnTriggerEnter(Collider other)
        {
            if(other.gameObject.tag == "Player")
            {
                // this co-routine will run between scenes. makes it possible to transfer
                // information into the new scene.
                StartCoroutine(Transition());
                print("Portal triggered");
            }
            
        }

        private IEnumerator Transition()
        {   
            // If we forgott to set the scene build index.
            if (sceneToLoad < 0)
            {
                Debug.LogError("Scene to load not set.");
                // exits function.
                yield break;
            }

            // must keep the portal alive after next scene has 
            // loaded, until we have finished the co-routine. so
            // the gameobject and this script dont dissapear.
            DontDestroyOnLoad(gameObject);
            // save current level. saves for all scenes.
            SavingWrapper wrapper = FindObjectOfType<SavingWrapper>();
            // fade out from scene.
            Fader fader = FindObjectOfType<Fader>();

            // remove controll from the player so we dont run in another portal when this transition is running and create two
            // transitions running at the same time creating a race condition. if not we could portal to an arbitrary location.
            PlayerController playerController = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
            playerController.enabled = false;
            // with yield return the IEnumerator is run as a coroutine
            // and code after the yield return waits for it to finish.
            yield return fader.FadeOut(fadeOutTime);
             
            
            wrapper.Save();

            
            
            // we use the buildindex to load our scene. The courutine runs until this point
            // and loads the scene. It will then return the async operation to unity. Then
            // unity will call this co-routune again when the scene has finished loading
            // and finish the rest of the code.
            yield return SceneManager.LoadSceneAsync(sceneToLoad);
            print("scene loaded");
            
            // disable player again when we load the scene. this is a new player so must find the player again. 
            PlayerController newPlayerController = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
            newPlayerController.enabled = false;

            // load current level.
            wrapper.Load();
            
            // get hold of the portal in the scene we enter. we want to spawn here.
            Portal otherPortal = GetOtherPortal();
            // then we update the player, place him at the portal.
            UpdatePlayer(otherPortal);
            // we save again after we placed the player so if we quite the game
            // we will load into this spot in this scene.
            wrapper.Save();
            
            // wait a bitt for camera etc to stabilaze.
            yield return new WaitForSeconds(fadeWaitTime);
            // fade into scene. must do this after portal and player is set.
            yield return fader.FadeIn(fadeInTime);

            // restore controll to player.
            newPlayerController.enabled = true;

            Destroy(gameObject);
        }
        // return portal in the new scene not the one this script is attached to.
        // must spawn at the right location.
        private Portal GetOtherPortal()
        {
            foreach  (Portal portal in FindObjectsOfType<Portal>())
            {
                // return the portal with the correct enum. dont return
                // portal from the scene we came from.
                if (portal == this) continue;
                if (this.destination == portal.destination)
                return portal;
            }
            // if we dont find a portal
            return null;
        }
        // set position and rotation of player based on the spawn point.
        private void UpdatePlayer(Portal otherPortal)
        {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            // since we load in our player position above in wrapper.load we must disable the navmesh agent before 
            // we place our player or we can run into a bug.
            player.GetComponent<NavMeshAgent>().enabled = false;
            // use warp to set position. must use warp or else if we have multiple terrains in a 
            // scene we could be warped to a wrong location.
            player.GetComponent<NavMeshAgent>().Warp(otherPortal.spawnPoint.position);
            player.transform.rotation = otherPortal.spawnPoint.rotation;
            player.GetComponent<NavMeshAgent>().enabled = true;
        }

        
    }
}

Things do look right. Can you put a comment on the line that the error is pointing to? (Our system is great, but it doesn’t apply line numbers in code posts.

the error points to line 99. “newPlayerController.enabled = true;”. if i comment out all the places i enable and disable the player controller i loose the error but i still loose everything in my inventory and i end up in the wrong place on the map. im quite sure this happened after i imported the inventory asset pack since I must have tried using the portal after we made the changes to the portal script, but i cant be sure.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.AI;
using RPG.Control;
using GameDevTV.Saving;

namespace RPG.SceneManagement
{
    public class Portal : MonoBehaviour
    {
        // set it to -1 so we get an error if we have not set it up in unity.
        [SerializeField] int sceneToLoad = -1;
        [SerializeField] Transform spawnPoint;
        [SerializeField] float fadeOutTime = 0.5f;
        [SerializeField] float fadeInTime = 0.5f;
        [SerializeField] float fadeWaitTime = 1f;
        // set letters on our portals so we spawn at the correct one.
        enum DestinationIdentifier
        {
            A,B,C,D,E
        }
        [SerializeField] DestinationIdentifier destination;

        private void OnTriggerEnter(Collider other)
        {
            if(other.gameObject.tag == "Player")
            {
                // this co-routine will run between scenes. makes it possible to transfer
                // information into the new scene.
                StartCoroutine(Transition());
                print("Portal triggered");
            }
            
        }

        private IEnumerator Transition()
        {   
            // If we forgott to set the scene build index.
            if (sceneToLoad < 0)
            {
                Debug.LogError("Scene to load not set.");
                // exits function.
                yield break;
            }

            // must keep the portal alive after next scene has 
            // loaded, until we have finished the co-routine. so
            // the gameobject and this script dont dissapear.
            DontDestroyOnLoad(gameObject);
            // save current level. saves for all scenes.
            SavingWrapper wrapper = FindObjectOfType<SavingWrapper>();
            // fade out from scene.
            Fader fader = FindObjectOfType<Fader>();

            // remove controll from the player so we dont run in another portal when this transition is running and create two
            // transitions running at the same time creating a race condition. if not we could portal to an arbitrary location.
            PlayerController playerController = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
            playerController.enabled = false;
            // with yield return the IEnumerator is run as a coroutine
            // and code after the yield return waits for it to finish.
            yield return fader.FadeOut(fadeOutTime);
             
            
            wrapper.Save();

            
            
            // we use the buildindex to load our scene. The courutine runs until this point
            // and loads the scene. It will then return the async operation to unity. Then
            // unity will call this co-routune again when the scene has finished loading
            // and finish the rest of the code.
            yield return SceneManager.LoadSceneAsync(sceneToLoad);
            print("scene loaded");
            
            // disable player again when we load the scene. this is a new player so must find the player again. 
            PlayerController newPlayerController = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
            newPlayerController.enabled = false;

            // load current level.
            wrapper.Load();
            
            // get hold of the portal in the scene we enter. we want to spawn here.
            Portal otherPortal = GetOtherPortal();
            // then we update the player, place him at the portal.
            UpdatePlayer(otherPortal);
            // we save again after we placed the player so if we quite the game
            // we will load into this spot in this scene.
            wrapper.Save();
            
            // wait a bitt for camera etc to stabilaze.
            yield return new WaitForSeconds(fadeWaitTime);
            // fade into scene. must do this after portal and player is set.
            yield return fader.FadeIn(fadeInTime);

            // restore controll to player.
            newPlayerController.enabled = true; // error points here.

            Destroy(gameObject);
        }
        // return portal in the new scene not the one this script is attached to.
        // must spawn at the right location.
        private Portal GetOtherPortal()
        {
            foreach  (Portal portal in FindObjectsOfType<Portal>())
            {
                // return the portal with the correct enum. dont return
                // portal from the scene we came from.
                if (portal == this) continue;
                if (this.destination == portal.destination)
                {
                    return portal;
                }
                
                
            }
            // if we dont find a portal
            return null;
        }
        // set position and rotation of player based on the spawn point.
        private void UpdatePlayer(Portal otherPortal)
        {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            // since we load in our player position above in wrapper.load we must disable the navmesh agent before 
            // we place our player or we can run into a bug.
            player.GetComponent<NavMeshAgent>().enabled = false;
            // use warp to set position. must use warp or else if we have multiple terrains in a 
            // scene we could be warped to a wrong location.
            player.GetComponent<NavMeshAgent>().Warp(otherPortal.spawnPoint.position);
            
            player.transform.rotation = otherPortal.spawnPoint.rotation;
            player.GetComponent<NavMeshAgent>().enabled = true;
        }

        
    }
}

So the PlayerController in the new scene is found and acted on

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

without error
and then after updating the location, saving, warping, waiting and fading in, that newPlayerController is destroyed? And I’m assuming that when you look in the heirarchy after this, that the Player is still in the heirarchy in the Core prefab?

I’m not seeing anything in the code that would destroy the Player, but I can think of one situation in which the player could be destroyed…
Check to amke sure that your scene you’re transitioning to does not have a PersistentObjectPrefab or a SavingWrapper in the Heirarchy when the game isn’t running.
If there is one in the heirarchy, then it willl reload the scene after the new scene is loaded. So if there is, then Actions will happen in the SavingWrapper, and between the Portal and SavingWrapper it will look like this:

Portal:  LoadSceneAsync complete
Portal: Find and disable Player
Portal: Load()
Portal: Locate Other Portal
Portal: UpdatePlayer()
Portal: Save()
Portal: Yield return WaitForSeconds
Saving Wrapper:  Begin LoadLastScene, ultimately destroying the scene we just worked on, ***destroying*** the Player, replacing it with a new one
Portal: Fade
Portal: enable newPlayerContrller, but... it's gone baby gone.

Yes the player is still in the core prefab. The message that the playercontroller is missing comes at the end after the scene is loaded and saved when the script tries to enable it again. Theres is no persisten object or saving prefab in the hierarchy in any scene when the game isnt loading. But after checking the console a bit closer with print statements I can see that the scene is loaded many times that’s why the UI flickers. Before the code that enables the new playerController is runned, the scene is loaded and saved many times. The player is also not spawning inside any of the portals as the spawning point is outside of the portal, but maybe thats whats happening? Maybe something is wrong with the saving wrapper or saving system after introducing the new asset pack? I put in my script with the print statements and the console messages, i had to upload the console messages as images as i couldnt find a way to copy them directly.

I also see that after saving the debug log in the awake function in the fighter script prints out the names of the enemies in the first scene, scene 0. I have no enemies in the second scene, scene 1, so they should not be printed right? Maybe scene 0 and scene 1 is loaded many times? But from the print statements I can only see scene 1 loaded many times.

I also tried printing out the newPlayerController throughout the script and it is found many times as the scene is saved and loaded but right before it tries to enable it after, yield return fader.FadeIn(fadeInTime);, the newPlayerController is null.

after the scene has loaded i have 4 portals from the first scene, scene 0, under dont destroy on load in the next scene.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.AI;
using RPG.Control;
using GameDevTV.Saving;

namespace RPG.SceneManagement
{
    public class Portal : MonoBehaviour
    {
        // set it to -1 so we get an error if we have not set it up in unity.
        [SerializeField] int sceneToLoad = -1;
        [SerializeField] Transform spawnPoint;
        [SerializeField] float fadeOutTime = 0.5f;
        [SerializeField] float fadeInTime = 0.5f;
        [SerializeField] float fadeWaitTime = 1f;
        // set letters on our portals so we spawn at the correct one.
        enum DestinationIdentifier
        {
            A,B,C,D,E
        }
        [SerializeField] DestinationIdentifier destination;

        private void OnTriggerEnter(Collider other)
        {
            if(other.gameObject.tag == "Player")
            {
                // this co-routine will run between scenes. makes it possible to transfer
                // information into the new scene.
                StartCoroutine(Transition());
                print("Portal triggered");
            }
            
        }

        private IEnumerator Transition()
        {   
            // If we forgott to set the scene build index.
            if (sceneToLoad < 0)
            {
                Debug.LogError("Scene to load not set.");
                // exits function.
                yield break;
            }

            // must keep the portal alive after next scene has 
            // loaded, until we have finished the co-routine. so
            // the gameobject and this script dont dissapear.
            print("DONT DESTROY GAMEOBJECT");
            DontDestroyOnLoad(gameObject);
            // save current level. saves for all scenes.
            SavingWrapper wrapper = FindObjectOfType<SavingWrapper>();
            // fade out from scene.
            Fader fader = FindObjectOfType<Fader>();

            // remove controll from the player so we dont run in another portal when this transition is running and create two
            // transitions running at the same time creating a race condition. if not we could portal to an arbitrary location.
            PlayerController playerController = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
            print("DISABLING PLAYER CONTROLLER");
            playerController.enabled = false;
            // with yield return the IEnumerator is run as a coroutine
            // and code after the yield return waits for it to finish.
            yield return fader.FadeOut(fadeOutTime);
             
            
            wrapper.Save();



            // we use the buildindex to load our scene. The courutine runs until this point
            // and loads the scene. It will then return the async operation to unity. Then
            // unity will call this co-routune again when the scene has finished loading
            // and finish the rest of the code.
            print("LOADING NEW SCENE " + sceneToLoad );
            yield return SceneManager.LoadSceneAsync(sceneToLoad);
            print("SCENE LOADED");
            
            // disable player again when we load the scene. this is a new player so must find the player again. 
            PlayerController newPlayerController = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
            print("DISABLING NEW PLAYER CONTROLLER");
            newPlayerController.enabled = false;
            print("LOADING STATES");
            // load current level.
            wrapper.Load();
            print("GETTING PORTAL");
            // get hold of the portal in the scene we enter. we want to spawn here.
            Portal otherPortal = GetOtherPortal();
            // then we update the player, place him at the portal.
            print("UPDATING PLAYER TO PORTAL");
            UpdatePlayer(otherPortal);
            // we save again after we placed the player so if we quite the game
            // we will load into this spot in this scene.
            print("SAVE STATE");
            wrapper.Save();
            
            // wait a bitt for camera etc to stabilaze.
            yield return new WaitForSeconds(fadeWaitTime);
            // fade into scene. must do this after portal and player is set.
            yield return fader.FadeIn(fadeInTime);
            print("ENABLING PLAYERCONTROLLER");
            // restore controll to player.
            newPlayerController.enabled = true; // error points here.
            print("DESTROY GAMEOBJECT");
            Destroy(gameObject);
        }
        // return portal in the new scene not the one this script is attached to.
        // must spawn at the right location.
        private Portal GetOtherPortal()
        {
            foreach  (Portal portal in FindObjectsOfType<Portal>())
            {
                // return the portal with the correct enum. dont return
                // portal from the scene we came from.
                if (portal == this) continue;
                if (this.destination == portal.destination)
                {
                    return portal;
                }
                
                
            }
            // if we dont find a portal
            return null;
        }
        // set position and rotation of player based on the spawn point.
        private void UpdatePlayer(Portal otherPortal)
        {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            // since we load in our player position above in wrapper.load we must disable the navmesh agent before 
            // we place our player or we can run into a bug.
            player.GetComponent<NavMeshAgent>().enabled = false;
            // use warp to set position. must use warp or else if we have multiple terrains in a 
            // scene we could be warped to a wrong location.
            player.GetComponent<NavMeshAgent>().Warp(otherPortal.spawnPoint.position);
            
            player.transform.rotation = otherPortal.spawnPoint.rotation;
            player.GetComponent<NavMeshAgent>().enabled = true;
        }

        
    }
}









Paste in your SavingWrapper script, I think I know what might be going on.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using GameDevTV.Saving;

namespace RPG.SceneManagement
{
    public class SavingWrapper : MonoBehaviour
    {
        // we name the save file.
        const string defaultSaveFile = "save";
        [SerializeField] float fadeInTime = 2f;

        // call the load scene function wich restores our savefile. do this in awake
        // so we load our game and savefile before any start methods are run. ex our basesstats
        // script uses restored xp to calculate level. in start we calculate the level, if we havent
        // restored the xp before start is run we always calculate level 1.
        private void Awake()
        {
            StartCoroutine(LoadLastScene());
        }

        // we dont exit co-routines using return. we use yield.
        // yield return holds execution and what comes after the yield return
        // determines how long the method stops before resuming again.

        //IEnumerator. Becomes a co-routine. Here we load up the saved scene 
        // when we start the game.
        private IEnumerator LoadLastScene()
        {
            
            // In an IEnumerator you can yield return another IEnumerator. LoadLastScene is an IEnumerator.
            // Waits for the co-routine. Code after waits for this to finish.
            yield return GetComponent<SavingSystem>().LoadLastScene(defaultSaveFile);
            // We fade in when we load the scene so we dont see any jerking around. we can call this method in awake
            // since we do it after loadlastscene, which means its called after all the awakes have taken place.
            Fader fader = FindObjectOfType<Fader>();
            fader.FadeOutImmideate();

            yield return fader.FadeIn(fadeInTime);
            
        }

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

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

        public void Save()
        {
            // call to saving system to save.
            // we give save a string that is the file we wish to save.
            GetComponent<SavingSystem>().Save(defaultSaveFile);
        }

        public void Load()
        {
            // call to saving system to load.
            // we give load a string that is the save file.
            // must run it as a coroutine since loadlastscene is a coroutine.
            StartCoroutine(GetComponent<SavingSystem>().LoadLastScene(defaultSaveFile));
            
        }

        public void Delete()
        {
            // delete our save file.
            GetComponent<SavingSystem>().Delete(defaultSaveFile);
        }
    }
}

It’s as I thought… I was pretty sure we had a patch recorded to address this one.

The problem is that at some point between the Core Combat course and the Inventory course, Sam made a change to the Load() method that created an infinite loop.

Here’s the fix:
In Update(), change

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

to

            if (Input.GetKeyDown(KeyCode.L))
            {
                 StartCoroutine(LoadLastScene());
            }

And change the contents of Load() from

        public void Load()
        {
            // call to saving system to load.
            // we give load a string that is the save file.
            // must run it as a coroutine since loadlastscene is a coroutine.
            StartCoroutine(GetComponent<SavingSystem>().LoadLastScene(defaultSaveFile));
            
        }

to

        public void Load()
        {
            // call to saving system to load.
            // we give load a string that is the save file.
            GetComponent<SavingSystem>().Load(defaultSaveFile);
            
        }

The original fix was because before, when you pressed the L key, the scene wasn’t truly reloaded and this caused issues like dead characters not coming back to life, etc. This conflicted with Portal calling Load, which caused a sort of quasi-infinite loop to occur.

Fantastic it all works now! I had to make the load method in the savingsystem public. I didnt go through the savingsystem videos since sam said we could skip this. But why do we LoadLastScene when we press L and just use Load for everything else?

One thing still remains. When i pick up the weapon gameobjects which has a pickupspawner and a saveable entityscript on them and save in the same scene they are gone if i stop the game and start it again. But if i go to the next scene and back again they are still gone. But if i now shut down the game and start it again the items are loaded back into the scene.

This is as it should be.

What do you mean by shut down the game? As in exit Unity?
They should still be gone unless the save file has been deleted.

I have not deleted the save file. I mean when i press play and stop inside unity. If i go to the next scene and back again and then press stop and play the items are back. I put a print function that prints out the boolean shouldBeCollected in the PickUpSpawner script in restorestate. If i save in the same scene all these booleans are returned true so the pickup prefabs are destroyed. But if i enter the next scene and go back again and without saving manually stop and start the game again all these booleans are returned false.

Ok so i found a solution from the saving bug video where another person had the same problem with items appearing again after going into a new scene an loading the game once more. You save with the savingWrapper after you have enabled the newPlayerController again in the portal script. Why this works i have no idea.

Privacy & Terms