Forum begins after the advertisement:


[part 15] NullReferenceException on treasure chest

Home Forums Video Game Tutorial Series Creating a Rogue-like Shoot-em Up in Unity [part 15] NullReferenceException on treasure chest

Viewing 20 posts - 1 through 20 (of 21 total)
  • Author
    Posts
  • #18692
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    ok so im back again and almost finished with part 15(it’s quite dense and a bit difficult to troubleshoot as we only test the game at the end). when i play the game now everything apparently works (with a couple of errors on the console) except for the chest not being destroyed when my character collides with it

    when i start the game i get both this errors:

    NullReferenceException: Object reference not set to an instance of an object Projectile.Start () (at Assets/Scripts/Weapons/Weapon Effects/Projectile.cs:17)
    NullReferenceException: Object reference not set to an instance of an object Projectile.FixedUpdate () (at Assets/Scripts/Weapons/Weapon Effects/Projectile.cs:70)

    it happens anyways if I use the aura or the projectile characters, but the game plays normally. it just shows this messages on the console

    and then when i try to pick the chest i get this error and the chest dont get destroyed (my chest is called xenovault):

    NullReferenceException: Object reference not set to an instance of an object PlayerInventory.Remove (WeaponData data, System.Boolean removeUpgradeAvailability) (at Assets/Scripts/Player/PlayerInventory.cs:112) Item.AttemptEvolution (ItemData+Evolution evolutionData, System.Int32 levelUpAmount) (at Assets/Scripts/Passive Items/Item.cs:86) XenoVault.OpenXenoVault (PlayerInventory inventory, System.Boolean isHigherTier) (at Assets/Scripts/Pickups/XenoVault.cs:33) XenoVault.OnTriggerEnter2D (UnityEngine.Collider2D col) (at Assets/Scripts/Pickups/XenoVault.cs:13)

    here’s my scripts:

    Projectile:

    using UnityEngine;
    
    [RequireComponent(typeof(Rigidbody2D))]
    public class Projectile : WeaponEffect
    {
        public enum DamageSource {  projectile, owner };
        public DamageSource damageSource = DamageSource.projectile;
        public bool hasAutoAim = false;
        public Vector3 rotationSpeed = new Vector3(0, 0, 0);
    
        protected Rigidbody2D rb;
        protected int piercing;
    
        protected virtual void Start()
        {
            rb = GetComponent<Rigidbody2D>();
            Weapon.Stats stats = weapon.GetStats();
            if (rb.bodyType == RigidbodyType2D.Dynamic)
            {
                rb.angularVelocity = rotationSpeed.z;
                rb.linearVelocity = transform.right * stats.speed;
            }
    
            //prevent the area from being 0, as it hides the projectile
            float area = stats.area == 0 ? 1 : stats.area;
            transform.localScale = new Vector3(
                area * Mathf.Sign(transform.localScale.x), 
                area * Mathf.Sign(transform.localScale.y), 1
            );
    
            //set how much piercing this object has
            piercing = stats.piercing;
    
            //destroy the projectile after its lifespan expires
            if (stats.lifespan > 0) Destroy(gameObject, stats.lifespan);
    
            //if the projectile is auto-aiming, automatically find a suitable enemy
            if (hasAutoAim) AcquireAutoAimFacing();
        }
    
        //if the projectile is homing , it will automatically find a suitable target to move towards
        public virtual void AcquireAutoAimFacing()
        {
            float aimAngle;
    
            //find all enemis on screen
            EnemyStats[] targets = FindObjectsByType<EnemyStats>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
    
            //select a random enemy (if there is at least 1). otherwise, pick a random angle
            if (targets.Length > 0)
            {
                EnemyStats selectedTarget = targets[Random.Range(0, targets.Length)];
                Vector2 difference = selectedTarget.transform.position - transform.position;
                aimAngle = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
            }
            else
            {
                aimAngle = Random.Range(0f, 360f);
            }
    
            //point the projectile towards where we are aiming at
            transform.rotation = Quaternion.Euler(0, 0, aimAngle);
        }
    
        protected virtual void FixedUpdate()
        {
            //only drive movement ourselves if this is a kinematic
            if (rb.bodyType == RigidbodyType2D.Kinematic)
            {
                Weapon.Stats stats = weapon.GetStats();
                transform.position += transform.right * stats.speed * Time.fixedDeltaTime;
                rb.MovePosition(transform.position);
                transform.Rotate(rotationSpeed * Time.fixedDeltaTime);
            }
        }
    
        protected virtual void OnTriggerEnter2D(Collider2D other)
        {
            EnemyStats es = other.GetComponent<EnemyStats>();
            BreakableProps p = other.GetComponent<BreakableProps>();
    
            //only collide with enemies or breakable stuff
            if (es)
            {
                //if there is an owner and the damage source is set to owner we calculate knockback using the owner instead of projectile
                Vector3 source = damageSource == DamageSource.owner && owner ? owner.transform.position : transform.position;
    
                //deals damage and destroys the projectile
                es.TakeDamage(GetDamage(), source);
    
                Weapon.Stats stats = weapon.GetStats();
                piercing--;
                if (stats.hitEffect)
                {
                    Destroy(Instantiate(stats.hitEffect, transform.position, Quaternion.identity), 5f);
                }
            }
            else if (p)
            {
                p.TakeDamage(GetDamage());
                piercing--;
    
                Weapon.Stats stats = weapon.GetStats();
                if (stats.hitEffect)
                {
                    Destroy(Instantiate(stats.hitEffect, transform.position, Quaternion.identity), 5f);
                }
            }
    
            //destroy this object if it has run out of health from hitting other stuff
            if (piercing <= 0) Destroy(gameObject);
        }
    }
    

    PlayerInventory

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using TMPro;
    
    public class PlayerInventory : MonoBehaviour
    {
        [System.Serializable]
        public class  Slot
        {
            public Item item;
            public Image image;
    
            public void Assign(Item assignedItem)
            {
                item = assignedItem;
                if (item is Weapon)
                {
                    Weapon w = item as Weapon;
                    image.enabled = true;
                    image.sprite = w.data.icon;
                }
                else
                {
                    Passive p = item as Passive;
                    image.enabled = true;
                    image.sprite = p.data.icon;
                }
                Debug.Log(string.Format("Assigned {0} to player.", item.name));
            }
    
            public void Clear()
            {
                item = null;
                image.enabled = false;
                image.sprite = null;
            }
    
            public bool IsEmpty()
            {
                return item == null;
            }
        }
        public List<Slot> weaponSlots = new List<Slot>(6);
        public List<Slot> passiveSlots = new List<Slot>(6);
    
        [System.Serializable]
        public class UpgradeUI
        {
            public TMP_Text upgradeNameDisplay;
            public TMP_Text upgradeDescriptionDisplay;
            public Image upgradeIcon;
            public Button upgradeButton;
        }
    
        [Header("UI Elements")]
        public List<WeaponData> availableWeapons = new List<WeaponData>(); //list of upgrade options for weapons
        public List<PassiveData> availablePassives = new List<PassiveData>(); //list of upgrade options for passive items
        public List<UpgradeUI> upgradeUIOptions = new List<UpgradeUI>(); //list of ui for upgrade options present in the scene
    
        PlayerStats player;
    
        void Start()
        {
            player = GetComponent<PlayerStats>();
        }
    
        //checks if the inventory has an item of a certain type
        public bool Has(ItemData type) { return Get(type); }
    
        public Item Get(ItemData type)
        {
            if (type is WeaponData) return Get(type as WeaponData);
            else if (type is PassiveData) return Get(type as PassiveData);
            return null;
        }
    
        //find a passive of certain type in the inventory
        public Passive Get(PassiveData type)
        {
            foreach (Slot s in passiveSlots)
            {
                Passive p = s.item as Passive;
                if (p.data == type)
                    return p;
            }
            return null;
        }
    
        //find a weapon of certain type in the inventory
        public Weapon Get(WeaponData type)
        {
            foreach (Slot s in weaponSlots)
            {
                Weapon w = s.item as Weapon;
                if (w.data == type)
                    return w;
            }
            return null;
        }
    
        //removes a weapon of a particular type as specified by <data>
        public bool Remove(WeaponData data, bool removeUpgradeAvailability = false)
        {
            //remove this weapon from the upgrade pool
            if (removeUpgradeAvailability) availableWeapons.Remove(data);
    
            for (int i = 0; i < weaponSlots.Count; i++)
            {
                Weapon w = weaponSlots[i].item as Weapon;
                if (w.data == data)
                {
                    weaponSlots[i].Clear();
                    w.OnUnequip();
                    Destroy(w.gameObject);
                    return true;
                }
            }
            return false;
        }
    
        //removes a passive of a particular type, as specified by <data>
        public bool Remove(PassiveData data, bool removeUpgradeAvailability = false)
        {
            //remove this passive from the upgrade pool
            if (removeUpgradeAvailability) availablePassives.Remove(data);
    
            for (int i = 0; i < passiveSlots.Count; i++)   //
            //for (int i = 0; i < weaponSlots.Count; i++)   
                {
                Passive p = passiveSlots[i].item as Passive; //
                //Passive p = weaponSlots[i].item as Passive; 
                if (p.data == data)
                {
                    passiveSlots[i].Clear();   //
                    //weaponSlots[i].Clear();   
                    p.OnUnequip();
                    Destroy(p.gameObject);
                    return true;
                }
            }
            return false;
        }
    
        // if an ItemData is passed, determine what type it is and call the respective overload
        //we also have an optional boolean to remove this item from the upgrade list
    
        public bool Remove(ItemData data, bool removeUpgradeAvailability = false)
        {
            if (data is PassiveData) return Remove(data as PassiveData, removeUpgradeAvailability);
            else if (data is WeaponData) return Remove(data as WeaponData, removeUpgradeAvailability);
            return false;
        }
    
        //finds an empty slot and adds a weapon of a certain type,returns the slot number that the item was put in
    
        public int Add(WeaponData data)
        {
            int slotNum = -1;
    
            //try to find an empty slot
            for (int i = 0; i < weaponSlots.Capacity; i++)
            {
                if (weaponSlots[i].IsEmpty())
                {
                    slotNum = i;
                    break;
                }
            }
    
            //if there is no empty slot, exit
            if (slotNum < 0) return slotNum;
    
            //otherwise create the weapon in the slot and get the type of the weapon we want to spawn
            Type weaponType = Type.GetType(data.behaviour);
    
            if (weaponType != null)
            {
                //spawn the weapon GameObject
                GameObject go = new GameObject(data.baseStats.name + " Controller");
                Weapon spawnedWeapon = (Weapon)go.AddComponent(weaponType);
                spawnedWeapon.Initialise(data);
                spawnedWeapon.transform.SetParent(transform); //set the weapon to be a child of the player
                spawnedWeapon.transform.localPosition = Vector2.zero;
                spawnedWeapon.OnEquip();
    
                //assign the weapon to the slot
                weaponSlots[slotNum].Assign(spawnedWeapon);
    
                //close the level up UI if it is on
                if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
                    GameManager.instance.EndLevelUp();
    
                return slotNum;
            }
            else
            {
                Debug.LogWarning(string.Format(
                    "Invalid weapon type specified for {0}.",
                    data.name
                ));
            }
    
            return -1;
        }
    
        // finds an empty slot and adds a passive of a certain type, returns the slot number that the item was put in
        public int Add(PassiveData data)
        {
            int slotNum = -1;
    
            //try to find an empty slot
            for (int i = 0; i < passiveSlots.Capacity; i++)
            {
                if (passiveSlots[i].IsEmpty())
                {
                    slotNum = i;
                    break;
                }
            }
    
            //if there is no empty slot, exit
            if (slotNum < 0) return slotNum;
    
            //otherwise create the passive in the slot and get the type of the passive we want to spawn
            GameObject go = new GameObject(data.baseStats.name + " Passive");
            Passive p = go.AddComponent<Passive>();
            p.Initialise(data);
            p.transform.SetParent(transform); //set the passive to be a child of the player
            p.transform.localPosition = Vector2.zero;
    
            //assign the passive to the slot
            passiveSlots[slotNum].Assign(p);
    
            if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
            {
                GameManager.instance.EndLevelUp();
            }
            player.RecalculateStats();
    
            return slotNum;
        }
    
        // if we dont know what item is being added, this function will determine that
        public int Add(ItemData data)
        {
            if (data is WeaponData) return Add(data as WeaponData);
            else if (data is PassiveData) return Add(data as PassiveData);
            return -1;
        }
    
        public void LevelUpWeapon(int slotIndex, int upgradeIndex)
        {
            if (weaponSlots.Count > slotIndex)
            {
                Weapon weapon = weaponSlots[slotIndex].item as Weapon;
    
                //don't level up the weapon if it is already at max level
                if (!weapon.DoLevelUp())
                {
                    Debug.LogWarning(string.Format(
                        "Failed to level up {0}.",
                        weapon.name
                    ));
                    return;
                }
            }
    
            if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
            {
                GameManager.instance.EndLevelUp();
            }
        }
    
        public void LevelUpPassiveItem(int slotIndex, int upgradeIndex)
        {
            if (passiveSlots.Count > slotIndex)
            {
                Passive p = passiveSlots[slotIndex].item as Passive;
                if (!p.DoLevelUp())
                {
                    Debug.LogWarning(string.Format(
                        "Failed to level up {0}.",
                        p.name
                    ));
                    return;
                }
            }
    
            if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
            {
                GameManager.instance.EndLevelUp();
            }
            player.RecalculateStats();
        }
    
        //determine what upgrade options should appear
        void ApplyUpgradeOptions()
        {
            //make a duplicate of the available weapon / passive upgrade lists so we can iterate through them in the function
            List<WeaponData> availableWeaponUpgrades = new List<WeaponData>(availableWeapons);
            List<PassiveData> availablePassiveItemUpgrades = new List<PassiveData>(availablePassives);
    
            //iterate through each slot in the upgrade UI
            foreach (UpgradeUI upgradeOption in upgradeUIOptions)
            {
                //if there are no more available upgrades, then we abort
                if (availableWeaponUpgrades.Count == 0 && availablePassiveItemUpgrades.Count == 0)
                    return;
    
                //determine whether this upgrade should be for passive or active weapons
                int upgradeType;
                if (availableWeaponUpgrades.Count == 0)
                {
                    upgradeType = 2;
                }
                else if (availablePassiveItemUpgrades.Count == 0)
                {
                    upgradeType = 1;
                }
                else
                {
                    //random generates a number between 1 and 2
                    upgradeType = UnityEngine.Random.Range(1, 3);
                }
    
                //generates an active weapon upgrade
                if (upgradeType == 1)
                {
                    //pick a weapon upgrade, then remove it so that we don't get it twice
                    WeaponData chosenWeaponUpgrade = availableWeaponUpgrades[UnityEngine.Random.Range(0, availableWeaponUpgrades.Count)];
                    availableWeaponUpgrades.Remove(chosenWeaponUpgrade);
    
                    //ensure that the selected weapon data is valid
                    if (chosenWeaponUpgrade != null)
                    {
                        //turns on the UI slot
                        EnableUpgradeUI(upgradeOption);
    
                        //loops through all our existing weapons. if we find a match, we will
                        //hook an event listener to the button that will level up the weapon
                        //when this upgrade option is clicked
                        bool isLevelUp = false;
                        for (int i = 0; i < weaponSlots.Count; i++)
                        {
                            Weapon w = weaponSlots[i].item as Weapon;
                            if (w != null && w.data == chosenWeaponUpgrade)
                            {
                                //if the weapon is already at the max level, do not allow upgrade
                                if (chosenWeaponUpgrade.maxLevel <= w.currentLevel)
                                {
                                    //DisableUpgradeUI(upgradeOption);
                                    isLevelUp = false;
                                    break;
                                }
    
                                // set the event listener, item and level description to be that opf the next level
                                upgradeOption.upgradeButton.onClick.AddListener(() => LevelUpWeapon(i, i)); //apply button functionality
                                Weapon.Stats nextLevel = chosenWeaponUpgrade.GetLevelData(w.currentLevel + 1);
                                upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description;
                                upgradeOption.upgradeNameDisplay.text = nextLevel.name;
                                upgradeOption.upgradeIcon.sprite = chosenWeaponUpgrade.icon;
                                isLevelUp = true;
                                break;
                            }
                        }
    
                        //if the code gets here, it means that we will be adding a new weapon , intead of upgrading an existing one
                        if (!isLevelUp)
                        {
                            upgradeOption.upgradeButton.onClick.AddListener(() => Add(chosenWeaponUpgrade)); //apply button functionality
                            upgradeOption.upgradeDescriptionDisplay.text = chosenWeaponUpgrade.baseStats.description;
                            upgradeOption.upgradeNameDisplay.text = chosenWeaponUpgrade.baseStats.name;
                            upgradeOption.upgradeIcon.sprite = chosenWeaponUpgrade.icon;
                        }
                    }
                }
                else if (upgradeType == 2)
                {
                    //NOTE: we have to recode this system as right now it disables an upgrade slot if
                    //we hit a weapon that has already reached its max level
                    PassiveData chosenPassiveUpgrade = availablePassiveItemUpgrades[UnityEngine.Random.Range(0, availablePassiveItemUpgrades.Count)];
                    availablePassiveItemUpgrades.Remove(chosenPassiveUpgrade);
    
                    if (chosenPassiveUpgrade != null)
                    {
                        //turns on the UI slot
                        EnableUpgradeUI(upgradeOption);
    
                        //loops through all our existing passives. if we find a match, we will
                        //hook an event listener to the button that will level up the passive
                        //when this upgrade option is clicked
                        bool isLevelUp = false;
                        for (int i = 0; i < passiveSlots.Count; i++)
                        {
                            Passive p = passiveSlots[i].item as Passive;
                            if (p != null && p.data == chosenPassiveUpgrade)
                            {
                                //if the passive is already at the max level, do not allow upgrade
                                if (chosenPassiveUpgrade.maxLevel <= p.currentLevel)
                                {
                                    //DisableUpgradeUI(upgradeOption);
                                    isLevelUp = false;
                                    break;
                                }
    
                                // set the event listener, item and level description to be that of the next level
                                upgradeOption.upgradeButton.onClick.AddListener(() => LevelUpPassiveItem(i, i)); //apply button functionality
                                Passive.Modifier nextLevel = chosenPassiveUpgrade.GetLevelData(p.currentLevel + 1);
                                upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description;
                                upgradeOption.upgradeNameDisplay.text = nextLevel.name;
                                upgradeOption.upgradeIcon.sprite = chosenPassiveUpgrade.icon;
                                isLevelUp = true;
                                break;
                            }
                        }
    
                        if (!isLevelUp)  // spawn a new passive item
                        {
                            upgradeOption.upgradeButton.onClick.AddListener(() => Add(chosenPassiveUpgrade)); //apply button functionality
                            Passive.Modifier nextLevel = chosenPassiveUpgrade.baseStats;
                            upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description; //apply initial description
                            upgradeOption.upgradeNameDisplay.text = nextLevel.name; //apply initial name
                            upgradeOption.upgradeIcon.sprite = chosenPassiveUpgrade.icon;
                        }
                    }
                }
            }
        }
    
        void RemoveUpgradeOptions()
        {
            foreach (UpgradeUI upgradeOption in upgradeUIOptions)
            {
                upgradeOption.upgradeButton.onClick.RemoveAllListeners();
                DisableUpgradeUI(upgradeOption); //call the disableupgradeui method here to disable all UI options before applying upgrades to them
            }
        }
    
        public void RemoveAndApplyUpgrades()
        {
            RemoveUpgradeOptions();
            ApplyUpgradeOptions();
        }
    
        void DisableUpgradeUI(UpgradeUI ui)
        {
            ui.upgradeNameDisplay.transform.parent.gameObject.SetActive(false);
        }
    
        void EnableUpgradeUI(UpgradeUI ui)
        {
            ui.upgradeNameDisplay.transform.parent.gameObject.SetActive(true);
        }
    }
    

    Item

    using UnityEngine;
    using UnityEngine.UIElements;
    using System.Collections.Generic;
    
    /// <summary>
    /// base class for passive and weapon classes . it is intended to handle evolutions
    /// </summary>
    public abstract class Item : MonoBehaviour
    {
        public int currentLevel = 1, maxLevel = 1;
        protected ItemData.Evolution[] evolutionData;
        protected PlayerInventory inventory;
        protected PlayerStats owner;
    
        public virtual void Initialise(ItemData data)
        {
            maxLevel = data.maxLevel;
    
            //store the evolution data as we have to track whether all the catalysts are in the inventory so we can evolve
            evolutionData = data.evolutionData;
    
            //we have to find a better way to reference the player inventory in future, as this is inefficient
            inventory = FindFirstObjectByType<PlayerInventory>();
            owner = FindFirstObjectByType<PlayerStats>();
        }
    
        //call this function to get all the evolutions that the weapon can evolve to
        public virtual ItemData.Evolution[] CanEvolve()
        {
            List<ItemData.Evolution> possibleEvolutions = new List<ItemData.Evolution>();
    
            //check each listed evolution and whether it is in the inventory
            foreach (ItemData.Evolution e in evolutionData)
            {
                if (CanEvolve(e)) possibleEvolutions.Add(e);
            }
    
            return possibleEvolutions.ToArray();
        }
    
        // checks if a specific evolution is possible
    
        public virtual bool CanEvolve(ItemData.Evolution evolution, int levelUpAmount = 1)
        {
            //cannot evolve if the item hasn't reached the level to evolve
            if (evolution.evolutionLevel > currentLevel + levelUpAmount)
            {
                Debug.LogWarning(string.Format("Evolution failed. Current level {0}, evolution level {1}", currentLevel, evolution.evolutionLevel));
                return false;
            }
    
            //checks to see if all the catalysts are in the inventory
            foreach (ItemData.Evolution.Config c in evolution.catalysts)
            {
                Item item = inventory.Get(c.itemType);
                if (!item || item.currentLevel < c.level)
                {
                    Debug.LogWarning(string.Format("Evolution failed. Missing {0}", c.itemType.name));
                    return false;
                }
            }
    
            return true;
        }
    
        //attemptEvolution will spawn a new weapon for the character, and remove all the weapons that are supposed to be consumed
    
        public virtual bool AttemptEvolution(ItemData.Evolution evolutionData, int levelUpAmount = 1)
        {
            if (!CanEvolve(evolutionData, levelUpAmount))
                return false;
    
            //should we consume passives / weapons?
            bool consumePassives = (evolutionData.consumes & ItemData.Evolution.Consumption.passives) > 0;
            bool consumeWeapons = (evolutionData.consumes & ItemData.Evolution.Consumption.weapons) > 0;
    
            //loop through all the catalysts and check if we should consume thme
            foreach (ItemData.Evolution.Config c in evolutionData.catalysts)
            {
                if (c.itemType is PassiveData && consumePassives) inventory.Remove(c.itemType, true);
                if (c.itemType is WeaponData && consumeWeapons) inventory.Remove(c.itemType, true);
            }
    
            //should we consume ourselves as well?
            if (this is Passive && consumePassives) inventory.Remove((this as Passive).data, true);
            else if (this is Weapon && consumeWeapons) inventory.Remove((this as Weapon).data, true);
    
            //add the new weapon onto our inventory
            inventory.Add(evolutionData.outcome.itemType);
    
            return true;
        }
    
        public virtual bool CanLevelUp()
        {
            return currentLevel <= maxLevel;
        }
    
        //whenever an item levels up, attempt to make it evolve
        public virtual bool DoLevelUp()
        {
            //tries to evolve into every listed evolution of this weapon,
            //if the weapon's evolution condition is levelling up
            foreach (ItemData.Evolution e in evolutionData)
            {
                if (e.condition == ItemData.Evolution.Condition.auto)
                    AttemptEvolution(e);
            }
            return true;
        }
    
        //what effects you receive on equipping an item
        public virtual void OnEquip() { }
    
        //what effects are removed on unequipping an item
        public virtual void OnUnequip() { }
    
    }
    

    XenoVault(TreasureChest)

    using UnityEngine;
    
    public class XenoVault : MonoBehaviour
    {
    
        private void OnTriggerEnter2D(Collider2D col)
        {
            PlayerInventory p = col.GetComponent<PlayerInventory>();
            if (p)
            {
                bool randomBool = Random.Range(0, 2) == 0;
    
                OpenXenoVault(p, randomBool);
    
                Destroy(gameObject);
            }
        }
    
        public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
        {
            //loop through every weapon to check whether it can evolve
            foreach (PlayerInventory.Slot s in inventory.weaponSlots)
            {
                Weapon w = s.item as Weapon;
                if (w.data.evolutionData == null) continue; //ignore weapon if it cannot evolve
    
                //loop through every possible evolution of the weapon
                foreach (ItemData.Evolution e in w.data.evolutionData)
                {
                    //only attempt to evolve weapons via treasure chest evolution
                    if (e.condition == ItemData.Evolution.Condition.treasureChest)
                    {
                        bool attempt = w.AttemptEvolution(e, 0);
                        if (attempt) return; //if evolution succeds, stop
                    }
                }
            }
    
        }
    }
    
    #18693
    Alp Apustaja
    Level 10
    AI Assistant
    (BETA)
    Helpful?
    Up
    1
    ::

    Based on the code and error messages, there are a few potential issues:

    1. In the XenoVault script, there’s a null check missing before accessing w.data.evolutionData. This could cause a NullReferenceException if the weapon slot is empty.

    Modify the OpenXenoVault method to:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
        //loop through every weapon to check whether it can evolve
        foreach (PlayerInventory.Slot s in inventory.weaponSlots)
        {
            Weapon w = s.item as Weapon;
            if (w == null || w.data == null || w.data.evolutionData == null) continue; 
    
            //loop through every possible evolution of the weapon
            foreach (ItemData.Evolution e in w.data.evolutionData)
            {
                //only attempt to evolve weapons via treasure chest evolution
                if (e.condition == ItemData.Evolution.Condition.treasureChest)
                {
                    bool attempt = w.AttemptEvolution(e, 0);
                    if (attempt) return; //if evolution succeds, stop
                }
            }
        }
    }
    1. For the Projectile errors, ensure that the weapon variable in the WeaponEffect base class is properly initialized before the Projectile’s Start and FixedUpdate methods are called.

    Would you like me to elaborate on any of these points or provide more specific debugging advice?

    has upvoted this post.
    #18695
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    There is a known issue with the Treasure Chest pickup for Part 15, as it fails to work if you do not have anything that can be evolved in your inventory.

    @Alp’s suggestion above should help you remove the null reference.

    #18698
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    ive tried to change the method on the xenovault script but the error persists it’s worth mentioning that i had a test evolution of knife + aura since the beginning and im getting this error. do I need to add the evolution scriptable object to the Available Weapons List on the PlayerInventory as well? i’ve tried this and the evolution has appeared in the level up screen so i guess not. should i add it to any object?

    also, can you elaborate on the projectiles part @alp? couldn’t figure what to change in my script

    the weaponeffect script is like this:

    using UnityEngine;
    
    public abstract class WeaponEffect : MonoBehaviour
    {
        [HideInInspector] public PlayerStats owner;
        [HideInInspector] public Weapon weapon;
    
        public float GetDamage()
        {
            return weapon.GetDamage();
        }
    }
    #18699
    Alp Apustaja
    Level 10
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    For the XenoVault issue, the problem might be that the weapon or weapon.data is null when trying to access the evolution data. Let’s modify the script to add more robust null checking:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
        //loop through every weapon to check whether it can evolve
        foreach (PlayerInventory.Slot s in inventory.weaponSlots)
        {
            if (s == null || s.item == null) continue;
    
            Weapon w = s.item as Weapon;
            if (w == null || w.data == null || w.data.evolutionData == null) continue; 
    
            //loop through every possible evolution of the weapon
            foreach (ItemData.Evolution e in w.data.evolutionData)
            {
                //only attempt to evolve weapons via treasure chest evolution
                if (e.condition == ItemData.Evolution.Condition.treasureChest)
                {
                    bool attempt = w.AttemptEvolution(e, 0);
                    if (attempt) return; //if evolution succeds, stop
                }
            }
        }
    }

    For the Projectile errors, the issue is likely that the weapon and owner variables in the WeaponEffect base class are not being set before the Projectile’s Start and FixedUpdate methods are called.

    You’ll need to modify the script that instantiates the Projectile to ensure these variables are set before the Projectile starts. For example, in the Weapon script where you create the Projectile:

    GameObject projectileObject = Instantiate(projectilePrefab, spawnPosition, Quaternion.identity);
    Projectile projectile = projectileObject.GetComponent();
    projectile.weapon = this; // Set the weapon reference
    projectile.owner = playerStats; // Set the owner reference

    Could you confirm:

    1. Are you setting the weapon and owner references when instantiating Projectiles?
    2. Can you show me the code where Projectiles are created?
    #18702
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    ive tried the updates but the chest is still showing this error message if i try to pick it before i have the right requirements for an evolution and it does not destroy the chest. when i have the requirements it does work as intended

    about projectile part, this is the script where the projectiles are instantiated (the weapon and owner are set there):

    using UnityEngine;
    
    public class ProjectileWeapon : Weapon
    {
        protected float currentAttackInterval;
        protected int currentAttackCount;
    
        protected override void Update()
        {
            base.Update();
    
            if (currentAttackInterval > 0)
            {
                currentAttackInterval -= Time.deltaTime;
                if (currentAttackInterval <= 0) Attack(currentAttackCount);
            }
    
        }
    
        public override bool CanAttack()
        {
            if (currentAttackCount > 0) return true;
            return base.CanAttack();
        }
    
        protected override bool Attack(int attackCount = 1)
        {
            if (!currentStats.projectilePrefab)
            {
                Debug.LogWarning(string.Format("No projectile prefab assigned for {0}", name));
                currentCooldown = data.baseStats.cooldown;
                return false;
            }
    
            //can we attack?
            if (!CanAttack()) return false;
    
            //otherwise calculate the angle and offset of our spammed projectile
            float spawnAngle = GetSpawnAngle();
    
            //and spawn a copy of the projectile
            Projectile prefab = Instantiate(
                currentStats.projectilePrefab,
                owner.transform.position + (Vector3)GetSpawnOffset(spawnAngle),
                Quaternion.Euler(0, 0, spawnAngle)
            );
    
            prefab.weapon = this;
            prefab.owner = owner;
    
            //reset the cooldown only if this attack was triggered by cooldown
            if (currentCooldown <= 0)
                currentCooldown += currentStats.cooldown;
    
            attackCount--;
    
            // do we perform another attack?
            if (attackCount > 0)
            {
                currentAttackCount = attackCount;
                currentAttackInterval = data.baseStats.projectileInterval;
            }
    
            return true;
    
        }
    
        //gets which direction the projectile should face when spawning
    
        protected virtual float GetSpawnAngle()
        {
            return Mathf.Atan2(movement.lastMovedVector.y, movement.lastMovedVector.x) * Mathf.Rad2Deg;
        }
    
        // generates a random point to spawn the projectile on and rotates the facing of the point by spawnAngle
    
        protected virtual Vector2 GetSpawnOffset(float spawnAngle = 0)
        {
            return Quaternion.Euler(0, 0, spawnAngle) * new Vector2(
                Random.Range(currentStats.spawnVariance.xMin, currentStats.spawnVariance.xMax),
                Random.Range(currentStats.spawnVariance.yMin, currentStats.spawnVariance.yMax)
            );
        }
    }
    #18703
    Alp Apustaja
    Level 10
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    For the XenoVault issue, let’s modify the script to handle cases where no evolution is possible:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
       bool evolutionAttempted = false;
    
       //loop through every weapon to check whether it can evolve
       foreach (PlayerInventory.Slot s in inventory.weaponSlots)
       {
           if (s == null || s.item == null) continue;
    
           Weapon w = s.item as Weapon;
           if (w == null || w.data == null || w.data.evolutionData == null) continue; 
    
           //loop through every possible evolution of the weapon
           foreach (ItemData.Evolution e in w.data.evolutionData)
           {
               //only attempt to evolve weapons via treasure chest evolution
               if (e.condition == ItemData.Evolution.Condition.treasureChest)
               {
                   bool attempt = w.AttemptEvolution(e, 0);
                   if (attempt)
                   {
                       evolutionAttempted = true;
                       break; //if evolution succeeds, stop
                   }
               }
           }
    
           if (evolutionAttempted) break;
       }
    
       // Always destroy the chest, regardless of whether evolution was successful
       Destroy(gameObject);
    }

    The key changes are:

    1. Added a flag to track if an evolution was attempted
    2. Always destroy the gameObject at the end of the method
    3. Break out of loops once an evolution is successful

    This ensures that:

    • The chest is always destroyed
    • The first possible evolution is attempted
    • No further attempts are made after a successful evolution

    Regarding the Projectile script, the code you’ve shown looks correct for setting the weapon and owner references.

    Could you provide more context about the specific Null Reference Exception you’re seeing in the Projectile script? Are you seeing any specific line numbers or additional error details?

    #18711
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    To add on to the code that Alp provided, the highlighted portion below is what is important:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
        //loop through every weapon to check whether it can evolve
        foreach (PlayerInventory.Slot s in inventory.weaponSlots)
        {
            if (s == null || s.item == null) continue;
    
            Weapon w = s.item as Weapon;
            if (w == null || w.data == null || w.data.evolutionData == null) continue; 
    
            //loop through every possible evolution of the weapon
            foreach (ItemData.Evolution e in w.data.evolutionData)
            {
                //only attempt to evolve weapons via treasure chest evolution
                if (e.condition == ItemData.Evolution.Condition.treasureChest)
                {
                    bool attempt = w.AttemptEvolution(e, 0);
                    if (attempt) return; //if evolution succeds, stop
                }
            }
        }
    }

    The reason for that is that NullReferenceException stops execution of your code when they happen. This means that in your XenoVault.OnTriggerEnter2D() function:

    private void OnTriggerEnter2D(Collider2D col)
    {
        PlayerInventory p = col.GetComponent<PlayerInventory>();
        if (p)
        {
            bool randomBool = Random.Range(0, 2) == 0;
    
            OpenXenoVault(p, randomBool);
    
            Destroy(gameObject);
        }
    }

    If OpenXenoVault() causes a NullReferenceException, the Destroy() function doesn’t run because the NullReferenceException blocks the rest of your function from occurring.

    As for the NullReferenceException with your projectiles, your ProjectileWeapon is correct. The important part is here:

        // And spawn a copy of the projectile.
        Projectile prefab = Instantiate(
            currentStats.projectilePrefab,
            owner.transform.position + (Vector3)GetSpawnOffset(spawnAngle),
            Quaternion.Euler(0, 0, spawnAngle)
        );
    
        prefab.weapon = this;
        prefab.owner = owner;

    If the error only fires once and it doesn’t cause further problems, you can delay the function for 1 frame using Invoke() so it only fires when the variable is filled:

    protected virtual void Start()
    {
        if(!weapon) {
            Invoke("Start", 0);
            return;
        }
        rb = GetComponent<Rigidbody2D>();
        Weapon.Stats stats = weapon.GetStats();
        if (rb.bodyType == RigidbodyType2D.Dynamic)
        {
            rb.angularVelocity = rotationSpeed.z;
            rb.linearVelocity = transform.right * stats.speed;
        }
    
        //prevent the area from being 0, as it hides the projectile
        float area = stats.area == 0 ? 1 : stats.area;
        transform.localScale = new Vector3(
            area * Mathf.Sign(transform.localScale.x), 
            area * Mathf.Sign(transform.localScale.y), 1
        );
    
        //set how much piercing this object has
        piercing = stats.piercing;
    
        //destroy the projectile after its lifespan expires
        if (stats.lifespan > 0) Destroy(gameObject, stats.lifespan);
    
        //if the projectile is auto-aiming, automatically find a suitable enemy
        if (hasAutoAim) AcquireAutoAimFacing();
    }

    For FixedUpdate(), prevent the function from firing if we are unable to retrieve the stats.

    protected virtual void FixedUpdate()
    {
        //only drive movement ourselves if this is a kinematic
        if (rb.bodyType == RigidbodyType2D.Kinematic)
        {
            if(!weapon) return;
            Weapon.Stats stats = weapon.GetStats();
            transform.position += transform.right * stats.speed * Time.fixedDeltaTime;
            rb.MovePosition(transform.position);
            transform.Rotate(rotationSpeed * Time.fixedDeltaTime);
        }
    }

    Let me know if the above fixes your issues. It is very good that you are striving to fix your NulLReferenceExceptions. People tend to ignore them and it causes massive problems (i.e. lots of bugs) in the long run.

    #18718
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    Hi Terence! thank you for the feedback, after checking everything in the answers, the projectile part is running smooth now but the chest still gives this error only if i can’t evolve a weapon at that point, so it wont get destroyed and give this null reference. as soon as i meet the requirements to evolve the weapon it works as intended.

    NullReferenceException: Object reference not set to an instance of an object PlayerInventory.Get (WeaponData type) (at Assets/Scripts/Player/PlayerInventory.cs:97) PlayerInventory.Get (ItemData type) (at Assets/Scripts/Player/PlayerInventory.cs:74) Item.CanEvolve (ItemData+Evolution evolution, System.Int32 levelUpAmount) (at Assets/Scripts/Passive Items/Item.cs:55) Item.AttemptEvolution (ItemData+Evolution evolutionData, System.Int32 levelUpAmount) (at Assets/Scripts/Passive Items/Item.cs:70) XenoVault.OpenXenoVault (PlayerInventory inventory, System.Boolean isHigherTier) (at Assets/Scripts/Pickups/XenoVault.cs:35) XenoVault.OnTriggerEnter2D (UnityEngine.Collider2D col) (at Assets/Scripts/Pickups/XenoVault.cs:13)

    here’s how my script looks like now:

    using UnityEngine;
    
    public class XenoVault : MonoBehaviour
    {
    
        private void OnTriggerEnter2D(Collider2D col)
        {
            PlayerInventory p = col.GetComponent<PlayerInventory>();
            if (p)
            {
                bool randomBool = Random.Range(0, 2) == 0;
    
                OpenXenoVault(p, randomBool);
    
                Destroy(gameObject);
            }
        }
    
        public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
        {
            //loop through every weapon to check whether it can evolve
            foreach (PlayerInventory.Slot s in inventory.weaponSlots)
            {
                if (s == null || s.item == null) continue;
    
                Weapon w = s.item as Weapon;
                if (w == null || w.data == null || w.data.evolutionData == null) continue;  //ignore weapon if it cannot evolve
    
                //loop through every possible evolution of the weapon
                foreach (ItemData.Evolution e in w.data.evolutionData)
                {
                    //only attempt to evolve weapons via treasure chest evolution
                    if (e.condition == ItemData.Evolution.Condition.treasureChest)
                    {
                        bool attempt = w.AttemptEvolution(e, 0);
                        if (attempt) return; //if evolution succeds, stop
                    }
                }
            }
    
        }
    }

    now i have noticed one more bug that im not sure if its supposed to be there because you dont reach that part of playtesting in the video. when im playing the game, the items displays on the screen works fine, but when i finish that run it wont show the items ive used in that run. all slots are empty.

    sorry for bothering with all this bugs and thank you so much for your patience! im trying to fix everything before i move to the next part

    #18719
    Alp Apustaja
    Level 10
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    For the XenoVault chest issue, modify the script to handle potential null references more robustly:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
        bool evolutionAttempted = false;
    
        // Null check for inventory and weaponSlots
        if (inventory?.weaponSlots == null) 
        {
            Debug.LogWarning("Inventory or weapon slots are null");
            return;
        }
    
        foreach (PlayerInventory.Slot s in inventory.weaponSlots)
        {
            if (s?.item == null) continue;
    
            Weapon w = s.item as Weapon;
            if (w?.data?.evolutionData == null) continue;
    
            foreach (ItemData.Evolution e in w.data.evolutionData)
            {
                if (e.condition == ItemData.Evolution.Condition.treasureChest)
                {
                    try 
                    {
                        bool attempt = w.AttemptEvolution(e, 0);
                        if (attempt)
                        {
                            evolutionAttempted = true;
                            break;
                        }
                    }
                    catch (System.Exception ex)
                    {
                        Debug.LogError($"Evolution attempt failed: {ex.Message}");
                    }
                }
            }
    
            if (evolutionAttempted) break;
        }
    }

    Regarding the second issue about items not showing after a run, this sounds like a potential problem with game state persistence or reset. Could you provide:

    1. The script responsible for ending/resetting the run
    2. How the inventory UI is being populated
    3. Any code related to run completion or scene transitions

    Would you like me to elaborate on any of these points or provide more specific debugging advice?

    #18720
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    thank you Alp! now i can collect the chest even if i can’t evolve the weapon. i just get this error now :

    Evolution attempt failed: Object reference not set to an instance of an object UnityEngine.Debug:LogError (object) XenoVault:OpenXenoVault (PlayerInventory,bool) (at Assets/Scripts/Pickups/XenoVault.cs:52) XenoVault:OnTriggerEnter2D (UnityEngine.Collider2D) (at Assets/Scripts/Pickups/XenoVault.cs:13)

    about the UI bug, this is the game manager script:

    using UnityEngine;
    using UnityEngine.UI;
    using TMPro;
    using System.Collections;
    using System.Collections.Generic;
    
    public class GameManager : MonoBehaviour
    {
        public static GameManager instance;
    
        //define the different states of the game
        public enum GameState
        {
            Gameplay,
            Paused,
            GameOver,
            LevelUp
        }
    
        //store the current state of the game
        public GameState currentState;
        //store the previous state of the game before it was paused
        public GameState previousState;
    
        [Header("Damage Text Settings")]
        public Canvas damageTextCanvas;
        public float textFontSize = 20;
        public TMP_FontAsset textFont;
        public Camera referenceCamera;
    
        [Header("Screens")]
        public GameObject pauseScreen;
        public GameObject resultsScreen;
        public GameObject levelUpScreen;
    
        [Header("Current Stat Displays")]
        public TextMeshProUGUI currentHealthDisplay;
        public TextMeshProUGUI currentRecoveryDisplay;
        public TextMeshProUGUI currentMoveSpeedDisplay;
        public TextMeshProUGUI currentMightDisplay;
        public TextMeshProUGUI currentProjectileSpeedDisplay;
        public TextMeshProUGUI currentMagnetDisplay;
    
        [Header("Current Stat Displays")]
        public Image chosenCharacterImage;
        public TextMeshProUGUI chosenCharacterName;
        public TextMeshProUGUI levelReachedDisplay;
        public TextMeshProUGUI timeSurvivedDisplay;
        public List<Image> chosenWeaponsUI = new List<Image>(6);
        public List<Image> chosenPassiveItemsUI = new List<Image>(6);
    
        [Header("Stopwatch")]
        public float timeLimit; //time limit in seconds
        float stopwatchTime; //time elapsed since stopwatch started
        public TextMeshProUGUI stopwatchDisplay;
    
        public bool isGameOver = false;
    
        //flag to check if the player is chooseing their upgrades
        public bool choosingUpgrade;
    
        //reference to the player's game object
        public GameObject playerObject;
    
        void Awake()
        {
            if(instance == null)
            {
                instance = this;
            }
            else
            {
                Debug.LogWarning("Extra " + this + " deleted");
            }
    
                DisableScreens();
        }
    
        void Update()
        {
            //define the behaviour for each state
            switch (currentState)
            {
                case GameState.Gameplay:
                    CheckForPauseAndResume();
                    UpdateStopwatch();
                    break;
    
                case GameState.Paused:
                    CheckForPauseAndResume();
                    break;
    
                case GameState.GameOver:
                    if(!isGameOver)
                    {
                        isGameOver = true;
                        Time.timeScale = 0f; //stops the game entirely
                        Debug.Log("Game Over");
                        DisplayResults();
                    }
                    break;
                case GameState.LevelUp:
                    if(!choosingUpgrade)
                    {
                        choosingUpgrade = true;
                        Time.timeScale = 0f; // pause the game for now
                        Debug.Log("Upgrades shown");
                        levelUpScreen.SetActive(true);
                    }
                    break;
    
                default:
                    Debug.LogWarning("STATE DOES NOT EXIST");
                    break;
            }
        }
    
        IEnumerator GenerateFloatingTextCoroutine(string text, Transform target, float duration = 1f, float speed = 50f)
        {
            //start generating the floating text
            GameObject textObj = new GameObject("Damage Floating Text");
            RectTransform rect = textObj.AddComponent<RectTransform>();
            TextMeshProUGUI tmPro = textObj.AddComponent<TextMeshProUGUI>();
            tmPro.text = text;
            tmPro.horizontalAlignment = HorizontalAlignmentOptions.Center;
            tmPro.verticalAlignment = VerticalAlignmentOptions.Middle;
            tmPro.fontSize = textFontSize;
            if (textFont) tmPro.font = textFont;
            rect.position = referenceCamera.WorldToScreenPoint(target.position);
    
            Destroy(textObj, duration);
    
            //parent generated text to the canvas
            textObj.transform.SetParent(instance.damageTextCanvas.transform);
            textObj.transform.SetAsFirstSibling();
    
            //pan the text upwards and fade it over time
            WaitForEndOfFrame w = new WaitForEndOfFrame();
            float t = 0;
            float yOffset = 0;
            while (t < duration)
            {
                tmPro.color = new Color(tmPro.color.r, tmPro.color.g, tmPro.color.b, 1 - t / duration);
    
                if (target)
                {
                    yOffset += speed * Time.deltaTime;
                    if (rect != null) // Add this null check
                    {
                        rect.position = referenceCamera.WorldToScreenPoint(target.position + new Vector3(0, yOffset));
                    }
                }
                else
                {
                    // If target is dead, just pan up where the text is at.
                    if (rect != null) // Add this null check
                    {
                        rect.position += new Vector3(0, speed * Time.deltaTime, 0);
                    }
                }
                yield return w;
                t += Time.deltaTime;
            }
    
    
        }
    
        public static void GenerateFloatingText(string text, Transform target, float duration = 1f, float speed = 1f)
        {
            //if the canvas is not set, return the function so we dont generate any floating text
            if (!instance.damageTextCanvas) return;
    
            //find a relevant camera that we can use to convert the world position to a screen position
            if (!instance.referenceCamera) instance.referenceCamera = Camera.main;
    
            instance.StartCoroutine(instance.GenerateFloatingTextCoroutine(text, target, duration, speed));
    
        }
    
        //define the method to change the state of the game
        public void ChangeState(GameState newState)
        {
            currentState = newState;
        }
    
        public void PauseGame()
        {
            if(currentState != GameState.Paused)
            {
                previousState = currentState;
                ChangeState(GameState.Paused);
                Time.timeScale = 0f; //stop the game
                pauseScreen.SetActive(true);
                Debug.Log("Game is paused");
            }
        }
    
        public void ResumeGame()
        {
            if(currentState == GameState.Paused)
            {
                ChangeState(previousState);
                Time.timeScale = 1f; //resume the game
                pauseScreen.SetActive(false);
                Debug.Log("Game is resumed");
            }
        }
    
        //define the method to check for pause and resume input
        void CheckForPauseAndResume()
        {
            if(Input.GetKeyDown(KeyCode.Escape))
            {
                if(currentState == GameState.Paused)
                {
                    ResumeGame();
                }
                else
                {
                    PauseGame();
                }
    
            }
        }
    
        void DisableScreens()
        {
            pauseScreen.SetActive(false);
            resultsScreen.SetActive(false);
            levelUpScreen.SetActive(false);
        }
    
        public void GameOver()
        {
    
            timeSurvivedDisplay.text = stopwatchDisplay.text;
            ChangeState(GameState.GameOver);
        }
    
        void DisplayResults()
        {
            resultsScreen.SetActive(true);
        }
    
        public void AssignChosenCharacterUI(CharacterData chosenCharacterData)
        {
            chosenCharacterImage.sprite = chosenCharacterData.Icon;
            chosenCharacterName.text = chosenCharacterData.Name;
        }
    
        public void AssignLevelReachedUI(int levelReachedData)
        {
            levelReachedDisplay.text = levelReachedData.ToString();
        }
    
        public void AssignChosenWeaponsAndPassiveItemsUI(List<Image> chosenWeaponsData, List<Image> chosenPassiveItemsData)
        {
            if (chosenWeaponsData.Count != chosenWeaponsUI.Count || chosenPassiveItemsData.Count != chosenPassiveItemsUI.Count)
            {
                Debug.Log("Chosen Weapons and Passive Items data lists have different lengths ");
                return;
            }
    
            //assign the chosen weapons data to choseWeaponsUI
            for (int i = 0; i < chosenWeaponsUI.Count; i++)
            {
                //check that the sprite of the corresponding element in chosenWeaponsData is not null
                if(chosenWeaponsData[i].sprite)
                {
                    //enable the corersponding element in chosenWeaponUi and set its sprite to the corresponding sprite in choseWeaponsData
                    chosenWeaponsUI[i].enabled = true;
                    chosenWeaponsUI[i].sprite = chosenWeaponsData[i].sprite;
                }
                else
                {
                    //if sprite is null, disable the corresponding element in choseWeaponsUI
                    chosenWeaponsUI[i].enabled = false;
                }
            }
    
            //assign the chosen weapons data to chosenPassiveItemsUI
            for (int i = 0; i < chosenPassiveItemsUI.Count; i++)
            {
                //check that the sprite of the corresponding element in chosenPassiveItemsData is not null
                if (chosenPassiveItemsData[i].sprite)
                {
                    //enable the corersponding element in chosenPassiveItemsUI and set its sprite to the corresponding sprite in chosenPassiveItemsData
                    chosenPassiveItemsUI[i].enabled = true;
                    chosenPassiveItemsUI[i].sprite = chosenPassiveItemsData[i].sprite;
                }
                else
                {
                    //if sprite is null, disable the corresponding element in chosenPassiveItemsUI
                    chosenPassiveItemsUI[i].enabled = false;
                }
            }
        }
    
        void UpdateStopwatch()
        {
            stopwatchTime += Time.deltaTime;
    
            UpdateStopwatchDisplay();
    
            if(stopwatchTime >= timeLimit)
            {
                playerObject.SendMessage("Kill");
            }
        }
    
        void UpdateStopwatchDisplay()
        {
            //calculate the number of minutes and seconds that have elapsed
            int minutes = Mathf.FloorToInt(stopwatchTime / 60);
            int seconds = Mathf.FloorToInt(stopwatchTime % 60);
    
            //update stopwatch text to display elapsed time
            stopwatchDisplay.text = string.Format("{0:00}:{1:00}", minutes, seconds);
        }
    
        public void StartLevelUp()
        {
            ChangeState(GameState.LevelUp);
            playerObject.SendMessage("RemoveAndApplyUpgrades");
        }
    
        public void EndLevelUp()
        {
            choosingUpgrade = false;
            Time.timeScale = 1f; //resume the game
            levelUpScreen.SetActive(false);
            ChangeState(GameState.Gameplay);
        }
    }

    and this is the inventory script:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using TMPro;
    
    public class PlayerInventory : MonoBehaviour
    {
        [System.Serializable]
        public class  Slot
        {
            public Item item;
            public Image image;
    
            public void Assign(Item assignedItem)
            {
                item = assignedItem;
                if (item is Weapon)
                {
                    Weapon w = item as Weapon;
                    image.enabled = true;
                    image.sprite = w.data.icon;
                }
                else
                {
                    Passive p = item as Passive;
                    image.enabled = true;
                    image.sprite = p.data.icon;
                }
                Debug.Log(string.Format("Assigned {0} to player.", item.name));
            }
    
            public void Clear()
            {
                item = null;
                image.enabled = false;
                image.sprite = null;
            }
    
            public bool IsEmpty()
            {
                return item == null;
            }
        }
        public List<Slot> weaponSlots = new List<Slot>(6);
        public List<Slot> passiveSlots = new List<Slot>(6);
    
        [System.Serializable]
        public class UpgradeUI
        {
            public TMP_Text upgradeNameDisplay;
            public TMP_Text upgradeDescriptionDisplay;
            public Image upgradeIcon;
            public Button upgradeButton;
        }
    
        [Header("UI Elements")]
        public List<WeaponData> availableWeapons = new List<WeaponData>(); //list of upgrade options for weapons
        public List<PassiveData> availablePassives = new List<PassiveData>(); //list of upgrade options for passive items
        public List<UpgradeUI> upgradeUIOptions = new List<UpgradeUI>(); //list of ui for upgrade options present in the scene
    
        PlayerStats player;
    
        void Start()
        {
            player = GetComponent<PlayerStats>();
        }
    
        //checks if the inventory has an item of a certain type
        public bool Has(ItemData type) { return Get(type); }
    
        public Item Get(ItemData type)
        {
            if (type is WeaponData) return Get(type as WeaponData);
            else if (type is PassiveData) return Get(type as PassiveData);
            return null;
        }
    
        //find a passive of certain type in the inventory
        public Passive Get(PassiveData type)
        {
            foreach (Slot s in passiveSlots)
            {
                Passive p = s.item as Passive;
                if (p.data == type)
                    return p;
            }
            return null;
        }
    
        //find a weapon of certain type in the inventory
        public Weapon Get(WeaponData type)
        {
            foreach (Slot s in weaponSlots)
            {
                Weapon w = s.item as Weapon;
                if (w.data == type)
                    return w;
            }
            return null;
        }
    
        //removes a weapon of a particular type as specified by <data>
        public bool Remove(WeaponData data, bool removeUpgradeAvailability = false)
        {
            //remove this weapon from the upgrade pool
            if (removeUpgradeAvailability) availableWeapons.Remove(data);
    
            for (int i = 0; i < weaponSlots.Count; i++)
            {
                Weapon w = weaponSlots[i].item as Weapon;
                if (w.data == data)
                {
                    weaponSlots[i].Clear();
                    w.OnUnequip();
                    Destroy(w.gameObject);
                    return true;
                }
            }
            return false;
        }
    
        //removes a passive of a particular type, as specified by <data>
        public bool Remove(PassiveData data, bool removeUpgradeAvailability = false)
        {
            //remove this passive from the upgrade pool
            if (removeUpgradeAvailability) availablePassives.Remove(data);
    
            for (int i = 0; i < passiveSlots.Count; i++)   //
            //for (int i = 0; i < weaponSlots.Count; i++)   
                {
                Passive p = passiveSlots[i].item as Passive; //
                //Passive p = weaponSlots[i].item as Passive; 
                if (p.data == data)
                {
                    passiveSlots[i].Clear();   //
                    //weaponSlots[i].Clear();   
                    p.OnUnequip();
                    Destroy(p.gameObject);
                    return true;
                }
            }
            return false;
        }
    
        // if an ItemData is passed, determine what type it is and call the respective overload
        //we also have an optional boolean to remove this item from the upgrade list
    
        public bool Remove(ItemData data, bool removeUpgradeAvailability = false)
        {
            if (data is PassiveData) return Remove(data as PassiveData, removeUpgradeAvailability);
            else if (data is WeaponData) return Remove(data as WeaponData, removeUpgradeAvailability);
            return false;
        }
    
        //finds an empty slot and adds a weapon of a certain type,returns the slot number that the item was put in
    
        public int Add(WeaponData data)
        {
            int slotNum = -1;
    
            //try to find an empty slot
            for (int i = 0; i < weaponSlots.Capacity; i++)
            {
                if (weaponSlots[i].IsEmpty())
                {
                    slotNum = i;
                    break;
                }
            }
    
            //if there is no empty slot, exit
            if (slotNum < 0) return slotNum;
    
            //otherwise create the weapon in the slot and get the type of the weapon we want to spawn
            Type weaponType = Type.GetType(data.behaviour);
    
            if (weaponType != null)
            {
                //spawn the weapon GameObject
                GameObject go = new GameObject(data.baseStats.name + " Controller");
                Weapon spawnedWeapon = (Weapon)go.AddComponent(weaponType);
                spawnedWeapon.Initialise(data);
                spawnedWeapon.transform.SetParent(transform); //set the weapon to be a child of the player
                spawnedWeapon.transform.localPosition = Vector2.zero;
                spawnedWeapon.OnEquip();
    
                //assign the weapon to the slot
                weaponSlots[slotNum].Assign(spawnedWeapon);
    
                //close the level up UI if it is on
                if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
                    GameManager.instance.EndLevelUp();
    
                return slotNum;
            }
            else
            {
                Debug.LogWarning(string.Format(
                    "Invalid weapon type specified for {0}.",
                    data.name
                ));
            }
    
            return -1;
        }
    
        // finds an empty slot and adds a passive of a certain type, returns the slot number that the item was put in
        public int Add(PassiveData data)
        {
            int slotNum = -1;
    
            //try to find an empty slot
            for (int i = 0; i < passiveSlots.Capacity; i++)
            {
                if (passiveSlots[i].IsEmpty())
                {
                    slotNum = i;
                    break;
                }
            }
    
            //if there is no empty slot, exit
            if (slotNum < 0) return slotNum;
    
            //otherwise create the passive in the slot and get the type of the passive we want to spawn
            GameObject go = new GameObject(data.baseStats.name + " Passive");
            Passive p = go.AddComponent<Passive>();
            p.Initialise(data);
            p.transform.SetParent(transform); //set the passive to be a child of the player
            p.transform.localPosition = Vector2.zero;
    
            //assign the passive to the slot
            passiveSlots[slotNum].Assign(p);
    
            if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
            {
                GameManager.instance.EndLevelUp();
            }
            player.RecalculateStats();
    
            return slotNum;
        }
    
        // if we dont know what item is being added, this function will determine that
        public int Add(ItemData data)
        {
            if (data is WeaponData) return Add(data as WeaponData);
            else if (data is PassiveData) return Add(data as PassiveData);
            return -1;
        }
    
        public void LevelUpWeapon(int slotIndex, int upgradeIndex)
        {
            if (weaponSlots.Count > slotIndex)
            {
                Weapon weapon = weaponSlots[slotIndex].item as Weapon;
    
                //don't level up the weapon if it is already at max level
                if (!weapon.DoLevelUp())
                {
                    Debug.LogWarning(string.Format(
                        "Failed to level up {0}.",
                        weapon.name
                    ));
                    return;
                }
            }
    
            if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
            {
                GameManager.instance.EndLevelUp();
            }
        }
    
        public void LevelUpPassiveItem(int slotIndex, int upgradeIndex)
        {
            if (passiveSlots.Count > slotIndex)
            {
                Passive p = passiveSlots[slotIndex].item as Passive;
                if (!p.DoLevelUp())
                {
                    Debug.LogWarning(string.Format(
                        "Failed to level up {0}.",
                        p.name
                    ));
                    return;
                }
            }
    
            if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
            {
                GameManager.instance.EndLevelUp();
            }
            player.RecalculateStats();
        }
    
        //determine what upgrade options should appear
        void ApplyUpgradeOptions()
        {
            //make a duplicate of the available weapon / passive upgrade lists so we can iterate through them in the function
            List<WeaponData> availableWeaponUpgrades = new List<WeaponData>(availableWeapons);
            List<PassiveData> availablePassiveItemUpgrades = new List<PassiveData>(availablePassives);
    
            //iterate through each slot in the upgrade UI
            foreach (UpgradeUI upgradeOption in upgradeUIOptions)
            {
                //if there are no more available upgrades, then we abort
                if (availableWeaponUpgrades.Count == 0 && availablePassiveItemUpgrades.Count == 0)
                    return;
    
                //determine whether this upgrade should be for passive or active weapons
                int upgradeType;
                if (availableWeaponUpgrades.Count == 0)
                {
                    upgradeType = 2;
                }
                else if (availablePassiveItemUpgrades.Count == 0)
                {
                    upgradeType = 1;
                }
                else
                {
                    //random generates a number between 1 and 2
                    upgradeType = UnityEngine.Random.Range(1, 3);
                }
    
                //generates an active weapon upgrade
                if (upgradeType == 1)
                {
                    //pick a weapon upgrade, then remove it so that we don't get it twice
                    WeaponData chosenWeaponUpgrade = availableWeaponUpgrades[UnityEngine.Random.Range(0, availableWeaponUpgrades.Count)];
                    availableWeaponUpgrades.Remove(chosenWeaponUpgrade);
    
                    //ensure that the selected weapon data is valid
                    if (chosenWeaponUpgrade != null)
                    {
                        //turns on the UI slot
                        EnableUpgradeUI(upgradeOption);
    
                        //loops through all our existing weapons. if we find a match, we will
                        //hook an event listener to the button that will level up the weapon
                        //when this upgrade option is clicked
                        bool isLevelUp = false;
                        for (int i = 0; i < weaponSlots.Count; i++)
                        {
                            Weapon w = weaponSlots[i].item as Weapon;
                            if (w != null && w.data == chosenWeaponUpgrade)
                            {
                                //if the weapon is already at the max level, do not allow upgrade
                                if (chosenWeaponUpgrade.maxLevel <= w.currentLevel)
                                {
                                    //DisableUpgradeUI(upgradeOption);
                                    isLevelUp = false;
                                    break;
                                }
    
                                // set the event listener, item and level description to be that opf the next level
                                upgradeOption.upgradeButton.onClick.AddListener(() => LevelUpWeapon(i, i)); //apply button functionality
                                Weapon.Stats nextLevel = chosenWeaponUpgrade.GetLevelData(w.currentLevel + 1);
                                upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description;
                                upgradeOption.upgradeNameDisplay.text = nextLevel.name;
                                upgradeOption.upgradeIcon.sprite = chosenWeaponUpgrade.icon;
                                isLevelUp = true;
                                break;
                            }
                        }
    
                        //if the code gets here, it means that we will be adding a new weapon , intead of upgrading an existing one
                        if (!isLevelUp)
                        {
                            upgradeOption.upgradeButton.onClick.AddListener(() => Add(chosenWeaponUpgrade)); //apply button functionality
                            upgradeOption.upgradeDescriptionDisplay.text = chosenWeaponUpgrade.baseStats.description;
                            upgradeOption.upgradeNameDisplay.text = chosenWeaponUpgrade.baseStats.name;
                            upgradeOption.upgradeIcon.sprite = chosenWeaponUpgrade.icon;
                        }
                    }
                }
                else if (upgradeType == 2)
                {
                    //NOTE: we have to recode this system as right now it disables an upgrade slot if
                    //we hit a weapon that has already reached its max level
                    PassiveData chosenPassiveUpgrade = availablePassiveItemUpgrades[UnityEngine.Random.Range(0, availablePassiveItemUpgrades.Count)];
                    availablePassiveItemUpgrades.Remove(chosenPassiveUpgrade);
    
                    if (chosenPassiveUpgrade != null)
                    {
                        //turns on the UI slot
                        EnableUpgradeUI(upgradeOption);
    
                        //loops through all our existing passives. if we find a match, we will
                        //hook an event listener to the button that will level up the passive
                        //when this upgrade option is clicked
                        bool isLevelUp = false;
                        for (int i = 0; i < passiveSlots.Count; i++)
                        {
                            Passive p = passiveSlots[i].item as Passive;
                            if (p != null && p.data == chosenPassiveUpgrade)
                            {
                                //if the passive is already at the max level, do not allow upgrade
                                if (chosenPassiveUpgrade.maxLevel <= p.currentLevel)
                                {
                                    //DisableUpgradeUI(upgradeOption);
                                    isLevelUp = false;
                                    break;
                                }
    
                                // set the event listener, item and level description to be that of the next level
                                upgradeOption.upgradeButton.onClick.AddListener(() => LevelUpPassiveItem(i, i)); //apply button functionality
                                Passive.Modifier nextLevel = chosenPassiveUpgrade.GetLevelData(p.currentLevel + 1);
                                upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description;
                                upgradeOption.upgradeNameDisplay.text = nextLevel.name;
                                upgradeOption.upgradeIcon.sprite = chosenPassiveUpgrade.icon;
                                isLevelUp = true;
                                break;
                            }
                        }
    
                        if (!isLevelUp)  // spawn a new passive item
                        {
                            upgradeOption.upgradeButton.onClick.AddListener(() => Add(chosenPassiveUpgrade)); //apply button functionality
                            Passive.Modifier nextLevel = chosenPassiveUpgrade.baseStats;
                            upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description; //apply initial description
                            upgradeOption.upgradeNameDisplay.text = nextLevel.name; //apply initial name
                            upgradeOption.upgradeIcon.sprite = chosenPassiveUpgrade.icon;
                        }
                    }
                }
            }
        }
    
        void RemoveUpgradeOptions()
        {
            foreach (UpgradeUI upgradeOption in upgradeUIOptions)
            {
                upgradeOption.upgradeButton.onClick.RemoveAllListeners();
                DisableUpgradeUI(upgradeOption); //call the disableupgradeui method here to disable all UI options before applying upgrades to them
            }
        }
    
        public void RemoveAndApplyUpgrades()
        {
            RemoveUpgradeOptions();
            ApplyUpgradeOptions();
        }
    
        void DisableUpgradeUI(UpgradeUI ui)
        {
            ui.upgradeNameDisplay.transform.parent.gameObject.SetActive(false);
        }
    
        void EnableUpgradeUI(UpgradeUI ui)
        {
            ui.upgradeNameDisplay.transform.parent.gameObject.SetActive(true);
        }
    }
    #18722
    Alp Apustaja
    Level 10
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    For the XenoVault error, modify the script to add more robust null checking:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
       if (inventory == null) 
       {
           Debug.LogWarning("Inventory is null");
           return;
       }
    
       bool evolutionAttempted = false;
    
       foreach (PlayerInventory.Slot s in inventory.weaponSlots)
       {
           if (s?.item == null) continue;
    
           Weapon w = s.item as Weapon;
           if (w?.data?.evolutionData == null) continue;
    
           foreach (ItemData.Evolution e in w.data.evolutionData)
           {
               if (e == null) continue;
    
               if (e.condition == ItemData.Evolution.Condition.treasureChest)
               {
                   try 
                   {
                       bool attempt = w.AttemptEvolution(e, 0);
                       if (attempt)
                       {
                           evolutionAttempted = true;
                           break;
                       }
                   }
                   catch (System.Exception ex)
                   {
                       Debug.LogError($"Evolution attempt failed: {ex.Message}");
                   }
               }
           }
    
           if (evolutionAttempted) break;
       }
    }

    For the UI bug in the GameManager, add a method to reset the UI state when a new run starts:

    public void ResetRunUI()
    {
        // Clear weapon and passive item UI
        foreach (Image weaponImage in chosenWeaponsUI)
        {
            weaponImage.enabled = false;
            weaponImage.sprite = null;
        }
    
        foreach (Image passiveImage in chosenPassiveItemsUI)
        {
            passiveImage.enabled = false;
            passiveImage.sprite = null;
        }
    
        // Reset other run-related UI elements
        stopwatchTime = 0;
        UpdateStopwatchDisplay();
    }

    Call this method when starting a new run or before loading the game scene.

    Would you like me to elaborate on any of these points or provide more specific debugging advice?

    #18728
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    Hi Alp! ive tried both solutions but it did not work. if i replace the method on the xenovault script , then unity cant compile the game and give me an error message on this line: ” if (e == null) continue; “

    about the ResetRunUI method , i have added it to the gamemanager script and called it on the end of Awake function. is that right? it will just hide all the empty item icon borders in the end and wont show anything. before it was showing just the borders without the items ive collected through the game. Also the pause screen is not updating the stats when I pick an passive item. I have checked on debug mode and the values are updated on the player stats, but not showing on the stats display on pause screen.

    #18729
    Alp Apustaja
    Level 10
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    For the XenoVault script, modify the code to remove the null check for the evolution:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
     if (inventory == null) 
     {
         Debug.LogWarning("Inventory is null");
         return;
     }
    
     bool evolutionAttempted = false;
    
     foreach (PlayerInventory.Slot s in inventory.weaponSlots)
     {
         if (s?.item == null) continue;
    
         Weapon w = s.item as Weapon;
         if (w?.data?.evolutionData == null) continue;
    
         foreach (ItemData.Evolution e in w.data.evolutionData)
         {
             if (e.condition == ItemData.Evolution.Condition.treasureChest)
             {
                 try 
                 {
                     bool attempt = w.AttemptEvolution(e, 0);
                     if (attempt)
                     {
                         evolutionAttempted = true;
                         break;
                     }
                 }
                 catch (System.Exception ex)
                 {
                     Debug.LogError($"Evolution attempt failed: {ex.Message}");
                 }
             }
         }
    
         if (evolutionAttempted) break;
     }
    }

    For updating stats on the pause screen, add a method to the GameManager to update stats:

    public void UpdatePauseScreenStats(PlayerStats playerStats)
    {
        currentHealthDisplay.text = "Health: " + playerStats.currentHealth.ToString("F0") + " / " + playerStats.maxHealth.ToString("F0");
        currentRecoveryDisplay.text = "Recovery: " + playerStats.recovery.ToString("F1");
        currentMoveSpeedDisplay.text = "Move Speed: " + playerStats.moveSpeed.ToString("F1");
        currentMightDisplay.text = "Might: " + playerStats.might.ToString("F1");
        currentProjectileSpeedDisplay.text = "Projectile Speed: " + playerStats.projectileSpeed.ToString("F1");
        currentMagnetDisplay.text = "Magnet: " + playerStats.magnet.ToString("F1");
    }

    Call this method when pausing the game:

    public void PauseGame()
    {
        if(currentState != GameState.Paused)
        {
            previousState = currentState;
            ChangeState(GameState.Paused);
            Time.timeScale = 0f;
            pauseScreen.SetActive(true);
    
            // Add this line to update stats
            UpdatePauseScreenStats(playerObject.GetComponent());
    
            Debug.Log("Game is paused");
        }
    }

    Could you clarify:

    1. Where exactly are you calling ResetRunUI?
    2. Do you want to preserve the item icons between runs or reset them completely?
    #18730
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    well i think ill wait for terence to sort this, because today im changing the original code too much without any results, it will make it difficult to follow the next steps if i keep on doing it like this

    @terence are the pause screen’s stats and results screen’s item icons supposed to be working like before at the end of part 15 (if so ill review the video bit by bit to find the errors on my code) or should i move on until the updated UI videos to fix this?

    #18736
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    1
    ::

    No worries Bruno. For the UI issue, you can check out this code. It is addressed at the start of Part 16: https://blog.terresquall.com/community/topic/part-15-assignchosenweaponsandpassiveitemsui-not-working/

    For the XenoVault, can you share your script with me again? The latest version.

    The start of Part 16 addresses quite a few of these issues found in Part 15. Perhaps you can check out that part to see if it helps with your issues as well.

      1 anonymous person
    has upvoted this post.
    #18738
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    ill check the topic you mentioned and follow the part 16 to see if all bugs get fixed then

    my xenovault script is like this now: (now it gets destroyed even if I don’t have the evolution requirements, but will still give this error when i pick it in this situation)

    using UnityEngine;
    
    public class XenoVault : MonoBehaviour
    {
    
        private void OnTriggerEnter2D(Collider2D col)
        {
            PlayerInventory p = col.GetComponent<PlayerInventory>();
            if (p)
            {
                bool randomBool = Random.Range(0, 2) == 0;
    
                OpenXenoVault(p, randomBool);
    
                Destroy(gameObject);
            }
        }
    
        public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
        {
            if (inventory == null)
            {
                Debug.LogWarning("Inventory is null");
                return;
            }
    
            bool evolutionAttempted = false;
    
            foreach (PlayerInventory.Slot s in inventory.weaponSlots)
            {
                if (s?.item == null) continue;
    
                Weapon w = s.item as Weapon;
                if (w?.data?.evolutionData == null) continue;
    
                foreach (ItemData.Evolution e in w.data.evolutionData)
                {
                    if (e.condition == ItemData.Evolution.Condition.treasureChest)
                    {
                        try
                        {
                            bool attempt = w.AttemptEvolution(e, 0);
                            if (attempt)
                            {
                                evolutionAttempted = true;
                                break;
                            }
                        }
                        catch (System.Exception ex)
                        {
                            Debug.LogError($"Evolution attempt failed: {ex.Message}");
                        }
                    }
                }
    
                if (evolutionAttempted) break;
            }
        }
    }

    the error is

    Evolution attempt failed: Object reference not set to an instance of an object UnityEngine.Debug:LogError (object) XenoVault:OpenXenoVault (PlayerInventory,bool) (at Assets/Scripts/Pickups/XenoVault.cs:51) XenoVault:OnTriggerEnter2D (UnityEngine.Collider2D) (at Assets/Scripts/Pickups/XenoVault.cs:13)
    #18739
    Alp Apustaja
    Level 10
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    The error suggests there’s a null reference in the AttemptEvolution method. Let’s modify the XenoVault script to add more robust null checking:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
        if (inventory == null)
        {
            Debug.LogWarning("Inventory is null");
            return;
        }
    
        bool evolutionAttempted = false;
    
        foreach (PlayerInventory.Slot s in inventory.weaponSlots)
        {
            if (s?.item == null) continue;
    
            Weapon w = s.item as Weapon;
            if (w?.data?.evolutionData == null) continue;
    
            foreach (ItemData.Evolution e in w.data.evolutionData)
            {
                if (e == null) continue;
    
                if (e.condition == ItemData.Evolution.Condition.treasureChest)
                {
                    try 
                    {
                        // Add null checks before attempting evolution
                        if (w == null || e == null)
                        {
                            Debug.LogWarning("Weapon or evolution data is null");
                            continue;
                        }
    
                        bool attempt = w.AttemptEvolution(e, 0);
                        if (attempt)
                        {
                            evolutionAttempted = true;
                            break;
                        }
                    }
                    catch (System.Exception ex)
                    {
                        Debug.LogError($"Evolution attempt failed: {ex.Message}");
                    }
                }
            }
    
            if (evolutionAttempted) break;
        }
    }

    Could you provide more context about:

    1. The specific implementation of the AttemptEvolution method in the Weapon class
    2. The structure of the ItemData.Evolution class
    3. The full error message and stack trace

    This will help diagnose the exact cause of the null reference exception.

    #18740
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    @brunoazalim The error was caused by your own message 😆 Specifically this line:

    public void OpenXenoVault(PlayerInventory inventory, bool isHigherTier)
    {
        if (inventory == null)
        {
            Debug.LogWarning("Inventory is null");
            return;
        }
    
        bool evolutionAttempted = false;
    
        foreach (PlayerInventory.Slot s in inventory.weaponSlots)
        {
            if (s?.item == null) continue;
    
            Weapon w = s.item as Weapon;
            if (w?.data?.evolutionData == null) continue;
    
            foreach (ItemData.Evolution e in w.data.evolutionData)
            {
                if (e.condition == ItemData.Evolution.Condition.treasureChest)
                {
                    try
                    {
                        bool attempt = w.AttemptEvolution(e, 0);
                        if (attempt)
                        {
                            evolutionAttempted = true;
                            break;
                        }
                    }
                    catch (System.Exception ex)
                    {
                        Debug.LogError($"Evolution attempt failed: {ex.Message}");
                    }
                }
            }
    
            if (evolutionAttempted) break;
        }
    }

    The error will be gone if you remove the highlighted line above. What the try and catch block is doing is if you do w.AttemptEvolution() when w is null, it will instead jump over to the catch block to prevent the error.

    You can instead replace the catch block with a Destroy() call if you want the treasure chest to be consumed without any effect after getting picked up.

    try
    {
        bool attempt = w.AttemptEvolution(e, 0);
        if (attempt)
        {
            evolutionAttempted = true;
            break;
        }
    }
    catch (System.Exception ex)
    {
        Debug.LogError($"Evolution attempt failed: {ex.Message}");
        Destroy(gameObject);
    }
    #18755
    Bruno Azalim
    Level 4
    Participant
    Helpful?
    Up
    1
    ::

    thank you terence! i can finally move on to the next part

    has upvoted this post.
Viewing 20 posts - 1 through 20 (of 21 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: