Forum begins after the advertisement:
[Part 15] Problem with Passive item
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [Part 15] Problem with Passive item
- This topic has 4 replies, 2 voices, and was last updated 2 weeks, 2 days ago by
Alp Apustaja.
-
AuthorPosts
-
June 25, 2025 at 1:43 pm #18374::
This happen when I choose a new passive (example: when I choose wing passive there only one buff in it which is move speed buff set to 10 the other buff I dont write any number in it, after choose the wing every stats of the player set to exactly like the wing stats) Any Idea how to fix this problem
June 25, 2025 at 1:50 pm #18375::Based on the reference information provided, the issue seems to be related to how passive items are applying their modifiers. The current implementation might be resetting all stats instead of incrementally modifying them.
To fix this, you should modify the passive item application logic to:
- Use incremental modifications (multipliers or additions) instead of direct replacements
- Ensure only specified stats are modified
- Preserve other stat values when applying a new passive item
However, the exact implementation details are NOT_FOUND in the given reference information. I would need to see the full implementation of the PassiveItem script and how passive items are being applied to provide a precise solution.
Clarifying question: Can you share the full code for your PassiveItem script and how you’re currently applying passive item modifiers?
June 25, 2025 at 1:59 pm #18376::using UnityEngine; //replacement of passiveitemscriptableobject class [CreateAssetMenu(fileName = "Passive Data", menuName = "2D Top-down Rogue-like/Passive Data")] public class PassiveData : ItemData { public Passive.Modifier baseStats; public Passive.Modifier[] growth; public Passive.Modifier GetLevelData(int Level) { if (Level - 2 < growth.Length) { return growth[Level - 2]; } Debug.LogWarning(string.Format("Passive doesnt habe its level up stats config", Level)); return new Passive.Modifier(); } }
using UnityEngine; //a class that takes an passivedata and used to increase a player stat public class Passive : Item { public PassiveData data; [SerializeField] CharacterData.Stats currentBoosts; [System.Serializable] public struct Modifier { public string name, description; public CharacterData.Stats boosts; } public virtual void Initialise(PassiveData data) { base.Initialise(data); this.data = data; currentBoosts = data.baseStats.boosts; } public virtual CharacterData.Stats GetBoosts() { return currentBoosts; } public override bool DoLevelUp() { base.DoLevelUp(); //prevent levelup when max level if (!CanLevelUp()) { Debug.LogWarning(string.Format("cannot level up [0] to level [1], max level reached", name, currentLevel, data.maxLevel)); return false; } currentBoosts += data.GetLevelData(++currentLevel).boosts; return true; } }
June 25, 2025 at 2:01 pm #18377::Based on the code provided, the issue is likely in the WingsPassiveItem script. The current implementation directly sets the player’s move speed instead of incrementally modifying it.
Here’s a corrected version of the WingsPassiveItem script:
protected override void ApplyModifier() { // Increment move speed by the multiplier percentage player.currentMoveSpeed += player.currentMoveSpeed * (passiveItemData.Multiplier / 100f); }
This change ensures that:
- The existing move speed is preserved
- The new speed is calculated as an incremental increase
- Other stats remain unchanged
Would you like me to elaborate on how this modification addresses the stat reset issue?
June 25, 2025 at 2:15 pm #18378::using UnityEngine; //a class that takes an passivedata and used to increase a player stat public class Passive : Item { public PassiveData data; [SerializeField] CharacterData.Stats currentBoosts; [System.Serializable] public struct Modifier { public string name, description; public CharacterData.Stats boosts; } public virtual void Initialise(PassiveData data) { base.Initialise(data); this.data = data; currentBoosts = data.baseStats.boosts; } public virtual CharacterData.Stats GetBoosts() { return currentBoosts; } public override bool DoLevelUp() { base.DoLevelUp(); //prevent levelup when max level if (!CanLevelUp()) { Debug.LogWarning(string.Format("cannot level up [0] to level [1], max level reached", name, currentLevel, data.maxLevel)); return false; } currentBoosts += data.GetLevelData(++currentLevel).boosts; return true; } }
this is the current way of the passive level up and apply buff
using UnityEngine; //replacement of passiveitemscriptableobject class [CreateAssetMenu(fileName = "Passive Data", menuName = "2D Top-down Rogue-like/Passive Data")] public class PassiveData : ItemData { public Passive.Modifier baseStats; public Passive.Modifier[] growth; public Passive.Modifier GetLevelData(int Level) { if (Level - 2 < growth.Length) { return growth[Level - 2]; } Debug.LogWarning(string.Format("Passive doesnt habe its level up stats config", Level)); return new Passive.Modifier(); } }
this is the passivedata currenly not using any other passive script beside this
has upvoted this post. June 25, 2025 at 2:16 pm #18379::Based on the provided code, the issue might be in how the initial passive stats are set and applied. The current implementation sets
currentBoosts
todata.baseStats.boosts
during initialization, which could potentially replace all existing stats.To fix this, modify the
Initialise
method to incrementally add stats instead of replacing them:public virtual void Initialise(PassiveData data) { base.Initialise(data); this.data = data; // Instead of directly setting currentBoosts, add the base stats if (Owner != null && Owner.Stats != null) { Owner.Stats.moveSpeed += data.baseStats.boosts.moveSpeed; Owner.Stats.health += data.baseStats.boosts.health; // Add other stat increments similarly } currentBoosts = data.baseStats.boosts; }
This approach ensures that:
- Existing stats are preserved
- New passive stats are added incrementally
- The
currentBoosts
still tracks the passive item’s modifications
Would you like me to elaborate on how this modification prevents stat replacement?
has upvoted this post. June 25, 2025 at 2:38 pm #18380::using System.Collections.Generic; using TMPro; using Unity.VisualScripting; using UnityEngine; using UnityEngine.UI; using System; public class PlayerStats : MonoBehaviour { CharacterData characterData; public CharacterData.Stats baseStats; [SerializeField] CharacterData.Stats actualStats; float health; #region Current State Properties public float CurrentHealth { get { return health; } set { //check if the value has change 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 { //check if the value has change 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 { //check if the value has change 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 { //check if the value has change if (actualStats.moveSpeed != value) { actualStats.moveSpeed = value; if (GameManager.instance != null) { GameManager.instance.currentMoveSpeedDisplay.text = "Move Speed: " + actualStats.moveSpeed; } } } } public float CurrentMight { get { return Might; } set { Might = value; } } public float Might { get { return actualStats.might; } set { //check if the value has change if (actualStats.might!= value) { actualStats.might = value; if (GameManager.instance != null) { GameManager.instance.currentMightDisplay.text = "Might: " + actualStats.might; } } } } public float CurrentProjectileSpeed { get { return Speed; } set { Speed = value; } } public float Speed { get { return actualStats.speed; } set { //check if the value has change if (actualStats.speed != value) { actualStats.speed = value; if (GameManager.instance != null) { GameManager.instance.currentProjectileSpeedDisplay.text = "Projectile Speed: " + actualStats.speed; } } } } public float CurrentMagnet { get { return Magnet; } set { Magnet = value; } } public float Magnet { get { return actualStats.magnet; } set { //check if the value has change 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 for the player [Header("Experience/Level")] public int experience = 0; public int level = 1; public int experienceCap; //Class for defining a level range and the corresponding experience cap increase for that range [System.Serializable] public class LevelRange { public int startLevel; public int endLevel; public int experienceCapIncrease; } //I-Frames [Header("I-Frames")] public float invincibilityDuration; float invincibilityTimer; 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 = CharacterSelector.GetData(); CharacterSelector.instance.DestroySingleton(); inventory = GetComponent<PlayerInventory>(); //assign the variables baseStats = actualStats = characterData.stats; health = actualStats.maxHealth; } void Start() { //spawn starting weapon inventory.Add(characterData.StartingWeapon); //initialize the first level cap experienceCap = levelRanges[0].experienceCapIncrease; //set the current stats display 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() { if (invincibilityTimer > 0) { invincibilityTimer -= Time.deltaTime; } //If the invincibility timer reach 0, set 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) { //check the player when player is not invincible will take damage, reduce health and change to invincible if (!isInvincible) { CurrentHealth -= dmg; if (damageEffect) Destroy(Instantiate(damageEffect, transform.position, Quaternion.identity), 5f); invincibilityTimer = invincibilityDuration; isInvincible = true; if (CurrentHealth <= 0) { Kill(); } UpdateHealthBar(); } } void UpdateHealthBar() { //Update the Health Bar healthBar.fillAmount = CurrentHealth / actualStats.maxHealth; } public void Kill() { if (!GameManager.instance.isGameOver) { GameManager.instance.AssignLevelReachedUI(level); GameManager.instance.AssignChosenWeaponsAndPassiveItemsUI(inventory.weaponSlots, inventory.passiveSlots); GameManager.instance.GameOver(); } } public void RestoreHealth(float amount) { //only heal when not full health if (CurrentHealth < actualStats.maxHealth) { CurrentHealth += amount; if (CurrentHealth > actualStats.maxHealth) { CurrentHealth = actualStats.maxHealth; } } } void Recover() { if (CurrentHealth < actualStats.maxHealth) { CurrentHealth += CurrentRecovery * Time.deltaTime; //doesnt recovery over the max health if (CurrentHealth > actualStats.maxHealth) { CurrentHealth = actualStats.maxHealth; } } } [System.Obsolete("old function kept to maintain compatibility")] public void SpawnWeapon(GameObject weapon) { //check if slot are full, and return when it is if (weaponIndex >= inventory.weaponSlots.Count - 1) { Debug.Log("Inventory slots already full"); return; } //spawn the starting weapon GameObject spawnedWeapon = Instantiate(weapon, transform.position, Quaternion.identity); spawnedWeapon.transform.SetParent(transform); //set weapon to be a child of the player //inventory.AddWeapon(weaponIndex, spawnedWeapon.GetComponent<WeaponController>()); //add waepon to it inventory slot index weaponIndex++; } [System.Obsolete("no need to spawn items directly now")] public void SpawnPassiveItem(GameObject passiveItem) { //check if slot are full, and return when it is if (passiveItemIndex >= inventory.passiveSlots.Count - 1) { Debug.Log("Inventory slots already full"); return; } //spawn the starting passive item GameObject spawnedPassiveItem = Instantiate(passiveItem, transform.position, Quaternion.identity); spawnedPassiveItem.transform.SetParent(transform); //set passive item to be a child of the player //inventory.AddPassiveItem(passiveItemIndex, spawnedPassiveItem.GetComponent<PassiveItem>()); //add passive item to it inventory slot index passiveItemIndex++; } }
Maybe this is the problem for why the passive not modifying?
has upvoted this post. June 25, 2025 at 2:41 pm #18381::The key issue is in the
RecalculateStats()
method. Currently, it’s resettingactualStats
tobaseStats
before adding passive boosts, which could cause the problem you’re experiencing.To fix this, modify the
RecalculateStats()
method to preserve the base stats and incrementally add passive boosts:public void RecalculateStats() { // Create a copy of base stats instead of direct assignment actualStats = new CharacterData.Stats(baseStats); foreach (PlayerInventory.Slot s in inventory.passiveSlots) { Passive p = s.item as Passive; if (p) { // Add boosts incrementally actualStats.moveSpeed += p.GetBoosts().moveSpeed; actualStats.maxHealth += p.GetBoosts().maxHealth; // Add other stat increments similarly } } }
Would you like me to elaborate on how this modification prevents stat replacement?
has upvoted this post. June 25, 2025 at 2:52 pm #18382June 25, 2025 at 2:55 pm #18383::Great! I’m glad the solution helped resolve the issue with passive item stat modifications.
To ensure the fix works correctly, could you confirm:
- Did you implement the changes to
RecalculateStats()
method? - Are all player stats now incrementing correctly when adding passive items?
- Did you need to make any additional modifications to the code?
If you have any further questions or need more detailed assistance, please provide more specific information about the implementation or any remaining issues.
has upvoted this post. -
AuthorPosts
- You must be logged in to reply to this topic.