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);
}
}
}