On Shield Damage Event?

snicker

Member
Hi, I'm trying to add an event to health to listen for when the shield attribute is damaged, but its not showing in the inspector.

Code:
lines added to Opsive Health.cs:

        [Tooltip("Unity event invoked when taking shield damage.")]
        [SerializeField] protected UnityFloatEvent m_OnShieldDamageEvent;

public UnityFloatEvent OnShieldDamageEvent { get { return m_OnShieldDamageEvent; } set { m_OnShieldDamageEvent = value; } }

 line 380:
             EventHandler.ExecuteEvent(m_GameObject, "OnShieldDamage", shieldAmount);
                if (m_OnShieldDamageEvent != null)
                {
                    m_OnShieldDamageEvent.Invoke(shieldAmount);
                }
 
The Health component has a custom inspector so you will also need to override that file, HealthInspector.cs
 
It is showing in the inspector now, but now the health and shield attributes aren't changing when taking damage, but the correct values are showing on my damage callback script when the character gets hit.


Code:
public class MyHealth : Health
{
    [Tooltip("Unity event invoked when taking shield damage.")]
    [SerializeField] protected UnityFloatEvent m_OnShieldDamageEvent;


    private Attribute m_HealthAttribute;
    private Attribute m_ShieldAttribute;
    private Rigidbody m_Rigidbody;
    private Dictionary<Collider, Hitbox> m_ColliderHitboxMap;
    private RaycastHit[] m_RaycastHits;
    private Opsive.UltimateCharacterController.Utility.UnityEngineUtility.RaycastHitComparer m_RaycastHitComparer;
    private IForceObject m_ForceObject;

#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
    private INetworkInfo m_NetworkInfo;
    private INetworkHealthMonitor m_NetworkHealthMonitor;
#endif
    public UnityFloatEvent OnShieldDamageEvent { get { return m_OnShieldDamageEvent; } set { m_OnShieldDamageEvent = value; } }

    /// <summary>
    /// Initialize the default values.
    /// </summary>
    protected override void Awake()
    {
        base.Awake();

    }



