CharacterController NullReferenceException only in Windows Build

Hi,

I’ve been running into an issue when attempting to build my game.

The game runs perfectly in the Unity Editor on both Windows and MacOS. The MacOS build of the game also runs perfectly without issue. However, for some reason in the Windows standalone build, when I enter my main game scene I encounter a NullReferenceException when attempting to access the CharacterController on my player in order to move it.

NullReferenceException: Object reference not set to an instance of an object
at PlayerController.Update () [0x0004c] in …\Reef Rally 2019.2.7\Assets\Scripts\PlayerController.cs:111

I have attached the editor log from the most recent build run.

Here is my PlayerController script where the error is originating. I should mention that the motion controller referenced within the script should have no effect on ability to find the CharacterController.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Leap;

/*
    This script controls the movement mechancics of the player when using both a keyboard or motion controller. The main function of this script
    determines whether or not the player is dead or alive. Then, depending on if a hand is detected by the controller it decides whether the game will be
    controlled by the keyboard or by the motion controller.
*/

public class PlayerController : MonoBehaviour
{
    //Define External Scripts
    [HideInInspector] HandController handPosition;
    [HideInInspector] public CharacterController controller;
    [HideInInspector] public GameplayController gameplayController;

    //Define GameObjects
    [HideInInspector] public GameObject player;
    [HideInInspector] private GameObject mainCamera;

    //Define player states
    [HideInInspector] public enum State { None, Alive, Dead };
    [HideInInspector] public State playerState;

    //Add ranged slider to inspector for camera distance from player
    [Range(0.0f, 20.0f)] [SerializeField] float cameraOffsetZ;
    [Range(-5.0f, 5.0f)] [SerializeField] float cameraOffsetY;

    //Define Leap Motion Interaction Box Vector
    Vector normalized;

    //Define input axis variables
    float leapHorizontal;
    float leapVertical;
    float horizontal;
    float vertical;

    //Create inspector input for Movement variables
    [SerializeField] float controlSpeed = 10f; //Speed in m/s^-1
    //[SerializeField] float leapControlSpeed = 15f; //Speed in m/s^-1
    [SerializeField] public float forwardVelocity = 5.0f;
    [SerializeField] float waterGravity = 3.0f;
    [SerializeField] float leapMovementScale = 1.5f;

    //Throw is 1 > -1 offset from centre of controller
    float xThrow;
    float yThrow;

    //Create inspector input for Player Rotation Variables
    private float controlPitchFactor = -30f;
    private float controlRollFactor = -20f;
    private float leapRotationFactor = 50;

    //Define Character postion as Vector3
    Vector3 characterPos;

    //Create floats for player movement restrictions
    [SerializeField] public float xClamp = 4f;
    [SerializeField] public float yClamp = 3f;
    private float xLeapClampMin = -3.5f;
    private float xLeapClampMax = 3.5f;
    private float yLeapClampMin = 1f;
    private float yLeapClampMax = 6f;

    private void Awake()
    {
        //Access Game Objects
        mainCamera = GameObject.FindGameObjectWithTag("MainCamera");

        //Access External Scripts
        handPosition = GameObject.FindGameObjectWithTag("HandController").GetComponent<HandController>();
        gameplayController = mainCamera.GetComponent<GameplayController>();
    }

    // Start is called before the first frame update
    void Start()
    {
        //Set Player to dead, is change to alive by GameplayController after start delay
        gameplayController.playerState = GameplayController.State.Dead;

        player = GameObject.FindGameObjectWithTag("Player");

        cameraOffsetY = 0.8f;
        cameraOffsetZ = 6f;
    }

    // Update is called once per frame
    void Update()
    {
        //Get HandController Data from current frame
        Frame frame = handPosition.GetFrame();
        //Define hand as most prominent hand detected in the frame by Leap Motion
        Hand hand = frame.Hands.Frontmost;

        controller = player.GetComponent<CharacterController>();
        //controller.detectCollisions = false;

        AccessInputVariables(frame, hand);

        //Run if player is not Dead
        if (gameplayController.playerState != GameplayController.State.Dead)
        {
            //Move player forward at specified speed m/s
            controller.Move((Vector3.forward * forwardVelocity) * Time.deltaTime);
            controller.Move((Vector3.down * waterGravity) * Time.deltaTime);

            //If hand is detected by Motion Controller
            if (hand.IsValid)
            {
                UseHandControls(frame, hand, leapHorizontal, leapVertical);
            }
            //else if no hand is detected
            else
            {
                UseKeyControls(horizontal, vertical);
            }
        }

        //Camera follows the player
        CameraFollow();

    }

    private void AccessInputVariables(Frame frame, Hand hand)
    {
        //Access Leap Motion Interaction Box
        normalized = frame.InteractionBox.NormalizePoint(hand.PalmPosition, true) * 2;

        //Access input axis and Interaction Box axis
        leapHorizontal = normalized.x - 1;
        leapVertical = normalized.y - 1;
        horizontal = Input.GetAxis("Horizontal");
        vertical = Input.GetAxis("Vertical");
    }

    public void UseKeyControls(float horizontal, float vertical)
    {
        xPlayerMovement(controlSpeed, horizontal);
        yPlayerMovement(controlSpeed, vertical);
        PlayerRotation(horizontal, vertical);
    }

    public void UseHandControls(Frame frame, Hand hand, float horizontal, float vertical)
    {
        //Interaction Box Hand Controls
        //xPlayerMovement(leapControlSpeed, horizontal);
        //yPlayerMovement(leapControlSpeed, vertical);
        //PlayerRotation(horizontal, vertical);

        //Legacy Hand Controls
        PlayerMovement(hand);
        PlayerRotation(hand);
    }

    private void CameraFollow()
    {
        //Define characterPos as current world position of the character model
        characterPos = player.transform.position;

        //Set camera position to player position with a defined offset behind the player
        mainCamera.transform.position = new Vector3(characterPos.x, characterPos.y + cameraOffsetY, characterPos.z - cameraOffsetZ);
    }

    private void xPlayerMovement(float SpeedFactor, float inputAxis)
    {
        //Horizontal input from controller/keyboard - joystick/a-d
        xThrow = inputAxis;

        //Time.deltaTime = time passed since last frame. Allows for constant velocity with varying fps distance = speed x Velocity
        float xOffset = xThrow * controlSpeed * Time.deltaTime;

        //Change player x-axis position with offset included
        float xPos = player.transform.localPosition.x + xOffset;

        //Restrict how far player can move
        xPos = Mathf.Clamp(xPos, -xClamp, xClamp);

        //Change position on x-axis without altering y and z axes
        player.transform.localPosition = new Vector3(xPos, player.transform.localPosition.y, player.transform.localPosition.z);
    }

    private void yPlayerMovement(float SpeedFactor, float inputAxis)
    {
        //Vertical input from controller/keyboard - joystick/w-s
        yThrow = inputAxis;

        //Time.deltaTime = time passed since last frame. Allows for constant velocity with varying fps distance = speed X Velocity
        float yOffset = yThrow * SpeedFactor * Time.deltaTime;

        //Change player y-axis position with offset included
        float yPos = player.transform.localPosition.y + yOffset;

        //Restrict how far player can move
        yPos = Mathf.Clamp(yPos, -yClamp, yClamp);

        //Change position on y-axis without altering y and z axes
        player.transform.localPosition = new Vector3(player.transform.localPosition.x, yPos, player.transform.localPosition.z);
    }

    private void PlayerRotation(float xRotation, float yRotation)
    {
        //Define Roll and Pitch multiplied by their control factor
        float roll = xRotation * controlRollFactor;
        float pitch = yRotation * controlPitchFactor;

        //Transform rotation based on factored proportions in relation to position of player
        player.transform.localRotation = Quaternion.Euler(pitch, 0, roll); //x, y, z
    }

    private void PlayerMovement(Hand hand)
    {
        Vector3 getHandPosition = handPosition.transform.TransformPoint(hand.PalmPosition.ToUnityScaled());

        //Define X and Y axes of hand position and mulitply by leapMovementScale
        float xPosHand = getHandPosition.x * leapMovementScale;
        float yPosHand = getHandPosition.y * leapMovementScale;

        //Set clamps for hand position
        float clampedXPos = Mathf.Clamp(xPosHand, xLeapClampMin, xLeapClampMax);
        float clampedYPos = Mathf.Clamp(yPosHand, yLeapClampMin, yLeapClampMax);

        //Transform position of Player with position of user's hand
        player.transform.position = new Vector3(clampedXPos, clampedYPos, player.transform.position.z);
    }

    private void PlayerRotation(Hand hand)
    {
        float pitch = hand.Direction.Pitch * -leapRotationFactor;
        float roll = hand.PalmNormal.Roll * leapRotationFactor;

        player.transform.rotation = Quaternion.Euler(pitch, 0, roll);
    }
}

This is my first time posting on these threads so apologies if I have not uploaded everything in the correct format.

You probably should put your file path in the post as everyone now knows you are in your 3rd year in college :slight_smile:

NullReferenceExceptions are a pain - especially when it works in the editor and not on a certain build. I had this problem many times and EVERY SINGLE TIME it was because I was using one of the Find methods like you are. Apparently, different platforms don’t maintain the same order of GameObjects… For instance, if you have two objects that have the same tag and you do the statement in line 73, FindGameObjectWithTag, it may pick up the 1st object in your list in the Unity Editor but on a different platform (Windows, XBOX, etc), it may pick up the 2nd one. If you don’t have the right setup on that object, you may receive this error.

Not sure if that’s your problem but just check and see if you have multiple objects with the same tag unintentionally (maybe you duplicated an object to test something?).

1 Like

Hey, thanks for taking the time to respond!

I managed to eventually trace it back to exactly how you had described. I had moved my the colliders from my character to a separate object within the parent and I had left the same tag on this new child object as the parent. And then yes, for whatever reason in the way the platform builders work, the Windows build appeared to go directly for the child object as opposed to the parent which contained the characterController.

No problem - I’m glad you were able to solve it!