Black screen when loading after changes in this lecture

After moving the Start method to a method called in Awake, a fresh save file seems to work fine, but if you load one, you just get a black screen. The error is a null reference exception in Health on navMeshAgent.enabled = false; The navmeshagent is cached in Awake of that script.

It’s not cached which is why it throws the NRE. You can check this by adding Debug.Assert(navMeshAgent != null); after you cached it (in Awake). If it is null, the debug will let you know. It will be something like

void Awake()
{
    navMeshAgent = GetComponent<NavMeshAgent>();
    Debug.Assert(navMeshAgent != null);
}

I added the Debug.Assert, nothing extra showed up in the debug, still just the one null reference exception.

namespace RPG.Attributes
{
    public class Health : MonoBehaviour, ISaveable
    {
        float healthPoints = -1f;
        bool isDead = false;
        NavMeshAgent navMeshAgent;

        void Awake() 
        {
            navMeshAgent = GetComponent<NavMeshAgent>();
            Debug.Assert(navMeshAgent != null);
        }

        void Start()
        {
            if(healthPoints < 0f)
            {
                healthPoints = GetComponent<BaseStats>().GetStat(Stat.Health);
            }
        }

        private void OnEnable() 
        {
            if(!isDead) navMeshAgent.enabled = true;
        }

        public bool IsDead()
        {
            return isDead;
        }
    
        public void TakeDamage(GameObject instigator, float damage)
        {
            healthPoints = Mathf.Max(healthPoints - damage, 0);
            if (healthPoints == 0f)
            {
                Die();
                AwardExperience(instigator);
            }

        }

        public float GetPercentage()
        {
            return 100f * (healthPoints / GetComponent<BaseStats>().GetStat(Stat.Health));
        }

        void Die()
        {
            if (isDead) return;
            GetComponent<Animator>().SetTrigger("die");
            isDead = true;
            GetComponent<ActionScheduler>().CancelCurrentAction();
            navMeshAgent.enabled = false;
        }

        void AwardExperience(GameObject instigator)
        {
            Experience experience = instigator.GetComponent<Experience>();
            if (experience == null) return;
            experience.GainXP(gameObject.GetComponent<BaseStats>().GetStat(Stat.XPReward));
        }

        public object CaptureState()
        {
            return healthPoints;
        }

        public void RestoreState(object state)
        {
            healthPoints = (float)state;
            if (healthPoints <= 0)
            {
            Die();
            }
        }
    }

}

The RestoreState() should be being called after the Awake() method is run, so I’m not entirely sure what’s going on here…
Just to make sure of things, change

navMeshAgent.enabled=false;

to

GetComponent<NavMeshAgent>().enabled = false;

That did somehow fix it. I’d definitely like to know what happened though.

Ironically, a race condition happened. Somehow RestoreState() is being called before Awake()
This generally shouldn’t be happening.
Let’s take a look at your SavingWrapper.cs file

namespace RPG.SceneMangement
{
    public class SavingWrapper : MonoBehaviour
    {
        const string defaultSaveFile = "save";
        [SerializeField] float fadeInTime = 0.2f;

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

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

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

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

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

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

bump

I’m still not entirely sure what’s going wrong with this… In the SavingSystem’s LoadLastScene, the line
yield return SceneManager.LoadSceneAsync(buildindex) should always have run Awake() on all of the components within the scene… which means that Awake (where the NavMeshAgent is cached) should be called before RestoreState().

The safest thing at this point is to leave the line as GetComponent<NavMeshAgent>().enabled=false; rather than cachine the NavMeshAgent (I’m at work atm, I’m not sure we cached the agent in the original code - There were a lot of places where caching components made sense and we didn’t, but this is an area where it doesn’t neccessarily make sense to do so now).

Caching is great when you’re going to be repeatedly accessing the component. When it’s a one-off, I’m not always sure it’s needed.

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

Privacy & Terms