    /// <summary>
    /// The object has taken been damaged.
    /// </summary>
    /// <param name="damageData">The data associated with the damage.</param>
    public override void OnDamage(DamageData damageData)
    {
        if (damageData == null) { return; }

        // Add a multiplier if a particular collider was hit. Do not apply a multiplier if the damage is applied through a radius because multiple
        // collider are hit.
        if (damageData.Radius == 0 && damageData.Direction != Vector3.zero && damageData.HitCollider != null)
        {
            if (m_ColliderHitboxMap != null && m_ColliderHitboxMap.Count > 0)
            {
                Hitbox hitbox;
                if (m_ColliderHitboxMap.TryGetValue(damageData.HitCollider, out hitbox))
                {
                    damageData.Amount *= hitbox.DamageMultiplier;
                }
                else
                {
                    // The main collider may be overlapping child hitbox colliders. Perform one more raycast to ensure a hitbox collider shouldn't be hit.
                    float distance = 0.2f;
                    if (damageData.HitCollider is CapsuleCollider capsuleCollider)
                    {
                        distance = capsuleCollider.radius;
                    }
                    else if (damageData.HitCollider is SphereCollider sphereCollider)
                    {
                        distance = sphereCollider.radius;
                    }

                    // The hitbox collider may be underneath the base collider. Fire a raycast to detemine if there are any colliders underneath the hit collider 
                    // that should apply a multiplier.
                    var hitCount = Physics.RaycastNonAlloc(damageData.Position, damageData.Direction, m_RaycastHits, distance,
                                    ~(1 << LayerManager.IgnoreRaycast | 1 << LayerManager.Overlay | 1 << LayerManager.VisualEffect), QueryTriggerInteraction.Ignore);
                    for (int i = 0; i < hitCount; ++i)
                    {
                        var closestRaycastHit = QuickSelect.SmallestK(m_RaycastHits, hitCount, i, m_RaycastHitComparer);
                        if (closestRaycastHit.collider == damageData.HitCollider)
                        {
                            continue;
                        }
                        // A new collider has been found - stop iterating if the hitbox map exists and use the hitbox multiplier.
                        if (m_ColliderHitboxMap.TryGetValue(closestRaycastHit.collider, out hitbox))
                        {
                            damageData.Amount *= hitbox.DamageMultiplier;
                            damageData.HitCollider = hitbox.Collider;
                            break;
                        }
                    }
                }
            }
        }

        // Apply the damage to the shield first because the shield can regenrate.
        if (m_ShieldAttribute != null && m_ShieldAttribute.Value > m_ShieldAttribute.MinValue)
        {
            var shieldAmount = Mathf.Min(damageData.Amount, m_ShieldAttribute.Value - m_ShieldAttribute.MinValue);
            damageData.Amount -= shieldAmount;
            m_ShieldAttribute.Value -= shieldAmount;
                 EventHandler.ExecuteEvent(m_GameObject, "OnShieldDamage", shieldAmount);
                 if (m_OnShieldDamageEvent != null)
                 {
                     m_OnShieldDamageEvent.Invoke(shieldAmount);
                 }
            
        }

        // Decrement the health by remaining amount after the shield has taken damage.
        if (m_HealthAttribute != null && m_HealthAttribute.Value > m_HealthAttribute.MinValue)
        {
            m_HealthAttribute.Value -= Mathf.Min(damageData.Amount, m_HealthAttribute.Value - m_HealthAttribute.MinValue);
        }

        var force = damageData.Direction * damageData.ForceMagnitude;
        if (damageData.ForceMagnitude > 0)
        {
            // Apply a force to the object.
            if (m_ForceObject != null)
            {
                m_ForceObject.AddForce(force, damageData.Frames);
            }
            else
            {
                // Apply a force to the rigidbody if the object isn't a character.
                if (m_Rigidbody != null && !m_Rigidbody.isKinematic)
                {
                    if (damageData.Radius == 0)
                    {
                        m_Rigidbody.AddForceAtPosition(force * MathUtility.RigidbodyForceMultiplier, damageData.Position);
                    }
                    else
                    {
                        m_Rigidbody.AddExplosionForce(force.magnitude * MathUtility.RigidbodyForceMultiplier, damageData.Position, damageData.Radius);
                    }
                }
            }
        }

        var attacker = damageData.DamageSource?.SourceOwner;
        // Let other interested objects know that the object took damage.
        EventHandler.ExecuteEvent<float, Vector3, Vector3, GameObject, Collider>(m_GameObject, "OnHealthDamage", damageData.Amount, damageData.Position, force, attacker, damageData.HitCollider);
        EventHandler.ExecuteEvent<DamageData>(m_GameObject, "OnHealthDamageWithData", damageData);
        if (m_OnDamageEvent != null)
        {
            m_OnDamageEvent.Invoke(damageData.Amount, damageData.Position, force, attacker);
        }
        if (m_DamagePopupMonitor != null)
        {
            m_DamagePopupMonitor.OpenDamagePopup(damageData);
        }

        // The object is dead when there is no more health or shield.
        if (!IsAlive())
        {
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
            if (m_NetworkInfo == null || m_NetworkInfo.HasAuthority())
            {
#endif
                Die(damageData.Position, force, attacker);
#if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER
            }
#endif
        }
        else
        {
            // Play any take damage audio if the object did not die. If the object died then the death audio will play.
            m_TakeDamageAudioClipSet.PlayAudioClip(m_GameObject);
        }
    }


}
 
Last edited:
Instead of completely replacing OnDamage I instead recommend calling the base method and then only adding what you need to add. This way everything will still function as it was before, and you can add any extra callbacks.
 
This seems to work.
Code:
public class MyHealth : Health
{
    [Tooltip("Unity event invoked when taking shield damage.")]
    [SerializeField] protected UnityFloatEvent m_OnShieldDamageEvent;
    private Attribute m_ShieldAttribute;
    public UnityFloatEvent OnShieldDamageEvent { get { return m_OnShieldDamageEvent; } set { m_OnShieldDamageEvent = value; } }
    protected override void Awake()
    {
        base.Awake();
    }
    /// <summary>
    /// The object has taken been damaged.
    /// </summary>
    /// <param name="damageData">The data associated with the damage.</param>
    public override void OnDamage(DamageData damageData)
    {
        base.OnDamage(damageData);

        if (m_ShieldAttribute != null && m_ShieldAttribute.Value > m_ShieldAttribute.MinValue)
        {
            var shieldAmount = m_ShieldAttribute.Value;
            damageData.Amount -= shieldAmount;
            m_ShieldAttribute.Value -= shieldAmount;
            EventHandler.ExecuteEvent(m_GameObject, "OnShieldDamage", shieldAmount);
            if (m_OnShieldDamageEvent != null)
            {
                m_OnShieldDamageEvent.Invoke(shieldAmount);
            }

        }
      
    }
 
Top