Help with health bar damage visual effect!

Hello! I would like some assistance with my scripting issues please! I've just started to code with C# and don't understand many aspects of it. I'm currently trying to implement an effect that happens when the player gets damaged.
For example, as the green foreground of the bar changes with the value of the Health attribute, there will be another bar at the background that is white.
When the player gets damaged, the white background will stay at the original health amount for a second, and then smoothly slide to the same length as the newly changed health amount. At this point the white background will not be seen until the player gets damaged again.

My problem:

When I get damaged, both the fill images in the canvas do not change in fill amount. So the health bar as a whole, essentially doesn't change in size/length. I think it is an issue with my code, especially at the part where it gets the Health attribute value and uses it to change the amount it fills the bar.

Here is my script:

C#:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Opsive.UltimateCharacterController.Traits;
using Attribute = Opsive.UltimateCharacterController.Traits.Attribute;

public class HealthShrink : MonoBehaviour
{
    private Attribute m_AttributeHealth;
    private AttributeManager m_AttributeManager;

    private const float DAMAGED_TIMER_MAX = 1f;

    private Image barImage;
    private Image damagedBarImage;
    private float damageTimer;

    public void Awake()
    {
        barImage = transform.Find("bar").GetComponent<Image>();
        damagedBarImage = transform.Find("damagedBar").GetComponent<Image>();
        m_AttributeManager = GetComponent<AttributeManager>();
        m_AttributeHealth = m_AttributeManager.GetAttribute("Health");
    }

    private void Start()
    {
        SetHealth(m_AttributeHealth.Value);
        damagedBarImage.fillAmount = barImage.fillAmount;
    }

    private void Update()
    {
        damageTimer -= Time.deltaTime;
        if (damageTimer < 0)
        {
            if (barImage.fillAmount < damagedBarImage.fillAmount)
            {
                float shrinkSpeed = 1f;
                damagedBarImage.fillAmount -= shrinkSpeed * Time.deltaTime;
            }
        }
    }

    private void OnUpdateValue(Attribute m_AttributeHealth)
    {
        damageTimer = DAMAGED_TIMER_MAX;
        SetHealth(m_AttributeHealth.Value);
    }

    private void SetHealth(float healthNormal)
    {
        barImage.fillAmount = healthNormal / 100;
    }
}

Any help would be appreciated, thanks!
 
I don't see anywhere that you're actually subscribing to the "OnAttributeUpdateValue" event? E.g.:

EventHandler.RegisterEvent<Attribute>(gameObject, "OnAttributeUpdateValue", OnUpdateValue);
EventHandler.UnregisterEvent<Attribute>(gameObject, "OnAttributeUpdateValue", OnUpdateValue);
 
Oh, I see! And thanks for the reply!
I'm guessing I have to put this in my void awake.
Do I also include the UnregisterEvent in an OnDestroy?
 
Last edited:
I have altered my script to function similarly to the Opsive's Attribute Monitor script and the script component is applied to the Canvas gameObject named "Health".
I play-tested it again and found that my health bar image still does not change in fill amount. I even made the Health attribute value 80 out of 100 and it still does not change. So the problem is with the images themselves or the parenting with the Canvas gameObject?

C#:
using UnityEngine;
using UnityEngine.UI;
using Opsive.Shared.Events;
using Opsive.Shared.Game;
using Opsive.UltimateCharacterController.UI;
using Opsive.UltimateCharacterController.Traits;

public class HealthShrink : CharacterMonitor
{
    private Attribute m_Attribute;
    protected string m_AttributeName = "Health";
    protected AttributeManager m_AttributeManager;

    private const float DAMAGED_TIMER_MAX = 1f;

    private Image barImage;
    private Image damagedBarImage;
    private float damageTimer;

    public new void Awake()
    {
        EventHandler.RegisterEvent<Attribute>(m_AttributeManager.gameObject, "OnAttributeUpdateValue", OnUpdateValue);
        barImage = transform.Find("bar").GetComponent<Image>();
        damagedBarImage = transform.Find("damagedBar").GetComponent<Image>();
    }

    protected override void OnAttachCharacter(GameObject character)
    {
        if (m_Character != null)
        {
            if (m_AttributeManager != null && m_AttributeManager.gameObject == character)
            {
                EventHandler.UnregisterEvent<Attribute>(m_AttributeManager.gameObject, "OnAttributeUpdateValue", OnUpdateValue);
                m_AttributeManager = null;
            }
        }

        base.OnAttachCharacter(character);

        if (m_Character == null)
        {
            return;
        }

        if (m_AttributeManager == null)
        {
            m_AttributeManager = m_Character.GetCachedComponent<AttributeManager>();
            EventHandler.RegisterEvent<Attribute>(m_AttributeManager.gameObject, "OnAttributeUpdateValue", OnUpdateValue);
        }
        if (m_AttributeManager == null)
        {
            enabled = false;
            gameObject.SetActive(false);
            return;
        }

        m_Attribute = m_AttributeManager.GetAttribute(m_AttributeName);
        if (m_Attribute == null)
        {
            enabled = false;
            gameObject.SetActive(false);
            return;
        }
        enabled = true;
        barImage.fillAmount = (m_Attribute.Value - m_Attribute.MinValue) / (m_Attribute.MaxValue - m_Attribute.MinValue);
        damagedBarImage.fillAmount = barImage.fillAmount;
    }

    private void Update()
    {
        damageTimer -= Time.deltaTime;
        if (damageTimer < 0)
        {
            if (barImage.fillAmount < damagedBarImage.fillAmount)
            {
                float shrinkSpeed = 1f;
                damagedBarImage.fillAmount -= shrinkSpeed * Time.deltaTime;
            }
        }
    }

    private void OnUpdateValue(Attribute attribute)
    {
        if (attribute != m_Attribute)
        {
            return;
        }
        damageTimer = DAMAGED_TIMER_MAX;
        barImage.fillAmount = (m_Attribute.Value - m_Attribute.MinValue) / (m_Attribute.MaxValue - m_Attribute.MinValue);
    }

    public new void OnDestroy()
    {
        base.OnDestroy();

        if (m_AttributeManager != null)
        {
            EventHandler.UnregisterEvent<Attribute>(m_AttributeManager.gameObject, "OnAttributeUpdateValue", OnUpdateValue);
        }
    }
}

Thanks again for helping me with this!
 
Last edited:
Is the event being received within OnUpdateValue? Have you looked at the AttributeMonitor? This class displays a fill bar and should have a similar structure for what you are going for. I would almost just copy that code and then apply your own changes.
 
Yep, my script is Opsive's Attribute Monitor but just modified a bit to change two sliders instead of one. One animates but the other doesn’t.
But something is still wrong with said modifications. So when I reach back home, I'll try troubleshooting again.
 
Last edited:
Now I have the two separate sliders as children within the Monitor prefab’s hierarchy.
My new script, now attached to a gameObject outside of the Canvas:

C#:
using UnityEngine;
using UnityEngine.UI;
using Opsive.Shared.Events;
using Opsive.Shared.Game;
using Opsive.UltimateCharacterController.UI;
using Opsive.UltimateCharacterController.Traits;

public class HealthShrink : CharacterMonitor
{
    private Attribute m_Attribute;
    protected string m_AttributeName = "Health";
    protected AttributeManager m_AttributeManager;
    protected Slider m_HealthSlider;
    protected Slider m_DamageSlider;

    private const float DAMAGED_TIMER_MAX = 1f;
    private float damageTimer;

    public GameObject BarForHealth;
    public GameObject BarForDamage;

    public new void Awake()
    {
        EventHandler.RegisterEvent<Attribute>(m_AttributeManager.gameObject, "OnAttributeUpdateValue", OnUpdateValue);
        m_HealthSlider = BarForHealth.GetComponent<Slider>();
        m_DamageSlider = BarForDamage.GetComponent<Slider>();
    }

    protected override void OnAttachCharacter(GameObject character)
    {
        if (m_Character != null)
        {
            if (m_AttributeManager != null && m_AttributeManager.gameObject == character)
            {
                EventHandler.UnregisterEvent<Attribute>(m_AttributeManager.gameObject, "OnAttributeUpdateValue", OnUpdateValue);
                m_AttributeManager = null;
            }
        }

        base.OnAttachCharacter(character);

        if (m_Character == null)
        {
            return;
        }

        if (m_AttributeManager == null)
        {
            m_AttributeManager = m_Character.GetCachedComponent<AttributeManager>();
            EventHandler.RegisterEvent<Attribute>(m_AttributeManager.gameObject, "OnAttributeUpdateValue", OnUpdateValue);
        }
        if (m_AttributeManager == null)
        {
            enabled = false;
            gameObject.SetActive(false);
            return;
        }

        m_Attribute = m_AttributeManager.GetAttribute(m_AttributeName);
        if (m_Attribute == null)
        {
            enabled = false;
            gameObject.SetActive(false);
            return;
        }
        enabled = true;
        m_HealthSlider.value = (m_Attribute.Value - m_Attribute.MinValue) / (m_Attribute.MaxValue - m_Attribute.MinValue);
        m_DamageSlider.value = m_HealthSlider.value;
    }

    private void Update()
    {
        damageTimer -= Time.deltaTime;
        if (damageTimer < 0)
        {
            if (m_HealthSlider.value < m_DamageSlider.value)
            {
                float shrinkSpeed = 1f;
                m_DamageSlider.value -= shrinkSpeed * Time.deltaTime;
            }
        }
    }

    private void OnUpdateValue(Attribute m_Attribute)
    {
        damageTimer = DAMAGED_TIMER_MAX;
        m_HealthSlider.value = (m_Attribute.Value - m_Attribute.MinValue) / (m_Attribute.MaxValue - m_Attribute.MinValue);
    }

    public new void OnDestroy()
    {
        base.OnDestroy();

        if (m_AttributeManager != null)
        {
            EventHandler.UnregisterEvent<Attribute>(m_AttributeManager.gameObject, "OnAttributeUpdateValue", OnUpdateValue);
        }
    }
}

Here’s a picture of my hierarchy, could there be something out of place?

tempsnip.png
 
Last edited:
Is the event being received?
Sorry! I'm not sure how to check whether or not the event is being registered or received. Should the UI element have Fill area and fill as children? Or it doesn't matter.

I think it's something to do with it getting the attribute value. Because when I change the health value in the manager from 100/100 to 80/100, the bar doesn't change in value either!
 
Last edited:
The easiest way to check would be to just put a Debug.Log at the top of OnUpdateValue, then have the character's health be modified during runtime and see if the log appears in the console. (I'm not 100% sure if it will detect changes that are made manually via the inspector, though?)
 
The easiest way to check would be to just put a Debug.Log at the top of OnUpdateValue, then have the character's health be modified during runtime and see if the log appears in the console. (I'm not 100% sure if it will detect changes that are made manually via the inspector, though?)
Alright, got it. Thanks.
I will report back soon
 
I don't know what I must've done wrong, but I finally got it to work!
I am so pleased and relaxed at the same time haha.. Finally! After 5 days I found my solution, and it was much simpler than I had thought it to be.

Thanks for helping immensely @Andrew and @Justin ! ?
 
Top