Forum begins after the advertisement:
[General] Adding Fade and Shrink Effect to HP Bar
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [General] Adding Fade and Shrink Effect to HP Bar
- This topic has 3 replies, 2 voices, and was last updated 1 month, 1 week ago by Kai.
-
AuthorPosts
-
October 14, 2024 at 11:54 am #16062::
I am having problems trying to implement fading and shrinking effect to player’s hp bar to show the amount of damage he took by having a white bar between the border and the hp bar.
When player took damage, the white bar will have its fill amounts alpha increase to 1 then decrease toward 0 after some time, also when player is hit twice in quick succession, the effect will stack and show as a single white bar.October 14, 2024 at 11:58 am #16063::The problem is the effect only work if player is hit twice in quick succession not a single hit from an enemy.
HealthBarFade.csusing UnityEngine; using UnityEngine.UI; public class HealthBarFade : MonoBehaviour { private Image barImage; // The normal health bar image reference private Image damagedBarImage; // The damaged health bar image reference private Color damagedColor; // Color for the damaged bar private const float DAMAGED_HEALTH_FADE_TIMER_MAX = 1f; // Time before fade starts private float damagedHealthFadeTimer; // Timer for fading effect private void Awake() { // Find the two bar image components barImage = transform.Find("bar").GetComponent<Image>(); damagedBarImage = transform.Find("damagedBar").GetComponent<Image>(); // Initialize damaged bar color to be fully visible (alpha = 1) damagedColor = damagedBarImage.color; damagedColor.a = 0f; damagedBarImage.color = damagedColor; } private void Update() { // Start fading if the damaged bar's alpha is greater than 0 if (damagedColor.a > 0) { damagedHealthFadeTimer -= Time.deltaTime; // Once the fade timer is up, start reducing the alpha value if (damagedHealthFadeTimer < 0) { float fadeAmount = 1f; // Fade speed damagedColor.a -= fadeAmount * Time.deltaTime; damagedBarImage.color = damagedColor; } } // Sync the damaged bar with the main bar, but make sure it lags behind if (damagedBarImage.fillAmount > barImage.fillAmount) { float shrinkSpeed = 0.05f; // Controls how fast the damaged bar shrinks towards the actual health bar damagedBarImage.fillAmount -= shrinkSpeed * Time.deltaTime; } } public void SetHealth(float healthNormalized) { // Update the main health bar to the current health value barImage.fillAmount = healthNormalized; // If the damaged bar is not already fading, sync it with the main bar if (damagedColor.a <= 0) { damagedBarImage.fillAmount = barImage.fillAmount; } // Reset the alpha for the damaged bar (fully visible) damagedColor.a = 1f; damagedBarImage.color = damagedColor; // Reset the fade timer damagedHealthFadeTimer = DAMAGED_HEALTH_FADE_TIMER_MAX; } }
SetHealth() is called in TakeDamage() function in PlayerStats.cs
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using System.Collections; public class PlayerStats : EntityStats { CharacterData characterData; public CharacterData.Stats baseStats; [SerializeField] CharacterData.Stats actualStats; public CharacterData.Stats Stats { get { return actualStats; } set { actualStats = value; } } public CharacterData.Stats Actual { get { return actualStats; } } #region Current Stats Properties public float CurrentHealth { get { return health; } // If we try and set the current health, the UI interface // on the pause screen will also be updated. set { //Check if the value has changed if (health != value) { health = value; UpdateHealthBar(); } } } #endregion [Header("Visuals")] public ParticleSystem damageEffect; // If damage is dealt. public ParticleSystem blockedEffect; // If armor completely blocks damage. public GameObject levelUpEffect; //If player level up //Experience and level of 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; PlayerCollector collector; [Header("UI")] public HealthBarFade healthBarFade; public Image healthBar; public Image expBar; public TMP_Text levelText; void Awake() { characterData = CharacterSelector.GetData(); if (CharacterSelector.instance) CharacterSelector.instance.DestroySingleton(); inventory = GetComponent<PlayerInventory>(); collector = GetComponentInChildren<PlayerCollector>(); //Assign the variables baseStats = actualStats = characterData.stats; collector.SetRadius(actualStats.magnet); health = actualStats.maxHealth; //Assign the Animator controller to the existing Animator component Animator animator = GetComponent<Animator>(); if (animator != null) { animator.runtimeAnimatorController = characterData.AnimatorController; } } protected override void Start() { //Spawn the starting weapon inventory.Add(characterData.StartingWeapon); //Initialize the experience cap as the first experience cap increase experienceCap = levelRanges[0].experienceCapIncrease; GameManager.instance.AssignChosenCharacterUI(characterData); UpdateHealthBar(); UpdateExpBar(); UpdateLevelText(); } protected override void Update() { base.Update(); if (invincibilityTimer > 0) { invincibilityTimer -= Time.deltaTime; } //If the invincibility timer has reached 0, set the invincibility flag to false else if (isInvincible) { isInvincible = false; } Recover(); } public override void RecalculateStats() { actualStats = baseStats; foreach (PlayerInventory.Slot s in inventory.passiveSlots) { Passive p = s.item as Passive; if (p) { actualStats += p.GetBoosts(); } } // Create a variable to store all the cumulative multiplier values. CharacterData.Stats multiplier = new CharacterData.Stats { maxHealth = 1f, recovery = 1f, armor = 1f, moveSpeed = 1f, might = 1f, area = 1f, speed = 1f, duration = 1f, amount = 1, cooldown = 1f, luck = 1f, growth = 1f, greed = 1f, curse = 1f, magnet = 1f, revival = 1 }; // We have to account for the buffs from EntityStats as well. foreach (Buff b in activeBuffs) { BuffData.Stats bd = b.GetData(); switch (bd.modifierType) { case BuffData.ModifierType.additive: actualStats += bd.playerModifier; break; case BuffData.ModifierType.multiplicative: multiplier *= bd.playerModifier; break; } } actualStats *= multiplier; // Update the PlayerCollector's radius. collector.SetRadius(actualStats.magnet); } public void IncreaseExperience(int amount) { experience += amount; LevelUpChecker(); UpdateExpBar(); } void LevelUpChecker() { if (experience >= experienceCap) { //Level up the player and reduce their experience by the experience cap level++; experience -= experienceCap; // Play the level up effect if it exists if (levelUpEffect != null) { // Activate the level up effect GameObject levelUpEffect.SetActive(true); Animator levelUpAnimator = levelUpEffect.GetComponent<Animator>(); if (levelUpAnimator != null) { // Trigger the level up animation levelUpAnimator.SetTrigger("LevelUp"); } } //Find the experience cap increase for the current level range int experienceCapIncrease = 0; foreach (LevelRange range in levelRanges) { if (level >= range.startLevel && level <= range.endLevel) { experienceCapIncrease = range.experienceCapIncrease; break; } } experienceCap += experienceCapIncrease; UpdateLevelText(); // Start a coroutine to wait for a few seconds before switching to the StartLevelUp state StartCoroutine(DelayedStartLevelUp()); // If the experience still exceeds the experience cap, level up again. if (experience >= experienceCap) LevelUpChecker(); } } IEnumerator DelayedStartLevelUp() { // Wait for a few seconds to allow the level up effect to play yield return new WaitForSeconds(1.5f); // Adjust the duration as needed // Switch to the StartLevelUp game state GameManager.instance.StartLevelUp(); } void UpdateExpBar() { // Update exp bar fill amount expBar.fillAmount = (float)experience / experienceCap; } public void UpdateLevelText() { // Update level text levelText.text = $"{level}"; } public override void TakeDamage(float dmg) { //If the player is not currently invincible, reduce health and start invincibility if (!isInvincible) { dmg -= actualStats.armor; if (dmg > 0) { Debug.Log("Player took damage: " + dmg); // Deal the damage. CurrentHealth -= dmg; healthBarFade.SetHealth(CurrentHealth / actualStats.maxHealth); invincibilityTimer = invincibilityDuration; isInvincible = true; // If there is a damage effect assigned, play it. if (damageEffect) Destroy(Instantiate(damageEffect, transform.position, Quaternion.identity), 5f); if (CurrentHealth <= 0) { Kill(); } } else { // If there is a blocked effect assigned, play it. if (blockedEffect) Destroy(Instantiate(blockedEffect, transform.position, Quaternion.identity), 5f); } } } void UpdateHealthBar() { //Update the health bar healthBar.fillAmount = CurrentHealth / actualStats.maxHealth; } public override void Kill() { if (!GameManager.instance.isGameOver) { GameManager.instance.AssignLevelReachedUI(level); GameManager.instance.GameOver(); } } public override void RestoreHealth(float amount) { // Only heal the player if their current health is less than their maximum health if (CurrentHealth < actualStats.maxHealth) { CurrentHealth += amount; // Make sure the player's health doesn't exceed their maximum health if (CurrentHealth > actualStats.maxHealth) { CurrentHealth = actualStats.maxHealth; } } } void Recover() { if (CurrentHealth < actualStats.maxHealth) { CurrentHealth += Stats.recovery * Time.deltaTime; // Make sure the player's health doesn't exceed their maximum health if (CurrentHealth > actualStats.maxHealth) { CurrentHealth = actualStats.maxHealth; } } } }
October 14, 2024 at 9:55 pm #16073::The issue where the white component of the health bar only triggers when hit twice is likely due to how the
SetHealth()
function and the alpha logic for thedamagedBarImage
are interacting.In the
Update()
method, you only start the fading effect ifdamagedColor.a > 0
. However, the fade timer (damagedHealthFadeTimer
) only resets and the damaged bar’s alpha value is set to 1 whenSetHealth()
is called. This happens right after the first health hit, but the fading effect hasn’t had enough time to complete since it’s triggered immediately after.The
damagedBarImage.fillAmount
is only updated whendamagedColor.a <= 0
, which means it only fully resets after the fade completes. The first time you reduce health, the damaged bar’s alpha is reset to 1 and the fill amount is synced with the normal health bar. However, if you hit the health bar a second time before the fading effect finishes, the second hit applies correctly since the alpha has dropped enough to allow the sync.You can adjust the behavior so that the damaged bar triggers correctly on the first hit by modifying the logic to better handle the initial health reduction. You want the damaged bar to shrink but not wait for a second hit to take effect. Here’s an updated approach:
public void SetHealth(float healthNormalized) { // Update the main health bar to the current health value barImage.fillAmount = healthNormalized; // Only update the damaged bar if its alpha is currently 0 (meaning it's not visible) if (damagedColor.a <= 0) { damagedBarImage.fillAmount = barImage.fillAmount; } else if (damagedBarImage.fillAmount > barImage.fillAmount) { // Sync the damaged bar immediately with the main bar's value on the first hit damagedBarImage.fillAmount = Mathf.Max(barImage.fillAmount, damagedBarImage.fillAmount); } // Reset the alpha for the damaged bar (fully visible) and the fade timer damagedColor.a = 1f; damagedBarImage.color = damagedColor; damagedHealthFadeTimer = DAMAGED_HEALTH_FADE_TIMER_MAX; }
damagedBarImage.fillAmount
is immediately synced with the main health bar during the first hit, so the white damage bar should react on the first hit instead of waiting for a second.October 15, 2024 at 1:50 am #16075::Hi Terence,
Thanks for addressing the issue, I managed to fix it by initializing alpha to 1f at the awake function and started from there.HealthBarFade.cs
using UnityEngine; using UnityEngine.UI; public class HealthBarFade : MonoBehaviour { private Image barImage; // health bar image reference private Image damagedBarImage; // damaged health bar image reference private Color damagedColor; // color of the damaged bar private float damagedHealthFadeTimer; // timer for fading effect [SerializeField] private float fadeSpeed = 1f; // adjustable fade speed [SerializeField] private float shrinkSpeed = 1f; // adjustable shrink speed for the damaged bar [SerializeField] private float damagedHealthFadeDuration = 1f; // adjustable fade duration for the damaged bar private void Awake() { // Find the two bar image components barImage = transform.Find("bar").GetComponent<Image>(); damagedBarImage = transform.Find("damagedBar").GetComponent<Image>(); // Initialize damaged bar color to be fully visible (alpha = 1) damagedColor = damagedBarImage.color; damagedColor.a = 1f; damagedBarImage.color = damagedColor; // Initialize the fade timer damagedHealthFadeTimer = 0f; // ensure it's inactive at the start } private void Update() { // Start fading only if the fade timer is active if (damagedHealthFadeTimer > 0) { damagedHealthFadeTimer -= Time.deltaTime; // Once the fade timer is up, start reducing the alpha value if (damagedHealthFadeTimer <= 0) { damagedColor.a -= fadeSpeed * Time.deltaTime; damagedColor.a = Mathf.Clamp(damagedColor.a, 0, 1); // ensure alpha does not go below 0 damagedBarImage.color = damagedColor; } } // Shrink the damaged bar towards the current health bar if (damagedBarImage.fillAmount > barImage.fillAmount) { damagedBarImage.fillAmount = Mathf.Lerp(damagedBarImage.fillAmount, barImage.fillAmount, shrinkSpeed * Time.deltaTime); } } public void OnDamaged(float healthNormalized) { // Check if the current health is less than the damaged bar if (healthNormalized < damagedBarImage.fillAmount) { // This means damage occurred, so we reset the damaged bar for the fading effect damagedHealthFadeTimer = damagedHealthFadeDuration; damagedColor.a = 1f; // ensure the damaged bar is visible after damage damagedBarImage.color = damagedColor; // Update the main health bar to the current health value barImage.fillAmount = healthNormalized; } else { // If no damage (healing or first update), just sync the bars instantly barImage.fillAmount = healthNormalized; damagedBarImage.fillAmount = healthNormalized; // If the damaged bar is currently showing damage, reset it to be fully invisible if (damagedColor.a > 0) { damagedColor.a = 0f; // Hide the damaged bar if healing or no damage occurs damagedBarImage.color = damagedColor; } } } }
has upvoted this post. -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: