Forum begins after the advertisement:


[Part 15] Evolution doesn’t work when one of the requirements is level 1

Home Forums Video Game Tutorial Series Creating a Rogue-like Shoot-em Up in Unity [Part 15] Evolution doesn’t work when one of the requirements is level 1

Viewing 1 post (of 1 total)
  • Author
    Posts
  • #16017
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    Grant Totinov has highlighted an issue with the evolution code in Part 15, where if one of your evolution requirements is a Level 1 Weapon or Passive, the system doesn’t detect that the evolution condition has been met.

    This issue occurs because evolution conditions are only checked on level up. They are not checked when you first pick up an item. To rectify this, we will have to run the script checking for evolution in the OnEquip() function in Item.cs, so that the evolution conditions are checked.

    The code checking for evolution is found in DoLevelUp(), but we cannot call DoLevelUp() because it is overridden by both the Weapon.cs and Passive.cs child classes to increase the level of the item whenever they are called. Hence, we will have to move the evolution code found in Item.DoLevelUp() to a new, separate function, AttemptEvolution().

    This AttemptEvolution() function is an overload of the other function by the same name, which only checks for a single evolution condition.

    Item.cs

    using System.Collections.Generic;
    using UnityEngine;
    
    /// <summary>
    /// Base class for both the Passive and the Weapon classes. It is primarily intended
    /// to handle weapon evolution, as we want both weapons and passives to be evolve-able.
    /// </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 = FindObjectOfType<PlayerInventory>();
            owner = FindObjectOfType<PlayerStats>();
        }
    
        // Call this function to get all the evolutions that the weapon
        // can currently 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 them.
            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 AttemptEvolution(int levelUpAmount = 1) {
            if (evolutionData == null) return false;
    
            // 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 the evolution succeeds, we stop trying to find further evolutions.
                if(e.condition == ItemData.Evolution.Condition.auto)
                    return AttemptEvolution(e);
            }
            return false;
        }
    
        public virtual bool CanLevelUp()
        {
            return currentLevel <= maxLevel;
        }
    
        // Whenever an item levels up, attempt to make it evolve.
        public virtual bool DoLevelUp()
        {
            if (evolutionData == null) return true;
    
            // 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;
            AttemptEvolution()
        }
    
        // What effects you receive on equipping an item.
        public virtual void OnEquip() {
            AttemptEvolution(0);
        }
    
        // What effects are removed on unequipping an item.
        public virtual void OnUnequip() { }
    }

    After doing this, we will also need to go back to PlayerInventory and make a small change to one of the Get() functions, specifically the Passive version because it does not call OnEquip() when a passive is given to the player.

    PlayerInventory.cs

    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 certaint 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 a 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 a 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 .
        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 < weaponSlots.Count; i++)
            {
                Passive p = weaponSlots[i].item as Passive;
                if (p.data == data)
                {
                    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.
            // 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.
            // 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 weapon to be a child of the player
            p.transform.localPosition = Vector2.zero;
            p.OnEquip();
    
            // 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 don't 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();
        }
    
        // Determines 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 avaiable 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 of 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, instead of
                        // upgrading an existing weapon.
                        if (!isLevelUp)
                        {
                            upgradeOption.upgradeButton.onClick.AddListener(() => Add(chosenWeaponUpgrade)); //Apply button functionality
                            upgradeOption.upgradeDescriptionDisplay.text = chosenWeaponUpgrade.baseStats.description;  //Apply initial description
                            upgradeOption.upgradeNameDisplay.text = chosenWeaponUpgrade.baseStats.name;    //Apply initial 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 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 passive. 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 < 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;
                                }
                                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);
        }
    
    }

    Finally, if you override the OnEquip() function on any of your subclassed weapon scripts, you will also have to go back and make sure you invoke the parent OnEquip() function as well. Otherwise, that whole category of weapons in your game using that script will not be able to evolve on being picked up. For example:

    AuraWeapon.cs

    using UnityEngine;
    
    public class AuraWeapon : Weapon
    {
    
        protected Aura currentAura;
    
        // Update is called once per frame
        protected override void Update() {}
    
        public override void OnEquip()
        {
            OnEquip();
            // Try to replace the aura the weapon has with a new one.
            if (currentStats.auraPrefab)
            {
                if (currentAura) Destroy(currentAura);
                currentAura = Instantiate(currentStats.auraPrefab, transform);
                currentAura.weapon = this;
                currentAura.owner = owner;
            }
        }
    
        public override void OnUnequip()
        {
            OnUnequip();
            if (currentAura) Destroy(currentAura);
        }
    
        public override bool DoLevelUp()
        {
            if (!base.DoLevelUp()) return false;
    
            // If there is an aura attached to this weapon, we update the aura.
            if (currentAura)
            {
                currentAura.transform.localScale = new Vector3(currentStats.area, currentStats.area, currentStats.area);
            }
            return true;
        }
    
    }
Viewing 1 post (of 1 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: