Forum begins after the advertisement:


[Part 15] Character unable to shoot (cannot shoot the projectile)

Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [Part 15] Character unable to shoot (cannot shoot the projectile)

Viewing 5 posts - 1 through 5 (of 5 total)
  • Author
    Posts
  • #19936
    Khôi Đỗ
    Level 1
    Participant
    Helpful?
    Up
    0
    ::

    Hey there,

    I’m Khoi. I followed exactly the tutorial video, copied the whole code from the vid as well but the result is when I pressed play, the character is not firing any knives. I attached the code below. Thanks in advance for helping me.

    [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 Weapons)
                {
                    Weapons w = item as Weapons;
                    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);
    
    
    
        public class UpgradeUI
        {
            public TMP_Text upgradeNameDisplay;
            public TMP_Text upgradeDescriptionDisplay;
            public Image upgradeIcon;
            public Button upgradeButton;
        }
    
        [Header("UI Element")]
        public List<WeaponData> availableWeapons = new List<WeaponData>();
        public List<PassiveData> availablePassives = new List<PassiveData>();
        public List<UpgradeUI> upgradeUIOptions = new List<UpgradeUI>();
    
        PlayerStats player;
    
        void Start()
        {
            player = GetComponent<PlayerStats>();
    
    
        }
    
        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;
        }
    
        public Passive Get(PassiveData type)
        {
            foreach (Slot s in passiveSlots)
            {
                Passive p = s.item as Passive;
                if (p.data == type)
                    return p;
            }
            return null;
    
        }
    
    
        public Weapons Get(WeaponData type)
        {
            foreach (Slot s in weaponSlots)
            {
                Weapons w = s.item as Weapons;
                if (w.data == type)
                    return w;
            }
            return null;
        }
    
        public bool Remove(WeaponData data, bool removeUpgradeAvailability = false)
        {
            if (removeUpgradeAvailability) availableWeapons.Remove(data);
    
            for(int i = 0; i < weaponSlots.Count; i++)
            {
                Weapons w = weaponSlots[i].item as Weapons;
                if(w.data == data)
                {
                    weaponSlots[i].Clear();
                    w.OnUnequip();
                    Destroy(w.gameObject);
                    return true;
                }
            }
    
            return false;
        }
    
    
        public bool Remove(PassiveData data, bool removeUpgradeAvailability = false)
        {
            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;
        }
    
    
        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;
        }
    
        public int Add(WeaponData data)
        {
            int slotNum = -1;
    
            for(int i = 0; i < weaponSlots.Capacity; i++)
            {
                if (weaponSlots[i].IsEmpty())
                {
                    slotNum = i;
                    break;
                }
            }
    
    
            if (slotNum < 0) return slotNum;
    
            Type weaponType = Type.GetType(data.behaviour);
    
            if(weaponType != null)
            {
                GameObject go = new GameObject(data.baseStats.name + " Controller");
                Weapons spawnedWeapon = (Weapons)go.AddComponent(weaponType);
                spawnedWeapon.transform.SetParent(transform);
                spawnedWeapon.transform.localPosition = Vector2.zero;
                spawnedWeapon.Initialise(data);
                spawnedWeapon.OnEquip();
    
                weaponSlots[slotNum].Assign(spawnedWeapon);
    
                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;
        }
    
        public int Add(PassiveData data)
        {
            int slotNum = -1;
    
            for (int i = 0; i < passiveSlots.Capacity; i++)
            {
                if (passiveSlots[i].IsEmpty())
                {
                    slotNum = i;
                    break;
                }
            }
    
    
            if (slotNum < 0) return slotNum;
    
    
    
    
            GameObject go = new GameObject(data.baseStats.name + " Passive");
            Passive p = go.AddComponent<Passive>();
            p.Initialise(data);
            p.transform.SetParent(transform);
            p.transform.localPosition = Vector2.zero;
    
    
            passiveSlots[slotNum].Assign(p);
    
            if (GameManager.instance != null && GameManager.instance.choosingUpgrade)
            {
                GameManager.instance.EndLevelUp();
            }
    
            player.RecalculateStats();
    
            return slotNum;
    
    
        }
    
        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)
            {
                Weapons weapon = weaponSlots[slotIndex].item as Weapons;
    
                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();
        }
    
    
        void ApplyUpgradeOptions()
        {
            List<WeaponData> availableWeaponUpgrades = new List<WeaponData>(availableWeapons);
            List<PassiveData> availablePassiveItemUpgrades = new List<PassiveData>(availablePassives);
    
            foreach (UpgradeUI upgradeOption in upgradeUIOptions)
            {
                if(availableWeaponUpgrades.Count == 0 && availablePassiveItemUpgrades.Count == 0)
                    return;
    
    
                int upgradeType;
                if(availableWeaponUpgrades.Count == 0)
                {
                    upgradeType = 2;
                }
                else if (availablePassiveItemUpgrades.Count == 0)
                {
                    upgradeType = 1;
                }
                else
                {
                    upgradeType = UnityEngine.Random.Range(1, 3);
                }
    
    
                if(upgradeType == 1)
                {
                    WeaponData chosenWeaponUpgrade = availableWeaponUpgrades[UnityEngine.Random.Range(0, availableWeaponUpgrades.Count)];
                    availableWeaponUpgrades.Remove(chosenWeaponUpgrade);
    
                    if(chosenWeaponUpgrade != null)
                    {
                        EnableUpgradeUI(upgradeOption);
    
                        bool isLevelUp = false;
                        for (int i = 0; i < weaponSlots.Count; i++)
                        {
                            Weapons w = weaponSlots[i].item as Weapons;
                            if( w != null && w.data == chosenWeaponUpgrade)
                            {
                                isLevelUp = false;
    
                                break;
                            }
                            upgradeOption.upgradeButton.onClick.AddListener(() => LevelUpWeapon(i, i));
                            Weapons.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 (!isLevelUp)
                        {
                            upgradeOption.upgradeButton.onClick.AddListener(() => Add(chosenWeaponUpgrade));
                            upgradeOption.upgradeDescriptionDisplay.text = chosenWeaponUpgrade.baseStats.description;
                            upgradeOption.upgradeNameDisplay.text = chosenWeaponUpgrade.baseStats.name;
                            upgradeOption.upgradeIcon.sprite = chosenWeaponUpgrade.icon;
                        }
                    }
    
                }
                else if (upgradeType == 2)
                {
                    PassiveData chosenPassiveUpgrade = availablePassiveItemUpgrades[UnityEngine.Random.Range(0, availablePassiveItemUpgrades.Count)];
                    availablePassiveItemUpgrades.Remove(chosenPassiveUpgrade);
    
                    if (chosenPassiveUpgrade != null)
                    {
                        EnableUpgradeUI(upgradeOption);
    
    
                        bool isLevelUp = false;
                        for (int i = 0; i < passiveSlots.Count; i++)
                        {
                            Passive p = passiveSlots[i].item as Passive;
                            if (chosenPassiveUpgrade.maxLevel <= p.currentLevel)
                            {
                                isLevelUp = false;
                                break;
                            }
                            upgradeOption.upgradeButton.onClick.AddListener(() => LevelUpPassiveItem(i, i));
                            Passive.Modiier 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)
                        {
                            upgradeOption.upgradeButton.onClick.AddListener(() => Add(chosenPassiveUpgrade));
                            Passive.Modiier nextLevel = chosenPassiveUpgrade.baseStats;
                            upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description;
                            upgradeOption.upgradeNameDisplay.text = nextLevel.name;
                            upgradeOption.upgradeIcon.sprite = chosenPassiveUpgrade.icon;
                        }
                    }
    
    
                }
    
            }
    
        }
    
        void RemoveUpgradeOptions()
        {
            foreach(UpgradeUI upgradeOption in upgradeUIOptions)
            {
                upgradeOption.upgradeButton.onClick.RemoveAllListeners();
                DisableUpgradeUI(upgradeOption);
            }
        }
    
        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);
        }
    
    }

    [Weapons.cs]

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    
    public abstract class Weapons : Item
    {
        [System.Serializable]
    
        public struct Stats
        {
            public string name, description;
    
            [Header("Visuals")]
    
            public Projectile projectilePrefab;
            public ParticleSystem hitEffect;
            public Rect spawnVariance;
    
    
    
            [Header("Values")]
            public float lifespan;
            public float damage, damageVariance, area, speed, cooldown, projectileInterval, knockback;
            public int number, piercing, maxInstances;
    
    
            public static Stats operator +(Stats s1, Stats s2)
            {
                Stats result = new Stats();
                result.name = s2.name ?? s1.name;
                result.description = s2.description ?? s1.description;
                result.hitEffect = s2.hitEffect == null ? s1.hitEffect : s2.hitEffect;
                result.spawnVariance = s2.spawnVariance;
                result.lifespan = s1.lifespan + s2.lifespan;
                result.damage = s1.damage + s2.damage;
                result.damageVariance = s1.damageVariance + s2.damageVariance;
                result.area = s1.area + s2.area;
                result.speed = s1.speed + s2.speed;
                result.cooldown = s1.cooldown + s2.cooldown;
                result.projectileInterval = s1.projectileInterval + s2.projectileInterval;
                result.knockback = s1.knockback + s2.knockback;
                result.number = s1.number + s2.number;
                result.piercing = s1.piercing + s2.piercing;
                result.maxInstances = Mathf.Max(s1.maxInstances, s2.maxInstances);
                return result;
            }
    
    
            public float GetDamage()
            {
                return damage + Random.Range(0, damageVariance);
            }
        }
    
        protected Stats currentStats;
        public WeaponData data;
        protected float currentCooldown;
        protected PlayerMovement movement;
    
        public virtual void Initialise(WeaponData data)
        {
            base.Initialise(data);
            this.data = data;
            currentStats = data.baseStats;
            movement = GetComponentInParent<PlayerMovement>();
            currentCooldown = currentStats.cooldown;
        }
    
    
        protected virtual void Awake()
        {
            if (data) currentStats = data.baseStats;
        }
    
    
        protected virtual void Start()
        {
            if(data)
            {
                Initialise(data);
            }
        }
    
    
        protected virtual void Update()
        {
            currentCooldown -= Time.deltaTime;
            if (currentCooldown <= 0f)
            {
                Attack(currentStats.number);
            }
    
        }
    
    
    
        public override bool DoLevelUp()
        {
            base.DoLevelUp(); 
            if (!CanLevelUp())
            {
    
                Debug.LogWarning(string.Format("Weapon is already at max level", name, currentLevel, data.maxLevel));
                return false;
            }
    
            currentStats += data.GetLevelData(++currentLevel);
            return true;
    
        }
    
        public virtual bool CanAttack()
        {
            return currentCooldown <= 0;
        }
    
        protected virtual bool Attack(int attackCount = 1)
        {
            if(CanAttack())
            {
                currentCooldown += currentStats.cooldown;
                return true;
            }
            return false;
    
        }
    
    
        public virtual float GetDamage()
        {
            return currentStats.GetDamage() * owner.CurrentMight;
        }
    
        public virtual Stats GetStats() { return currentStats; }
    
    }

    [Projectile.cs]

    using Unity.VisualScripting;
    using UnityEngine;
    
    public class ProjectileWeapon : Weapons
    {
        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()
        {
            print("CanAttack() attack count: " + currentAttackCount);
            if (currentAttackCount > 0) return true;
            return base.CanAttack();
        }
    
        protected override bool Attack(int attackCount = 1)
        {
            print("Attack() fired.");
            if (!currentStats.projectilePrefab)
            {
                Debug.LogWarning(string.Format("projectile prefab has not been set for {0}", name));
                currentCooldown = data.baseStats.cooldown;
                return false;
            }
    
            if (!CanAttack())
            {
                print("Attack() failed.");
                return false;
            }
    
            print("Attack() succeeded!.");
    
            float spawnAngle = GetSpawnAngle();
    
            Projectile prefab = Instantiate(
                currentStats.projectilePrefab,
                owner.transform.position + (Vector3)GetSpawnOffset(spawnAngle),
                Quaternion.Euler(0, 0, spawnAngle));
    
            prefab.weapon = this;
            prefab.owner = owner;
            currentCooldown += currentStats.cooldown;
    
            attackCount--;
    
    
            if(attackCount > 0)
            {
                currentAttackCount = attackCount;
                currentAttackInterval = data.baseStats.projectileInterval;
            }
    
            return true;
        }
    
    
        protected virtual float GetSpawnAngle()
        {
            return Mathf.Atan2(movement.lastMovedVector.y, movement.lastMovedVector.x) * Mathf.Rad2Deg;
    
        }
    
    
    
        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));
        }
    
    
    }
    
    
    [CharacterSelection.cs]
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor.PackageManager;
    
    public class CharacterSelection : MonoBehaviour
    {
        public static CharacterSelection instance;
        public CharacterData characterData;
    
    
        void Awake()
        {
            if(instance == null)
            {
                instance = this;
                DontDestroyOnLoad(gameObject);
    
            }
            else
            {
                Debug.LogWarning("EXTRA" + this + "DELETED");
                Destroy(gameObject);
            }
        }
    
        public static CharacterData GetData()
        {
            if(instance && instance.characterData)
                return instance.characterData;
            else
            {
                CharacterData[] characters = Resources.FindObjectsOfTypeAll<CharacterData>();
                if(characters.Length > 0)
                {
                    return characters[Random.Range(0, characters.Length)];
                }
            }
            return null;
        }
    
        public void SelectCharacter(CharacterData character)
        {
            characterData = character;
        }
    
        public void DestroySingleton()
        {
            instance = null;
            Destroy(gameObject);
        }
    }

    [PlayerStats.cs]

    using System;
    using UnityEngine;
    using System.Collections.Generic;
    using UnityEngine.UI;
    using TMPro;
    using System.Runtime.InteropServices.WindowsRuntime;
    
    public class PlayerStats : MonoBehaviour
    {
    
        CharacterData characterData;
        public CharacterData.Stats baseStats;
        [SerializeField] CharacterData.Stats actualStats;
    
    
    
        float health;
    
    
        #region Current Stats Properties 
        public float CurrentHealth
        {
            get { return health; }
            set
            {
                if (health != value)
                {
                    health = value;
                    if (GameManager.instance != null)
                    {
                        GameManager.instance.currentHealthDisplay.text = string.Format("Health: {0} / {1}", health, actualStats.maxHealth);
                    }
                }
            }
        }
    
        public float MaxHealth
        {
            get { return actualStats.maxHealth; }
    
            set
            {
                if (actualStats.maxHealth != value)
                {
                    actualStats.maxHealth = value;
                    if (GameManager.instance != null)
                    {
                        GameManager.instance.currentHealthDisplay.text = string.Format("Health: {0} / {1}", health, actualStats.maxHealth);
                    }
                }
            }
        }
    
        public float CurrentRecovery
        {
            get { return Recovery; }
            set { Recovery = value; }
    
        }
    
        public float Recovery
        {
            get {return actualStats.recovery;}
            set
            {
                if(actualStats.recovery != value)
                {
                    actualStats.recovery = value;
                    if(GameManager.instance != null)
                    {
                        GameManager.instance.currentRecoveryDisplay.text = "Recovery: " + actualStats.recovery;
                    }
                }
    
            }
    
        }
    
    
    
    
    
        public float CurrentMoveSpeed
        {
            get { return MoveSpeed; }
            set { MoveSpeed = value; }
    
        }
    
        public float MoveSpeed
        {
            get { return actualStats.moveSpeed; }
            set
            {
                if (actualStats.moveSpeed != value)
                {
                    actualStats.moveSpeed = value;
                    if (GameManager.instance != null)
                    {
                        GameManager.instance.currentRecoveryDisplay.text = "Move Speed: " + actualStats.moveSpeed;
                    }
                }
    
            }
    
        }
    
    
        public float CurrentMight
        {
            get { return Recovery; }
            set { Recovery = value; }
    
        }
    
        public float Might
        {
            get { return actualStats.might; }
            set
            {
                if (actualStats.might != value)
                {
                    actualStats.might = value;
                    if (GameManager.instance != null)
                    {
                        GameManager.instance.currentRecoveryDisplay.text = "Might: " + actualStats.might;
                    }
                }
    
            }
    
        }
    
        public float CurrentProjectileSpeed
        {
            get { return Speed; }
            set { Speed = value; }
    
        }
    
        public float Speed
        {
            get { return actualStats.speed; }
            set
            {
                if (actualStats.speed != value)
                {
                    actualStats.speed = value;
                    if (GameManager.instance != null)
                    {
                        GameManager.instance.currentProjectileSpeedDisplay.text = "Speed: " + actualStats.speed;
                    }
                }
    
            }
    
        }
    
        public float CurrentMagnet
        {
            get { return Magnet; }
            set { Magnet = value; }
    
        }
    
        public float Magnet
        {
            get { return actualStats.magnet; }
            set
            {
                if (actualStats.magnet != value)
                {
                    actualStats.magnet = value;
                    if (GameManager.instance != null)
                    {
                        GameManager.instance.currentMagnetDisplay.text = "Magnet: " + actualStats.magnet;
                    }
                }
    
            }
    
        }
    
        #endregion
    
    
    
        public ParticleSystem damageEffect;
    
    
        //Experience and level
        [Header("Experience/Level")]
        public int experience = 0;
        public int level = 1;
        public int experienceCap;
    
        // Level ranges for experience cap increases
        [System.Serializable]
        public class LevelRange
        {
            public int startLevel;
            public int endLevel;
            public int experienceCapIncrease;
        }
    
        //iframes
        [Header("I-Frames")]
        public float invicibilityDuration;
        float invicibilityTimer;
        bool isInvincible;
    
    
    
    
    
        public List<LevelRange> levelRanges;
    
        PlayerInventory inventory;
        public int weaponIndex;
        public int passiveItemIndex;
    
        [Header("UI")]
        public Image healthBar;
        public Image expBar;
        public TMP_Text levelText;
    
    
    
    
    
        void Awake()
        {
            characterData = CharacterSelection.GetData();
            //CharacterSelection.instance.DestroySingleton();
    
            inventory = GetComponent<PlayerInventory>();
    
            baseStats = actualStats = characterData.stats;
            health = actualStats.maxHealth;
    
        }
    
        void Start()
        {
            inventory.Add(characterData.StartingWeapon);
    
            // Initialise the experience cap based on the current level
            experienceCap = levelRanges[0].experienceCapIncrease;
    
            GameManager.instance.currentHealthDisplay.text = "Health: " + CurrentHealth;
            GameManager.instance.currentRecoveryDisplay.text = "Recovery: " + CurrentRecovery;
            GameManager.instance.currentMoveSpeedDisplay.text = "Move Speed: " + CurrentMoveSpeed;
            GameManager.instance.currentMightDisplay.text = "Might: " + CurrentMight;
            GameManager.instance.currentProjectileSpeedDisplay.text = "Projectile Speed: " + CurrentProjectileSpeed;
            GameManager.instance.currentMagnetDisplay.text = "Magnet: " + CurrentMagnet;
    
    
            GameManager.instance.AssignChosenCharacterUI(characterData);
    
            UpdateHealthBar();
            UpdateExpBar();
            UpdateLevelText();
    
        }
    
        void Update()
        {
            // Handle invincibility timer
            if (invicibilityTimer > 0)
            {
                invicibilityTimer -= Time.deltaTime;
            }
    
            //if the invicibity timer has reached 0, set the invincibility to false
            else if (isInvincible)
            {
                isInvincible = false;
            }
    
            Recover();
        }
    
        public void RecalculateStats()
        {
            actualStats = baseStats;
            foreach (PlayerInventory.Slot s in inventory.passiveSlots)
            {
                Passive p = s.item as Passive;
                if(p)
                {
                    actualStats += p.GetBoosts();
                }
            }
        }
    
        public void IncreaseExperience(int amount)
        {
            experience += amount;
            LevelUpChecker();
            UpdateExpBar();
        }
    
    
        void LevelUpChecker()
        {
            if(experience >= experienceCap)
            {
                level++;
                experience -= experienceCap;
    
                int experienceCapIncrease = 0;
                foreach (LevelRange range in levelRanges)
                {
                    if(level >= range.startLevel && level <= range.endLevel)
                    {
                        experienceCapIncrease = range.experienceCapIncrease;
                        break;
                    }
                }
                experienceCap += experienceCapIncrease;
    
    
                UpdateLevelText();
    
                GameManager.instance.StartLevelUp();
            }
        }
    
        void UpdateExpBar()
        {
            expBar.fillAmount = (float)experience / experienceCap;
        }
    
        void UpdateLevelText()
        {
            levelText.text = "LV. " + level.ToString();
        }
    
        public void TakeDamage(float dmg)
        {
            //if the player is not currently invincible, reduce health and start invincibility
            if (!isInvincible)
            {
                CurrentHealth -= dmg;
    
                if(damageEffect) Destroy(Instantiate(damageEffect, transform.position, Quaternion.identity), 5f);
    
                invicibilityTimer = invicibilityDuration;
                isInvincible = true;
    
                if (CurrentHealth <= 0)
                {
                    Kill();
                }
    
                UpdateHealthBar();
    
            }
        }
    
        void UpdateHealthBar()
        {
            //update heatlh bar
            healthBar.fillAmount = CurrentHealth / actualStats.maxHealth;
        }
    
        public void Kill()
        {
            if(!GameManager.instance.isGameOver)
            {
                GameManager.instance.AssignLevelReachedUI(level);
    
                GameManager.instance.GameOver();
            }
        }
    
        public void RestoreHealth(float amount)
        {
    
            if (CurrentHealth < actualStats.maxHealth)
            {
    
                CurrentHealth += amount;
    
                if(CurrentHealth > actualStats.maxHealth)
                {
                    CurrentHealth = actualStats.maxHealth;
                }
    
            }
        }
    
        void Recover()
        {
            if(CurrentHealth < actualStats.maxHealth)
            {
                CurrentHealth += CurrentRecovery * Time.deltaTime;
    
                // Ensure that currentHealth does not exceed MaxHealth
                if (CurrentHealth > actualStats.maxHealth)
                {
                    CurrentHealth = actualStats.maxHealth;
                }
    
            }
        }
    
    
        [System.Obsolete("Old function that is kept to maintain compatibility with the InventoryManager. Will be removede soon.")]
        public void SpawnedWeapon(GameObject weapon)
        {
            if(weaponIndex >= inventory.weaponSlots.Count - 1)
            {
                Debug.LogError("Inventory slots already full");
                return;
            }
    
            //spawn starting weapon 
            GameObject spawnedWeapon = Instantiate(weapon, transform.position, Quaternion.identity);
            spawnedWeapon.transform.SetParent(transform); //set the player as the parent of the spawned weapon
            //inventory.AddWeapon(weaponIndex, spawnedWeapon.GetComponent<WeaponsController>());
    
            weaponIndex++;
        }
    
        [System.Obsolete("No need to spawn passive items directly now")]
        public void SpawnedPassiveItem(GameObject passiveItem)
        {
            //if (passiveItemIndex >= inventory.passiveItemSlots.Count - 1)
            //{
                //Debug.LogError("Inventory slots already full");
                //return;
            //}
    
            //spawn starting passive item 
            //GameObject spawnedPassiveItem = Instantiate(passiveItem, transform.position, Quaternion.identity);
            //spawnedPassiveItem.transform.SetParent(transform); //set the player as the parent of the spawned weapon
            //inventory.AddPassiveItem(passiveItemIndex, spawnedPassiveItem.GetComponent<PassiveItem>());
    
            //passiveItemIndex++;
        }
    }
    #19937
    Terence
    Level 32
    Keymaster
    Helpful?
    Up
    0
    ::

    Hi Khoi,

    I checked your code against the PDF guide. The main issue preventing your knives from firing is in PlayerInventory.cs, inside Slot.IsEmpty(). You also have several other errors that will affect upgrades and damage.

    1. PlayerInventory.cs — fix IsEmpty()

    You currently have:

    public bool IsEmpty() { return item = null; }

    Change it to:

    public bool IsEmpty() { return item == null; }

    You used an assignment (=) instead of a comparison (==). Your current code clears the item reference and returns false, so your inventory never finds an empty slot to add your starting weapon.

    Fix this first and try running your game again.

    2. Weapons.cs — preserve the projectile prefab when adding stats

    Inside Stats.operator +, you are missing this assignment:

    result.projectilePrefab = s2.projectilePrefab ?? s1.projectilePrefab;

    Add it alongside your other assignments, for example:

    result.name = s2.name ?? s1.name;
    result.description = s2.description ?? s1.description;
    result.projectilePrefab = s2.projectilePrefab ?? s1.projectilePrefab;
    result.hitEffect = s2.hitEffect == null ? s1.hitEffect : s2.hitEffect;

    Without this, your weapon loses its projectile prefab when you level it up, which stops it from firing.

    3. PlayerInventory.cs — correct your weapon upgrade loop

    Inside ApplyUpgradeOptions(), your weapon matching check immediately breaks out when it finds the correct weapon. Your upgrade code is outside that check, so it can run against an unrelated weapon or an empty slot.

    Replace your weapon for loop with:

    for (int i = 0; i < weaponSlots.Count; i++)
    {
        Weapons w = weaponSlots[i].item as Weapons;
    
        if (w != null && w.data == chosenWeaponUpgrade)
        {
            if (chosenWeaponUpgrade.maxLevel <= w.currentLevel)
            {
                isLevelUp = false;
                break;
            }
    
            int slotIndex = i;
            upgradeOption.upgradeButton.onClick.AddListener(
                () => LevelUpWeapon(slotIndex, slotIndex));
    
            Weapons.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;
        }
    }

    The upgrade setup belongs inside the check that confirms you own the selected weapon.

    4. PlayerInventory.cs — correct your passive upgrade loop

    Your passive loop is missing the check that confirms the slot contains the selected passive. Replace that loop with:

    for (int i = 0; i < passiveSlots.Count; i++)
    {
        Passive p = passiveSlots[i].item as Passive;
    
        if (p != null && p.data == chosenPassiveUpgrade)
        {
            if (chosenPassiveUpgrade.maxLevel <= p.currentLevel)
            {
                isLevelUp = false;
                break;
            }
    
            int slotIndex = i;
            upgradeOption.upgradeButton.onClick.AddListener(
                () => LevelUpPassiveItem(slotIndex, slotIndex));
    
            Passive.Modiier nextLevel =
                chosenPassiveUpgrade.GetLevelData(p.currentLevel + 1);
    
            upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description;
            upgradeOption.upgradeNameDisplay.text = nextLevel.name;
            upgradeOption.upgradeIcon.sprite = chosenPassiveUpgrade.icon;
    
            isLevelUp = true;
            break;
        }
    }

    Your submitted code spells the type Passive.Modiier, so I have retained that spelling here. The guide uses Passive.Modifier. Check your declaration in Passive.cs and make sure every reference uses the same spelling.

    5. PlayerInventory.cs — make UpgradeUI serializable

    Add the attribute immediately above your class:

    [System.Serializable]
    public class UpgradeUI
    {
        public TMP_Text upgradeNameDisplay;
        public TMP_Text upgradeDescriptionDisplay;
        public Image upgradeIcon;
        public Button upgradeButton;
    }

    You need this for Unity to serialize the entries and expose their fields in the Inspector.

    6. ProjectileWeapon.cs — reset the cooldown only when appropriate

    Inside Attack(), replace:

    currentCooldown += currentStats.cooldown;

    With:

    if (currentCooldown <= 0)
        currentCooldown += currentStats.cooldown;

    Your current version adds another full cooldown for every projectile in a burst, making the delay before the next burst unnecessarily long.

    7. PlayerStats.cs — correct CurrentMight

    Your CurrentMight property currently reads and changes Recovery. Replace it with:

    public float CurrentMight
    {
        get { return Might; }
        set { Might = value; }
    }

    Your weapon damage is multiplied by CurrentMight, so your current code uses recovery as the damage multiplier. If recovery is zero, your weapon deals zero damage.

    8. PlayerStats.cs — correct the stat display references

    Inside your MoveSpeed setter, change the UI assignment to:

    GameManager.instance.currentMoveSpeedDisplay.text =
        "Move Speed: " + actualStats.moveSpeed;

    Inside your Might setter, change it to:

    GameManager.instance.currentMightDisplay.text =
        "Might: " + actualStats.might;

    You currently update currentRecoveryDisplay in both setters.

    There are also a few issues in the PDF’s code that you should correct. These are present in the guide itself.

    9. PlayerInventory.cs — use Count instead of Capacity

    In both Add() overloads, use:

    for (int i = 0; i < weaponSlots.Count; i++)

    And:

    for (int i = 0; i < passiveSlots.Count; i++)

    Capacity is the amount of storage reserved by the list, whereas Count is the number of entries you can access.

    Also, new List<Slot>(6) does not create six slots. Make sure you have populated your weapon and passive slot lists in the Inspector and assigned their images.

    10. PlayerInventory.cs — check for null items in Get() and Remove()

    Before accessing a weapon’s .data, check that the weapon exists:

    if (w != null && w.data == type)
        return w;

    For passives:

    if (p != null && p.data == type)
        return p;

    Apply the same checks in your Remove() overloads, using data instead of type:

    if (w != null && w.data == data)
    if (p != null && p.data == data)

    Otherwise, checking an empty slot can produce a NullReferenceException.

    11. PlayerInventory.cs — remove passives from the correct list

    Your Remove(PassiveData data, ...) method searches weaponSlots. Replace that method with:

    public bool Remove(
        PassiveData data,
        bool removeUpgradeAvailability = false)
    {
        if (removeUpgradeAvailability)
            availablePassives.Remove(data);
    
        for (int i = 0; i < passiveSlots.Count; i++)
        {
            Passive p = passiveSlots[i].item as Passive;
    
            if (p != null && p.data == data)
            {
                passiveSlots[i].Clear();
                p.OnUnequip();
                Destroy(p.gameObject);
                return true;
            }
        }
    
        return false;
    }

    12. PlayerInventory.cs — exclude max-level items from upgrade choices

    The upgrade loops above restore the guide’s structure, but the guide has another issue: a max-level item sets isLevelUp to false and falls through to Add(), potentially offering a duplicate.

    After you create the two temporary upgrade lists at the beginning of ApplyUpgradeOptions(), add:

    availableWeaponUpgrades.RemoveAll(data =>
    {
        if (data == null) return true;
    
        Weapons weapon = Get(data);
        return weapon != null && weapon.currentLevel >= data.maxLevel;
    });
    
    availablePassiveItemUpgrades.RemoveAll(data =>
    {
        if (data == null) return true;
    
        Passive passive = Get(data);
        return passive != null && passive.currentLevel >= data.maxLevel;
    });

    Fix the null checks in Get() from point 10 before adding this.

    13. Check your projectile weapon filename

    You labelled the script Projectile.cs, but the class you posted is:

    public class ProjectileWeapon : Weapons

    If that is its actual filename, rename it to ProjectileWeapon.cs. Your separate Projectile class should remain in Projectile.cs.

    Start by fixing point 1, then run your game and check the Console. If your knives still do not spawn, check that your character has a starting weapon assigned, and that the weapon data has the correct behaviour and projectile prefab selected.

    #19938
    Khôi Đỗ
    Level 1
    Participant
    Helpful?
    Up
    0
    ::

    Thank you, you are the GOAT. The problem has been fixed. But there is a thing that happens inside the PlayerInventory.cs, the variable “isLevelUp” is assigned but its value is never used. I’m wondering what goes wrong with it, I attached the code below. But thanks again, my character is now worked, able to shoot projectile.

    [PlayerInventory.cs]

    void ApplyUpgradeOptions()
    {
        List<WeaponData> availableWeaponUpgrades = new List<WeaponData>(availableWeapons);
    
        availableWeaponUpgrades.RemoveAll(data =>
        {
            if (data == null) return true;
    
            Weapons weapon = Get(data);
            return weapon != null && weapon.currentLevel >= data.maxLevel;
        });
    
        List<PassiveData> availablePassiveItemUpgrades = new List<PassiveData>(availablePassives);
    
        availablePassiveItemUpgrades.RemoveAll(data =>
        {
            if (data == null) return true;
    
            Passive passive = Get(data);
            return passive != null && passive.currentLevel >= data.maxLevel;
        });
    
        foreach (UpgradeUI upgradeOption in upgradeUIOptions)
        {
            if(availableWeaponUpgrades.Count == 0 && availablePassiveItemUpgrades.Count == 0)
                return;
    
    
    
            int upgradeType;
            if(availableWeaponUpgrades.Count == 0)
            {
                upgradeType = 2;
            }
            else if (availablePassiveItemUpgrades.Count == 0)
            {
                upgradeType = 1;
            }
            else
            {
                upgradeType = UnityEngine.Random.Range(1, 3);
            }
    
    
            if(upgradeType == 1)
            {
                WeaponData chosenWeaponUpgrade = availableWeaponUpgrades[UnityEngine.Random.Range(0, availableWeaponUpgrades.Count)];
                availableWeaponUpgrades.Remove(chosenWeaponUpgrade);
    
    
    
                if(chosenWeaponUpgrade != null)
                {
                    EnableUpgradeUI(upgradeOption);
    
    
    
                    bool isLevelUp = false;
                    for (int i = 0; i < weaponSlots.Count; i++)
                    {
                        Weapons w = weaponSlots[i].item as Weapons;
    
                        if (w != null && w.data == chosenWeaponUpgrade)
                        {
                            if (chosenWeaponUpgrade.maxLevel <= w.currentLevel)
                            {
                                isLevelUp = false;
                                break;
                            }
    
                            int slotIndex = i;
                            upgradeOption.upgradeButton.onClick.AddListener(
                                () => LevelUpWeapon(slotIndex, slotIndex));
    
                            Weapons.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;
                        }
                    }
                }
    
            }
            else if (upgradeType == 2)
            {
                PassiveData chosenPassiveUpgrade = availablePassiveItemUpgrades[UnityEngine.Random.Range(0, availablePassiveItemUpgrades.Count)];
                availablePassiveItemUpgrades.Remove(chosenPassiveUpgrade);
    
                if (chosenPassiveUpgrade != null)
                {
                    EnableUpgradeUI(upgradeOption);
    
    
                    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 (chosenPassiveUpgrade.maxLevel <= p.currentLevel)
                            {
                                isLevelUp = false;
                                break;
                            }
    
                            int slotIndex = i;
                            upgradeOption.upgradeButton.onClick.AddListener(
                                () => LevelUpPassiveItem(slotIndex, slotIndex));
    
                            Passive.Modiier nextLevel =
                                chosenPassiveUpgrade.GetLevelData(p.currentLevel + 1);
    
                            upgradeOption.upgradeDescriptionDisplay.text = nextLevel.description;
                            upgradeOption.upgradeNameDisplay.text = nextLevel.name;
                            upgradeOption.upgradeIcon.sprite = chosenPassiveUpgrade.icon;
    
                            isLevelUp = true;
                            break;
                        }
                    }
                }
    
    
            }
    
        }
    #19939
    Terence
    Level 32
    Keymaster
    Helpful?
    Up
    0
    ::

    Hi Khoi, glad your character is shooting projectiles now!

    The warning appears because you assign isLevelUp, but never check its value. You’ve left out the if (!isLevelUp) blocks that should come after the weapon and passive for loops.

    These blocks handle adding an item you don’t already own. Without them, your existing items can be upgraded, but new-item options won’t have their descriptions or button actions configured.

    For weapons, add this immediately after the weapon for loop, inside if (chosenWeaponUpgrade != null):

    if (!isLevelUp)
    {
        upgradeOption.upgradeButton.onClick.AddListener(
            () => Add(chosenWeaponUpgrade));
    
        upgradeOption.upgradeDescriptionDisplay.text =
            chosenWeaponUpgrade.baseStats.description;
    
        upgradeOption.upgradeNameDisplay.text =
            chosenWeaponUpgrade.baseStats.name;
    
        upgradeOption.upgradeIcon.sprite = chosenWeaponUpgrade.icon;
    }

    For passives, add this immediately after the passive for loop, inside if (chosenPassiveUpgrade != null):

    if (!isLevelUp)
    {
        upgradeOption.upgradeButton.onClick.AddListener(
            () => Add(chosenPassiveUpgrade));
    
        upgradeOption.upgradeDescriptionDisplay.text =
            chosenPassiveUpgrade.baseStats.description;
    
        upgradeOption.upgradeNameDisplay.text =
            chosenPassiveUpgrade.baseStats.name;
    
        upgradeOption.upgradeIcon.sprite = chosenPassiveUpgrade.icon;
    }

    Your RemoveAll() filters already exclude max-level items. Adding these two blocks will restore the new-item options and resolve the unused-variable warning.

    #19940
    Khôi Đỗ
    Level 1
    Participant
    Helpful?
    Up
    0
    ::

    Thank you, Terence. My Knife-character working great now.

Viewing 5 posts - 1 through 5 (of 5 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: