NullReferenceException - In Build but not in Editor

Good morning, all, I figured I’d post this here since I have been stuck on this for a week now. I have scriptable objects, in this case that show a list of passive upgrades that you can apply on level up. Somehow they all work fine in the editor, i.e. the potion fills up your health, the max health increases your max health, speed, damage etc. It all works in the editor, but suddenly in the build and play it throws this error.

Level Up! Now Level: 2

=== ShowLevelUpUI START ===

Available Upgrades: 8

Upgrade Loaded: Armor, Type: Armor, Effect: 0.05

Upgrade Loaded: Velocity, Type: AttackSpeed, Effect: 1

Upgrade Loaded: Damage, Type: Damage, Effect: 0.5

Upgrade Loaded: Speed, Type: Speed, Effect: 0.2

Upgrade Loaded: Regen++, Type: HealthRegen, Effect: 0.05

Upgrade Loaded: Drink!, Type: HealthPotion, Effect: 20

Upgrade Loaded: MaxHealth, Type: Health, Effect: 0

Upgrade Loaded: XPMagnetism, Type: XP_Magnet, Effect: 0.05

Chosen Upgrade: XPMagnetism

Chosen Upgrade: Armor

Chosen Upgrade: Speed

Assigned Button 0 to XPMagnetism

Assigned Button 1 to Armor

Assigned Button 2 to Speed

Mouse Click Registered in Build

Button 1 clicked - will apply: Armor

=== Applying Upgrade: Armor, Type: Armor ===

NullReferenceException: Object reference not set to an instance of an object

at GameManager.ApplyUpgrade (PassiveUpgrade upgrade) [0x001eb] in <60b7e24ff9bb49888a9b634c8a9fb9d2>:0

at GameManager.SelectUpgrade (PassiveUpgrade chosenUpgrade) [0x00000] in <60b7e24ff9bb49888a9b634c8a9fb9d2>:0

at GameManager+<>c__DisplayClass39_1.b__0 () [0x00056] in <60b7e24ff9bb49888a9b634c8a9fb9d2>:0

at UnityEngine.Events.InvokableCall.Invoke () [0x00010] in <9797f11fd4464efd956e83c7ca4e1986>:0

at UnityEngine.Events.UnityEvent.Invoke () [0x00022] in <9797f11fd4464efd956e83c7ca4e1986>:0

at UnityEngine.UI.Button.Press () [0x0001c] in <112f91bf5ea2491a833cfc57cb249ba4>:0

at UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) [0x00009] in <112f91bf5ea2491a833cfc57cb249ba4>:0

at UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00007] in <112f91bf5ea2491a833cfc57cb249ba4>:0

at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) [0x00067] in <112f91bf5ea2491a833cfc57cb249ba4>:0

UnityEngine.DebugLogHandler:Internal_LogException_Injected(Exception, IntPtr)

UnityEngine.DebugLogHandler:Internal_LogException(Exception, Object)

UnityEngine.DebugLogHandler:LogException(Exception, Object)

UnityEngine.Logger:LogException(Exception, Object)

UnityEngine.Debug:LogException(Exception)

UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)

UnityEngine.InputSystem.UI.InputSystemUIInputModule:ProcessPointerButton(ButtonState&, PointerEventData)

UnityEngine.InputSystem.UI.InputSystemUIInputModule:ProcessPointer(PointerModel&)

UnityEngine.InputSystem.UI.InputSystemUIInputModule:Process()

UnityEngine.EventSystems.EventSystem:Update()

BREAK

As you can see I tried debugging it by adding logs and show what’s been loaded regarding the upgrades.

Here is how I declare the variables in my GameManager script:

> //New variables for Upgrade text and passive upgrades
> public GameObject levelUpPanel; // Assign in Inspector
> public TMP_Text[] upgradeTexts; // Assign the three text elements from buttons
> public Button[] upgradeOptions; // These are the actual buttons
> 
> public List<PassiveUpgrade> availableUpgrades = new List<PassiveUpgrade>(); // Stores all available upgrades
> 
> //Add a dictionary to store upgrade levels
> private Dictionary<PassiveUpgrade.UpgradeType, int> upgradeLevels = new Dictionary<PassiveUpgrade.UpgradeType, int>();
//NEW FUNCTIONS BELOW TO SHOW LEVEL UP UI

private IEnumerator ShowLevelUpUIDelayed()
{
    yield return new WaitForSecondsRealtime(0.1f); // Small delay before showing UI
    ShowLevelUpUI(); // Call the actual function
}

private void ShowLevelUpUI()
{
    Debug.Log("=== ShowLevelUpUI START ===");
    Debug.Log("Available Upgrades: " + availableUpgrades.Count);

    foreach (var upg in availableUpgrades)
    {
        if (upg != null)
            Debug.Log($"Upgrade Loaded: {upg.upgradeName}, Type: {upg.upgradeType}, Effect: {upg.effectValue}");
        else
            Debug.LogError("Null upgrade found in availableUpgrades list!");
    }

    Time.timeScale = 0f; // Pause the game
    levelUpPanel.SetActive(true);

    int numUpgradesToShow = Mathf.Min(availableUpgrades.Count, 3);

    if (numUpgradesToShow == 0)
    {
        Debug.LogError("No available upgrades! Closing Level-Up UI.");
        levelUpPanel.SetActive(false);
        Time.timeScale = 1f;
        return;
    }

    List<PassiveUpgrade> chosenUpgrades = new List<PassiveUpgrade>();
    while (chosenUpgrades.Count < numUpgradesToShow)
    {
        PassiveUpgrade randomUpgrade = availableUpgrades[Random.Range(0, availableUpgrades.Count)];
        if (randomUpgrade != null && !chosenUpgrades.Contains(randomUpgrade))
        {
            chosenUpgrades.Add(randomUpgrade);
            Debug.Log($"Chosen Upgrade: {randomUpgrade.upgradeName}");
        }
    }

    for (int i = 0; i < numUpgradesToShow; i++)
    {
        if (chosenUpgrades[i] == null)
        {
            Debug.LogError($"Chosen upgrade at index {i} is NULL!");
            continue;
        }

        upgradeTexts[i].text = chosenUpgrades[i].upgradeName;
        upgradeOptions[i].GetComponentInChildren<Image>().sprite = chosenUpgrades[i].icon;

        int index = i; // Prevent closure issue
        upgradeOptions[i].onClick.RemoveAllListeners();
        upgradeOptions[i].onClick.AddListener(() =>
        {
            Debug.Log($"Button {index} clicked - will apply: {chosenUpgrades[index].upgradeName}");
            SelectUpgrade(chosenUpgrades[index]);
        });

        Debug.Log($"Assigned Button {i} to {chosenUpgrades[i].upgradeName}");
    }


    // Hide unused buttons if fewer than 3 upgrades exist
    for (int i = numUpgradesToShow; i < upgradeOptions.Length; i++)
    {
        upgradeOptions[i].gameObject.SetActive(false);
    }

    Canvas.ForceUpdateCanvases();
    LayoutRebuilder.ForceRebuildLayoutImmediate(levelUpPanel.GetComponent<RectTransform>());

}

Above is how I show the LevelUpUI and below is how I select and apply the upgrades.

//FUNCTION TO APPLY THE CHOSEN UPGRADE
public void SelectUpgrade(PassiveUpgrade chosenUpgrade)
{
    ApplyUpgrade(chosenUpgrade); // Apply the selected upgrade
    levelUpPanel.SetActive(false); // Hide UI
    Time.timeScale = 1f; // Resume game
}

//APPLY THE UPGRADES
private void ApplyUpgrade(PassiveUpgrade upgrade)
{
    if (upgrade == null)
    {
        Debug.LogError("Upgrade is NULL in ApplyUpgrade!");
        return;
    }

    Debug.Log($"=== Applying Upgrade: {upgrade.upgradeName}, Type: {upgrade.upgradeType} ===");

    if (!upgradeLevels.ContainsKey(upgrade.upgradeType))
        upgradeLevels[upgrade.upgradeType] = 0;

    if (upgradeLevels[upgrade.upgradeType] < 10)
    {
        upgradeLevels[upgrade.upgradeType]++;
        float totalBonus = upgrade.effectValue * upgradeLevels[upgrade.upgradeType];

        switch (upgrade.upgradeType)
        {
            case PassiveUpgrade.UpgradeType.Speed:
                playerController.moveSpeed += upgrade.effectValue;
                break;

            case PassiveUpgrade.UpgradeType.Damage:
                playerWeapon.IncreaseDamage(upgrade.effectValue);
                Debug.Log($"Damage increased! New multiplier: {playerWeapon.GetDamageMultiplier()}");
                break;

            case PassiveUpgrade.UpgradeType.AttackSpeed:
                playerWeapon.fireRate += upgrade.effectValue;
                playerWeapon.fireRate = Mathf.Clamp(playerWeapon.fireRate, 0.1f, 5f);
                break;

            case PassiveUpgrade.UpgradeType.Health:
                float previousMax = playerHealth.maxHealth;
                playerHealth.maxHealth += 20;
                if (playerHealth.CurrentHealth == previousMax)
                    playerHealth.CurrentHealth = playerHealth.maxHealth;
                GameManager.instance.UpdateHealthUI(playerHealth.CurrentHealth, playerHealth.maxHealth);
                break;

            case PassiveUpgrade.UpgradeType.XP_Magnet:
                xpMagnetRangeBonus += upgrade.effectValue;
                break;

            case PassiveUpgrade.UpgradeType.Armor:
                playerHealth.armor += upgrade.effectValue;
                break;

            case PassiveUpgrade.UpgradeType.HealthPotion:
                playerHealth.RestoreHealth((int)(playerHealth.maxHealth * 0.2f));
                break;

            case PassiveUpgrade.UpgradeType.HealthRegen:
                if (!isHealthRegenActive)
                {
                    isHealthRegenActive = true;
                    StartCoroutine(HealthRegenOverTime());
                }
                break;
        }

        Debug.Log($"✅ Applied {upgrade.upgradeName}, New Level: {upgradeLevels[upgrade.upgradeType]}");
    }
}

I also wanted to show below my script for the passive upgrades.

using UnityEngine;

[CreateAssetMenu(fileName = "New Passive Upgrade", menuName = "Upgrades/Passive Upgrade")]
public class PassiveUpgrade : ScriptableObject
{
    public string upgradeName;  // Name of the upgrade (e.g., "Muscle Arm")
    public string description;  // Short description (e.g., "Increases damage by 2%")
    public Sprite icon;         // Icon to display in the UI
    public float effectValue;   // The value this upgrade increases (e.g., 2% = 0.02)

    public enum UpgradeType {Damage, Speed, AttackSpeed, Health, Armor, HealthRegen, HealthPotion, XP_Magnet}
    public UpgradeType upgradeType;  // Defines what the upgrade affects
}

I can’t for the life of me figure out where this NullReferenceException is. From my understanding it is somehwere in the ApplyUpgrade function. What gets me is, why is this working just fine in the editor, but on build it isn’t? It’s almost like a script doesn’t run, or something doesn’t get initialized correctly and it can’t find the available upgrades?

I’m not asking for your to fix my script, but rather help me narrow it down and help me understand what is actually causing it. Your help is much appreciated :slight_smile:

I just answered my own question… It was missing player references (playerHealth, playerWeapon, playerController) when ApplyUpgrade() was called too early in the build.

I fixed it by creating a delayed player reference set up

StartCoroutine(DelayedReferenceSetup());

And inside that coroutine I added.

GameObject player = GameObject.FindWithTag("Player");
playerHealth = player.GetComponent<PlayerHealth>();
playerWeapon = player.GetComponent<PlayerWeapon>();
playerController = player.GetComponent<PlayerController>();

I think this gives Unity a bit of time to fully instantiate the Player prefab before trying to access the components.

Then I added safety checks so that if references are still null at the moment of upgrade…

if (playerHealth == null || playerWeapon == null || playerController == null)
{
    GameObject player = GameObject.FindWithTag("Player");
    if (player != null)
    {
        // reassign missing references here
    }
}

It seems to work flawlessly now and all my upgrades work upon build.

1 Like