Inconsistent null reference exception

I have a script with a whole bunch of objects, each one is one of three types, that is defined as class in the script, when I call a method of one of the objects in the script, I sometimes get a null reference exception, literally the same line of code works fine one moment and then causes problems the next…here’s my code…

Assigning the reference to the SoundController script SoundController soundController = GameObject.Find("ScenePersistentObject").GetComponent<SoundController>();

An example of where it sometimes crashes

        try
        {
            guiController.OutputText("\t <color=#500050D0>DEBUG: Rocket has entered stasis</color>");
            soundController.AcceptSound.Play(); 
        }
        catch
        {
            Debug.Log("NULL REFERENCE EXCEPTION WHEN CALLING ACCPETSOUND.PLAY()");

            Debug.Log("GameObject name is: " + soundController.gameObject.name);
            //This line is fine, so the null reference exception isn't the reference to soundController

            Debug.Log("Accept sound type is: " + soundController.AcceptSound.GetType().ToString());
            //This line causes a problem (sometimes), so it must be something to do with the AcceptSound object(?)
        }

(sorry for just dumping all my code in this next one, not sure what is and isn’t relevant)
SoundController Script

using UnityEngine;
using System;
using System.Collections;

//The purpose of this script is to play sound effects when other scripts call the appropriate method

public class SoundController : MonoBehaviour {

    public GameSound EngineSound;
    public GameSound ExplosionSound;
    public InterfaceSound ErrorSound;
    public InterfaceSound AcceptSound;
    public InterfaceSound WhiteNoiseSound;
    public InterfaceSound SegmentLoadSound;
    public InterfaceSound LoadInterfaceSound;
    public InterfaceSound TypingSound;
    public VoiceSound CountdownSound;
    public VoiceSound LiftOffSound;
    public VoiceSound ZeroGSound;
    public VoiceSound MoonLandingSound;
    //These must be public so they can be accessed by other scripts

    public AudioSource EngineSoundSource;
    public AudioSource ExplosionSoundSource;
    public AudioSource ErrorSoundSource;
    public AudioSource AcceptSoundSource;
    public AudioSource WhiteNoiseSoundSource;
    public AudioSource SegmentLoadSoundSource;
    public AudioSource LoadInterfaceSoundSource;
    public AudioSource TypingSoundSource;
    public AudioSource CountdownSoundSource;
    public AudioSource LiftOffSoundSource;
    public AudioSource ZeroGSoundSource;
    public AudioSource MoonLandingSoundSource;
    //These must be public so they can be assigned via the inspector

    void Start()
    {
        SetVolumes(); //Sets the volume on each sound object to the corresponding value in the DataMemory script
    }

    public void SetVolumes()
    {
        EngineSound = new GameSound(EngineSoundSource);
        ExplosionSound = new GameSound(ExplosionSoundSource);
        ErrorSound = new InterfaceSound(ErrorSoundSource);
        AcceptSound = new InterfaceSound(AcceptSoundSource);
        WhiteNoiseSound = new InterfaceSound(WhiteNoiseSoundSource);
        SegmentLoadSound = new InterfaceSound(SegmentLoadSoundSource);
        LoadInterfaceSound = new InterfaceSound(LoadInterfaceSoundSource);
        TypingSound = new InterfaceSound(TypingSoundSource);
        CountdownSound = new VoiceSound(CountdownSoundSource);
        LiftOffSound = new VoiceSound(LiftOffSoundSource);
        //These must be passed the sound source, the objects are reinitilaized when the volumes are change in the options menu, so the sound sources cannot be assigned via the editor using the serializable attribute
    }

    void Update()
    {
        if (Time.timeScale != 1)
        {
            CountdownSound.Stop(); //Can't have the countdown voice play if timescale isn't 1, wont sync with countdown timer
        }
    }

}

public abstract class AudioType
{
    protected AudioSource SoundEffect;
    protected DataMemory dataMemory; //Holds the script containing volume variables

    public AudioType(AudioSource soundEffectToSet)
    {
        SoundEffect = soundEffectToSet;
        dataMemory = GameObject.Find("ScenePersistentObject").GetComponent<DataMemory>();
        //sets the script to the component of the object it is attached to
    }

    public void Play()
    {
        try {SoundEffect.Play();}
        catch {Debug.Log("Can't access audiosource: " + GetType().ToString());}
    }
    public void Stop()
    {
        try {SoundEffect.Stop();}
        catch {Debug.Log("Can't access audiosource: " + GetType().ToString());}
    }
    public bool isPlaying()
    {
        try
        {
            if (SoundEffect.isPlaying)
            {return true;}
            else
            {return false;}
        }
        catch {Debug.Log("Can't access audiosource: " + GetType().ToString());}
        return false;
    }
    //The above three methods allow AudioType.Method, instead of AudioType.AudioSource.Method
}


public class GameSound : AudioType
{
    public GameSound(AudioSource soundEffectToSet) : base (soundEffectToSet)
    {//Must have the same paramaters as the constructor from the base class
        SoundEffect.volume = dataMemory.gameVolume;
        //Sets the volume of this objec's sound source to the corresponding variable
    }
}

public class InterfaceSound : AudioType
{
    public InterfaceSound(AudioSource soundEffectToSet) : base(soundEffectToSet)
    {//Must have the same paramaters as the constructor from the base class
        SoundEffect.volume = dataMemory.interfaceVolume;
        //Sets the volume of this objec's sound source to the corresponding variable
    }
}

public class VoiceSound : AudioType
{
    public VoiceSound(AudioSource soundEffectToSet) : base(soundEffectToSet)
    {//Must have the same paramaters as the constructor from the base class
        SoundEffect.volume = dataMemory.voiceOverVolume;
        //Sets the volume of this object's sound source to the corresponding variable
    }
}

There doesn’t seem to be any pattern as to when it does and doesn’t crash, but restarting it always seems to fix it, until it causes issues again, how is the code not behaving in the same way each time, is something making one of the reference variables to be, I don’t know forgotten or something?

Any input would be appreciated :slight_smile:

Thanks,
Fred T.A

If SoundController is disabled in the scene then Start wouldn’t be called and therefore AcceptSound (and everything else) would be null. Depending on how deep the error is, the AudioSource controlling AcceptSound could be null which means it isn’t present when all this initialization happens or it’s destroyed at some point.

That being said - this whole pattern is really confusing. What problem were you trying to solve by going this route? It’s very odd to set up all this abstraction and then just dump the result into a public member that anyone else could fiddle with. Why doesn’t SoundController just have a PlayAcceptSound() method that handles all this stuff internally? Seems like a cleaner interface in my mind.

It’s for a school project, to get all the marks you’ve just got to tick all the boxes of techniques used, even if isn’t necessarily the best way of doing it

The GameObject the script is attached maintains active throughout the entire scene, as does the script component :confused:

Hard to say then. You’ve at least narrowed it down - something is nulling out AcceptSound or you’re hitting it at a point where it hasn’t been initialized.

At the very least it’d be informative to log or throw the NullReferenceException to see where exactly it’s being thrown and what the call stack looks like.

I’m not very experienced with exception handling yet, is that not what I’m doing in the code in the second spoiler? It’s throwing the exception where I trying to play the sound, but there’s no special condition for it, even getting the sound to play under the exact same conditions can yield inconsistent results…

No - you’re catching it and printing your own error. You can either remove the try-catch block or re-throw/print the exception in your catch

try
{
}
catch (Exception e)
{
    Debug.LogError(e);
    throw e;
}