Forum begins after the advertisement:
[Part 27] Problem with Weapons/Passives
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [Part 27] Problem with Weapons/Passives
- This topic has 12 replies, 3 voices, and was last updated 1 week ago by
Terence.
-
AuthorPosts
-
August 17, 2025 at 11:45 pm #18645::
After updating the “PlayerInventory.cs” there is a problem with upgrading weapons and passives. When an item is already at maximum level (like 6), it still appears in the upgrade selection with no body text but the “MAX” Text in the top right corner. You can select this item and then it’s 1 level above the maximum level (like 7). How can I fix this?
EDIT: Maybe it is connected to the “TreasureChest.cs” where this line
ItemData.Evolution[] possibleEvolutions = i.CanEvolve(0);
makes problems, the error is: Argument “1”: Conversion from “int” to “ItemData.Evolution” not possible.I had to change it to
ItemData.Evolution[] possibleEvolutions = i.CanEvolve();
in order to not create this error.EDIT 2: Same for PlayerInventory.cs, line 397 causes the same problem, Argument “1”: Conversion from “int” to “ItemData.Evolution” not possible.
if (s.item is T t && t.CanEvolve(0).Length > 0) result.Add(t);
if I cahnge it to
if (s.item is T t && t.CanEvolve().Length > 0) result.Add(t);
the error is not there.August 17, 2025 at 11:50 pm #18646::Thanks for flagging this Grim. Let me have a look at this as soon as possible and get back to you.
@Ser.Apustaja, in the meantime, do you see any issue that the updated
PlayerInventory
below would cause that would cause an item that is already at maximum level (like 6) to still appear in the upgrade selection (with no body text but the “MAX” Text in the top right corner)?The item can still be selected, and then it’s 1 level above the maximum level (like 7). If there is an issue, how do you fix this?
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using System.Linq; public class PlayerInventory : MonoBehaviour { [System.Serializable] public class Slot { public Item item; public void Assign(Item assignedItem) { item = assignedItem; if (item is Weapon) { Weapon w = item as Weapon; } else { Passive p = item as Passive; } Debug.Log(string.Format("Assigned {0} to player.", item.name)); } public void Clear() { item = null; } public bool IsEmpty() { return item == null; } } public List<Slot> weaponSlots = new List<Slot>(6); public List<Slot> passiveSlots = new List<Slot>(6); public UIInventoryIconsDisplay weaponUI, passiveUI; [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 UIUpgradeWindow upgradeWindow; 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 a certain type in the inventory. public Passive Get(PassiveData type) { foreach (Slot s in passiveSlots) { Passive p = s.item as Passive; if (p && 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 && w.data == type) return w; } return null; } // Removes a weapon of a particular type, as specified by . 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.transform.SetParent(transform); //Set the weapon to be a child of the player spawnedWeapon.transform.localPosition = Vector2.zero; spawnedWeapon.Initialise(data); spawnedWeapon.OnEquip(); // Assign the weapon to the slot. weaponSlots[slotNum].Assign(spawnedWeapon); weaponUI.Refresh(); // 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; // Assign the passive to the slot. passiveSlots[slotNum].Assign(p); passiveUI.Refresh(); 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; } // Overload so that we can use both ItemData or Item to level up an // item in the inventory. public bool LevelUp(ItemData data) { Item item = Get(data); if (item) return LevelUp(item); return false; } // Levels up a selected weapon in the player inventory. public bool LevelUp(Item item) { // Tries to level up the item. if (!item.DoLevelUp()) { Debug.LogWarning(string.Format( "Failed to level up {0}.", item.name )); return false; } // Update the UI after the weapon has levelled up. weaponUI.Refresh(); passiveUI.Refresh(); // Close the level up screen afterwards. if (GameManager.instance != null && GameManager.instance.choosingUpgrade) { GameManager.instance.EndLevelUp(); } // If it is a passive, recalculate player stats. if (item is Passive) player.RecalculateStats(); return true; } // Get all the slots from the player of a certain type, // either Weapon or Passive. If you get all slots of Item, // it will return all Weapons and Passives. public Slot[] GetSlots<T>() where T : Item { // Check which set of slots to return. // If you get Items, it will give you both weapon and passive slots. switch (typeof(T).ToString()) { case "Passive": return passiveSlots.ToArray(); case "Weapon": return weaponSlots.ToArray(); case "Item": List<Slot> s = new List<Slot>(passiveSlots); s.AddRange(weaponSlots); return s.ToArray(); } // If you have other subclasses of Item, you will need to add extra cases above to // prevent this message from triggering. This message is here to help developers pinpoint // the part of the code they need to update. Debug.LogWarning("Generic type provided to GetSlots() call does not have a coded behaviour."); return null; } public Slot[] GetSlotsFor<T>() where T : ItemData { if (typeof(T) == typeof(PassiveData)) { return passiveSlots.ToArray(); } else if (typeof(T) == typeof(WeaponData)) { return weaponSlots.ToArray(); } else if (typeof(T) == typeof(ItemData)) { List<Slot> s = new List<Slot>(passiveSlots); s.AddRange(weaponSlots); return s.ToArray(); } // If you have other subclasses of Item, you will need to add extra cases above to // prevent this message from triggering. This message is here to help developers pinpoint // the part of the code they need to update. Debug.LogWarning("Generic type provided to GetSlotsFor() call does not have a coded behaviour."); return null; } // Checks a list of slots to see if there are any slots left. int GetSlotsLeft(List<Slot> slots) { int count = 0; foreach (Slot s in slots) { if (s.IsEmpty()) count++; } return count; } // Generic variants of GetSlotsLeft(), which is easier to use. public int GetSlotsLeft<T>() where T : Item { return GetSlotsLeft(new List<Slot>( GetSlots<T>() )); } public int GetSlotsLeftFor<T>() where T : ItemData { return GetSlotsLeft(new List<Slot>( GetSlotsFor<T>() )); } public T[] GetAvailable<T>() where T : ItemData { if (typeof(T) == typeof(PassiveData)) { return availablePassives.ToArray() as T[]; } else if (typeof(T) == typeof(WeaponData)) { return availableWeapons.ToArray() as T[]; } else if (typeof(T) == typeof(ItemData)) { List<ItemData> list = new List<ItemData>(availablePassives); list.AddRange(availableWeapons); return list.ToArray() as T[]; } Debug.LogWarning("Generic type provided to GetAvailable() call does not have a coded behaviour."); return null; } // Get all available items (weapons or passives) that we still do not have yet. public T[] GetUnowned<T>() where T : ItemData { // Get all available items. var available = GetAvailable<T>(); if (available == null || available.Length == 0) return new T[0]; // Return empty array if null or empty List<T> list = new List<T>(available); // Check all of our slots, and remove all items in the list that are found in the slots. var slots = GetSlotsFor<T>(); if (slots != null) { foreach (Slot s in slots) { if (s?.item?.data != null && list.Contains(s.item.data as T)) list.Remove(s.item.data as T); } } return list.ToArray(); } public T[] GetEvolvables<T>() where T : Item { // Check all the slots, and find all the items in the slot that // are capable of evolving. List<T> result = new List<T>(); foreach (Slot s in GetSlots<T>()) if (s.item is T t && t.CanEvolve(0).Length > 0) result.Add(t); return result.ToArray(); } public T[] GetUpgradables<T>() where T : Item { // Check all the slots, and find all the items in the slot that // are still capable of levelling up. List<T> result = new List<T>(); foreach (Slot s in GetSlots<T>()) if (s.item is T t && t.CanLevelUp()) result.Add(t); return result.ToArray(); } // Determines what upgrade options should appear. void ApplyUpgradeOptions() { // <availableUpgrades> is an empty list that will be filtered from // <allUpgrades>, which is the list of ALL upgrades in PlayerInventory. // Not all upgrades can be applied, as some may have already been // maxed out the player, or the player may not have enough inventory slots. List<ItemData> availableUpgrades = new List<ItemData>(); List<ItemData> allUpgrades = new List<ItemData>(availableWeapons); allUpgrades.AddRange(availablePassives); // We need to know how many weapon / passive slots are left. int weaponSlotsLeft = GetSlotsLeft(weaponSlots); int passiveSlotsLeft = GetSlotsLeft(passiveSlots); // Filters through the available weapons and passives and add those // that can possibly be an option. foreach (ItemData data in allUpgrades) { // If a weapon of this type exists, allow for the upgrade if the // level of the weapon is not already maxed out. Item obj = Get(data); if (obj) { if (obj.currentLevel <= data.maxLevel) availableUpgrades.Add(data); } else { // If we don't have this item in the inventory yet, check if // we still have enough slots to take new items. if (data is WeaponData && weaponSlotsLeft > 0) availableUpgrades.Add(data); else if (data is PassiveData && passiveSlotsLeft > 0) availableUpgrades.Add(data); } } // Show the UI upgrade window if we still have available upgrades left. int availUpgradeCount = availableUpgrades.Count; if (availUpgradeCount > 0) { bool getExtraItem = 1f - 1f / player.Stats.luck > UnityEngine.Random.value; if (getExtraItem || availUpgradeCount < 4) upgradeWindow.SetUpgrades(this, availableUpgrades, 4); else upgradeWindow.SetUpgrades(this, availableUpgrades, 3, "Increase your Luck stat for a chance to get 4 items!"); } else if (GameManager.instance != null && GameManager.instance.choosingUpgrade) { GameManager.instance.EndLevelUp(); } } public void RemoveAndApplyUpgrades() { ApplyUpgradeOptions(); } }
August 18, 2025 at 1:15 am #18647::Maybe this also helps: The “WeaponData.cs” creates a Debug warning “Weapon doesn’t have its level up stats configured for Level X” X is the next level that doesn’t exist.
August 18, 2025 at 1:35 am #18652::In the
ApplyUpgradeOptions()
method, the conditionif (obj.currentLevel <= data.maxLevel)
is incorrect. This allows items that have already reached the maximum level to still be added toavailableUpgrades
.To fix this, change the condition to:
if (obj.currentLevel < data.maxLevel) availableUpgrades.Add(data);
By using
<
instead of<=
, you ensure that only items with levels below the maximum level are added to the upgrade options, preventing max-level items from appearing in the upgrade selection.has upvoted this post. August 18, 2025 at 1:40 am #18655::Based on the reference information and the query, the solution is to modify the condition in the
ApplyUpgradeOptions()
method from:if (obj.currentLevel <= data.maxLevel) availableUpgrades.Add(data);
to:
if (obj.currentLevel < data.maxLevel) availableUpgrades.Add(data);
This change ensures that items at maximum level are not added to the upgrade selection, preventing the issue of selecting a max-level item and potentially going beyond its maximum level.
August 18, 2025 at 2:40 am #18660::Okay, that seems to fix the problem! Thank you AI Assistant Alp Apustaja.
But what about “
if (s.item is T t && t.CanEvolve(0).Length > 0) result.Add(t);
“? Why has it to be “if (s.item is T t && t.CanEvolve().Length > 0) result.Add(t);
” ?August 18, 2025 at 2:45 am #18662::The reason for changing
t.CanEvolve(0)
tot.CanEvolve()
is likely due to the method signature ofCanEvolve
in yourItemData
class. The original code seems to be passing an argument to theCanEvolve
method, but the system is indicating that it cannot convert anint
to the expected type, which suggests that the method does not accept parameters.By removing the argument and calling
t.CanEvolve()
instead, you are likely calling a version of the method that does not require any parameters, which resolves the type conversion error you encountered.To summarize:
- Original:
t.CanEvolve(0)
– trying to pass an integer which is not expected by the method. - Updated:
t.CanEvolve()
– calls the method without parameters, conforming to its expected usage.
Make sure to check the implementation of the
CanEvolve
method in yourItemData
class to confirm that it indeed does not require parameters. This will help ensure that your code works correctly and avoids similar issues in the future.August 18, 2025 at 2:46 am #18664August 18, 2025 at 2:50 am #18665::I’m sorry, but I cannot provide the complete code for your
ItemData.cs
andItem.cs
files as I don’t have access to your specific codebase or its context. However, I can help you with a general structure or common patterns for these types of classes based on typical game development practices.Example Structure for
Item.cs
using UnityEngine; public class Item : MonoBehaviour { public ItemData data; // Reference to the ItemData associated with this item public int currentLevel; // Current level of the item public virtual bool DoLevelUp() { if (currentLevel < data.maxLevel) { currentLevel++; // Logic for leveling up the item, e.g., increasing stats return true; } return false; } public virtual ItemData.Evolution[] CanEvolve() { // Logic to determine if the item can evolve return new ItemData.Evolution[0]; // Placeholder } }
Example Structure for
ItemData.cs
using System; [Serializable] public class ItemData { public string itemName; // Name of the item public int maxLevel; // Maximum level the item can reach // Example of evolution data [Serializable] public class Evolution { public string newItemName; // New name after evolution public int requiredLevel; // Level required to evolve } public Evolution[] evolutions; // Possible evolutions for the item }
Notes:
- The
Item
class is a MonoBehaviour that represents the actual item in the game world. It holds the current level and has methods for leveling up and checking if it can evolve. - The
ItemData
class holds the metadata for the item, including its name, maximum level, and possible evolutions. - You will need to adjust these examples to fit your specific game mechanics, properties, and methods.
If you have specific requirements or functionality you want included, please let me know, and I can help you refine the example further!
August 18, 2025 at 12:47 pm #18675::Okay, that seems to fix the problem! Thank you AI Assistant Alp Apustaja.
But what about “if (s.item is T t && t.CanEvolve(0).Length > 0) result.Add(t);“? Why has it to be “if (s.item is T t && t.CanEvolve().Length > 0) result.Add(t);” ?
Grim, apologies, there is an undocumented change to the
CanEvolve()
function in the item script that Alp wasn’t aware of (I haven’t updated his knowledge base yet):// Call this function to get all the evolutions that the weapon // can currently evolve to. public virtual ItemData.Evolution[] CanEvolve(int levelUpAmount = 1) { 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, levelUpAmount)) possibleEvolutions.Add(e); } return possibleEvolutions.ToArray(); }
The reason this update was made is to support
CanEvolve(0)
in the newPlayerInventory
script. You can see the updated explanation here in the article: https://blog.terresquall.com/2025/08/creating-a-rogue-like-vampire-survivors-part-27/#updated-Item-scriptThe summary of it is that in our older codebase,
CanEvolve()
always added a level to the item before checking if it could evolve, which isn’t something that we want here. We want to check if the item can evolve at its current level — hence the update to theCanEvolve()
function.This change above should fix your problem — I don’t think you need to implement what Alp suggested.
August 18, 2025 at 12:51 pm #18676::@Grim I’ve awarded you with 2 badges as thanks for flagging this issue–I’ll be updating the article and will likely also release a Part 27.5 video to address this.
Will appreciate if you can let me know if you find anything else!
You can see the badges on your profile: https://blog.terresquall.com/community/users/patreon_142113688/
August 18, 2025 at 3:35 pm #18678August 21, 2025 at 3:39 pm #18742::To prevent the
PlayerInventory
from issuing weapons that we already have maxed out, we need to remove a single character from theApplyUpgradeOptions()
function:// Determines what upgrade options should appear. void ApplyUpgradeOptions() { // <availableUpgrades> is an empty list that will be filtered from // <allUpgrades>, which is the list of ALL upgrades in PlayerInventory. // Not all upgrades can be applied, as some may have already been // maxed out the player, or the player may not have enough inventory slots. List<ItemData> availableUpgrades = new List<ItemData>(); List<ItemData> allUpgrades = new List<ItemData>(availableWeapons); allUpgrades.AddRange(availablePassives); // We need to know how many weapon / passive slots are left. int weaponSlotsLeft = GetSlotsLeft(weaponSlots); int passiveSlotsLeft = GetSlotsLeft(passiveSlots); // Filters through the available weapons and passives and add those // that can possibly be an option. foreach (ItemData data in allUpgrades) { // If a weapon of this type exists, allow for the upgrade if the // level of the weapon is not already maxed out. Item obj = Get(data); if (obj) { if (obj.currentLevel <
=data.maxLevel) availableUpgrades.Add(data); } else { // If we don't have this item in the inventory yet, check if // we still have enough slots to take new items. if (data is WeaponData && weaponSlotsLeft > 0) availableUpgrades.Add(data); else if (data is PassiveData && passiveSlotsLeft > 0) availableUpgrades.Add(data); } } // Show the UI upgrade window if we still have available upgrades left. int availUpgradeCount = availableUpgrades.Count; if (availUpgradeCount > 0) { bool getExtraItem = 1f - 1f / player.Stats.luck > UnityEngine.Random.value; if (getExtraItem || availUpgradeCount < 4) upgradeWindow.SetUpgrades(this, availableUpgrades, 4); else upgradeWindow.SetUpgrades(this, availableUpgrades, 3, "Increase your Luck stat for a chance to get 4 items!"); } else if (GameManager.instance != null && GameManager.instance.choosingUpgrade) { GameManager.instance.EndLevelUp(); } }Specifically, this is the line:
if (obj.currentLevel <
=data.maxLevel) availableUpgrades.Add(data);Previously, when considering whether a weapon is a legitimate option, we would check to see if it was less than or equal to the max level, but if an item is equal to the max level, it should never be upgradable.
- 1 anonymous person
- Original:
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: