Unity error I can not figure out

Hi,
I have been following this course from a coding perspective verbatum as I am new to game development and coding and a solo dev. I have followed Ben’s CameraRaycaster.cs script up to this point. Upon recent changes to the code during the delegate and event lectures I am not sure what I have missed as I have compared line for line in the project changes link. When I compile I get no errors. When I run the game, however, I get a system.nullreferencexception has been thrown on the calling of the delegate line “onLayerChange(layer);” in the if statement in the foreach loop. Can anyone help me think of why that error would appear unless Ben made a unity change that I missed. Thank you!!

James

Hi James,

It could be that nothing is subscribing to your delegate, thus would throw a NullReferenceException error, could you post your full script(s).

Thank you for your reply. here are my 3 current scripts that work together.

public class CameraRaycaster : MonoBehaviour
{
    public Layer[] layerPriorities = 
    {
        Layer.Enemy,
        Layer.Walkable
    };

    [SerializeField] float distanceToBackground = 100f;
    Camera viewCamera;
    RaycastHit raycastHit;

    public RaycastHit hit
    {
        get 
        { 
            return raycastHit; 
        }
    }

    Layer layerHit;

    public Layer currentLayerHit
    {
        get 
        { 
            return layerHit; 
        }
    }

    public delegate void OnLayerChange(Layer newLayer);
    public event OnLayerChange onLayerChange;


    void Start() // TODO Awake?
    {
        viewCamera = Camera.main;
    }

    void Update()
    {
        // Look for and return priority layer hit
        foreach (Layer layer in layerPriorities)
        {
            var hit = RaycastForLayer(layer);
            if (hit.HasValue)
            {
                raycastHit = hit.Value;
                if (layerHit != layer)
                {
                    layerHit = layer;
                    onLayerChange(layer);
                }
                    layerHit = layer;
                    return;
            }
        }

        // Otherwise return background hit
        raycastHit.distance = distanceToBackground;
        layerHit = Layer.RaycastEndStop;
    }

    RaycastHit? RaycastForLayer(Layer layer)
    {
        int layerMask = 1 << (int)layer; // See Unity docs for mask formation
        Ray ray = viewCamera.ScreenPointToRay(Input.mousePosition);

        RaycastHit hit; // used as an out parameter
        bool hasHit = Physics.Raycast(ray, out hit, distanceToBackground, layerMask);
        if (hasHit)
        {
            return hit;
        }
        return null;
    }
}
public class PlayerMovement : MonoBehaviour
{
    [SerializeField] float walkMoveStopRadius = .2f;
    [SerializeField] float attackMoveStopRadius = 5f;

    bool isInDirectMode = false;

    ThirdPersonCharacter m_Character;   // A reference to the ThirdPersonCharacter on the object
    CameraRaycaster cameraRaycaster;
    Vector3 currentDestination, clickPoint; 
     
        
    private void Start()
    {
        cameraRaycaster = Camera.main.GetComponent<CameraRaycaster>();
        m_Character = GetComponent<ThirdPersonCharacter>();
        currentDestination = transform.position;
    }

    // Fixed update is called in sync with physics
    private void FixedUpdate()
    {
        if (Input.GetKeyDown(KeyCode.G))
        {
            isInDirectMode = !isInDirectMode;
            currentDestination = transform.position;
        }

        if (isInDirectMode)
        {
            ProcessDirectMovement();
        }
        else
        {
            MouseInput();
        }

    }

    void ProcessDirectMovement()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");

        Vector3 m_CamForward = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized;
        Vector3 m_Move = v * m_CamForward + h * Camera.main.transform.right;

        m_Character.Move(m_Move, false, false);

    }

    void MouseInput()
    {
        if (Input.GetMouseButton(0))
        {
            clickPoint = cameraRaycaster.hit.point;
            switch (cameraRaycaster.currentLayerHit)
            {
                case Layer.Walkable:
                    currentDestination = ShortDestination(clickPoint, walkMoveStopRadius);
                    break;
                case Layer.Enemy:
                    currentDestination = ShortDestination(clickPoint, attackMoveStopRadius);
                    break;
                default:
                    return;
            }
        }
        WalkToDestination();
    }

        private void WalkToDestination()
        {
            var playerToClickPoint = currentDestination - transform.position;
            if (playerToClickPoint.magnitude >= 0)
            {
                m_Character.Move(playerToClickPoint, false, false);
            }
            else
            {
                m_Character.Move(Vector3.zero, false, false);
            }
        }

    Vector3 ShortDestination(Vector3 destination, float shortening)
    {
        Vector3 reductionVector = (destination - transform.position).normalized * shortening;
        return destination - reductionVector;
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.black;
        Gizmos.DrawLine(transform.position, clickPoint);
        Gizmos.DrawSphere(currentDestination, 0.15f);
        Gizmos.DrawSphere(clickPoint, 0.1f);

        // Gizmos.color = new Color(255f, 0f, 0, .5f);
        Gizmos.DrawWireSphere(transform.position, attackMoveStopRadius);
    }
}
public class CursorAffordance : MonoBehaviour {

    CameraRaycaster cameraRaycaster;

    [SerializeField] Texture2D walkCursor = null;
    [SerializeField] Vector2 cursorHotspot = new Vector2(0, 0);
    [SerializeField] Texture2D unknownCursor = null;
    [SerializeField] Texture2D targetCursor = null;


	// Use this for initialization
	void Start () {
        cameraRaycaster = Camera.main.GetComponent<CameraRaycaster>();
        cameraRaycaster.onLayerChange += OnLayerChanged;
	}
	

	void OnLayerChanged (Layer newLayer) 
    {
        switch (newLayer)
        {
            case Layer.Walkable:
                Cursor.SetCursor(walkCursor, cursorHotspot, CursorMode.Auto);
                break;            
            case Layer.Enemy:
                Cursor.SetCursor(targetCursor, cursorHotspot, CursorMode.Auto);
                break;
            case Layer.RaycastEndStop:
                Cursor.SetCursor(unknownCursor, cursorHotspot, CursorMode.Auto);
                break;
            default:
                Debug.LogError("");
                return;
        }

	}
}

Thanks James.

From a debugging perspective I would start where you are seeing the error itself. First, lets try wrapping the onLayerChange method call with some validation and see if there error still occurs, but in a more contained manner.

Within your Update method in CameraRaycaster.cs, change the line;

onLayerChange(layer);

to;

if(onLayerChange != null)
{
    onLayerChange(layer);
}
else
{
    Debug.Log("Delegate is null");
}  

Try that, and lets see what you get, still the same NullReferenceException error, or, the console output.

It is showing up in console as delegate is null. which probably explains why my layers are not switching when i click between walkable and enemy objects. Only the walkable cursor shows up in runtime. The attacking cursor does not show up when I hover over an enemy. What could I be missing?

Ok, so we know that your subscribing methods are not subscribing as you would like… let’s see if we can work out why.

The only thing which is trying to subscribe is the CursorAffordance.cs script, so, obvious first question, is the CursorAffordance script actually attached to a GameObject?

Assuming it is, in it’s Start method you are retrieving the CameraRaycaster component, if this wasn’t found and you tried to access its onLayerChange variable you would expect to see an error, so I’m going to assume that the component is found from your main camera. If you wanted to be sure, you could apply the same kind of test again, e.g. ;

void Start () 
{
    cameraRaycaster = Camera.main.GetComponent<CameraRaycaster>();

    if(cameraRaycaster != null)
    {
        cameraRaycaster.onLayerChange += OnLayerChanged;
    }
    else
    {
        Debug.Log("I didn't find the CameraRaycaster");
    }
}

You are wiring up the OnLayerChanged method from the CursorAffordance.cs script within the Start method, so if we now take a look at that, nothing is really jumping out at me and suggesting a problem, although the default condition is a bit pointless at the moment, as if the logic were to fall into that I don’t think you would see anything in the console at all, it would be best to put something in that LogError statement;

default:
    Debug.LogError("Unable to determine layer");
    return;

If you have PauseOnError enabled then this may not be an issue for you, but worth considering.

Another point of testing here, you could comment out that entire switch statement and just put a Debug statement in there, a handy way to see if it is getting called at all, e.g.

void OnLayerChanged(Layer newLayer)
{
    Debug.Log("CursorAffordance : OnLayerChanged called");
}

If you made this quick change, does this message appear in the console when you run the game?

I made the debug edits you suggested. My cursorAffordance script is on my Parent camera object which has the main camera as the child. The “Camera Arm” parent has both the CursorAffordance and Camera Raycaster scripts. The main camera has Camera Raycaster only. When i ran the game. The console gave me the delegate is null from before and OnLayerChanged was called. However, with the switch statement commented out there were no texture cursors anymore.

So it looks like my method OnLayerCHnaged with the switch loopis being called and the component script cameraraycaster is being found and my cursor comes back on just doesnt change into my target cursor. It looks like the error is in the camerRaycaster script somewhere

So, the Debug statement in the OnLayerChanged method within the CursorAffordance script is being run and you saw the output in the console yes?

Yes it did pop up in the console as being found

Ok, so that bit is good.

Do you still get the other output though regarding the delegate being null etc, or is that ok now too?

Just trying to establish which bit needs fixing now :slight_smile:

I still get the delegate is null output.

That seems odd being that the delegate is getting called, unless it’s just the order in which the scripts are running.

So, if you now put the switch statement back in to the CursorAffordance script, do your cursors change?

I only get the 1 cursor for walkable…the other 2 do not appear when they should

If you are reasonably early on in the course then you’ll find that the Unknown cursor type doesn’t work anyway, there is another lecture later where this functionality gets re-written and that resolves the error. You should however be able to get both the enemy and walkable cursor types at this point.

If you are not getting the enemy cursor type, it would be worth checking your enemies in the scene also, do they have the correct layer associated?

If you are confident you do, zip the project files up and I’ll be happy to take a quick look, it can be challenging remotely as there is only so far you can realistically.

The forum will allow files of up to 10mb, so if your project files are larger than that then please use a service such as Dropbox or Google Drive and then share the URL.

Can you post your inspector screenshots for the cursor scripts? Mostly I just want to make sure all your cursors have been dragged in. That’s usually where I start with NullReference errors.

I can do that when i get home but all my cursors are in their spots and all the scripts are working and calling the correct methods rob and i have been debugging. All that doesnt seem to work is my main delegate on my cameraRaycast script…the debug is showing up as delegate null instead of picking up a layer

if(onLayerChange != null)
{
    onLayerChange(layer);
}

The above is about the subscribers as opposed to the layers, it will return null if there are none, always best to have a check around these before actually making the call.

I am wondering if, although it’s challenging to debug this remotely, whether because of the execution order of your scripts, you get the null when this code first executes, but then the other script registers a subscriber and after that its ok.

If this is the case, leaving the if statement around it would help protect from the error being generated, but I don’t recall needing this in the course content. As I said earlier, this whole piece of functionality gets re-written a little later in the course so it’ll get fixed anyway.

Just to check, when the null message appears, does it appear only the once, or do you get multiple messages to the console? What is the mouse over when it occurs?

Privacy & Terms