/// <summary> /// Do the explosion. /// </summary> /// <param name="damageAmount">The amount of damage to apply to the hit objects.</param> /// <param name="impactForce">The amount of force to apply to the hit object.</param> /// <param name="impactForceFrames">The number of frames to add the force to.</param> /// <param name="originator">The originator of the object.</param> public void Explode(float damageAmount, float impactForce, int impactForceFrames, GameObject originator) { Health health = null; Rigidbody colliderRigidbody = null; IForceObject forceObject = null; var hitCount = Physics.OverlapSphereNonAlloc(m_Transform.position, m_Radius, m_CollidersHit, m_ImpactLayers, QueryTriggerInteraction.Ignore); #if UNITY_EDITOR if (hitCount == m_MaxCollisionCount) { Debug.LogWarning("Warning: The maximum number of colliders have been hit by " + m_GameObject.name + ". Consider increasing the Max Collision Count value."); } #endif for (int i = 0; i < hitCount; ++i) { // A GameObject can contain multiple colliders. Prevent the explosion from occurring on the same GameObject multiple times. if (m_ObjectExplosions.Contains(m_CollidersHit[i].gameObject)) { continue; } m_ObjectExplosions.Add(m_CollidersHit[i].gameObject); // The base character GameObject should only be checked once. if ((forceObject = m_CollidersHit[i].gameObject.GetCachedParentComponent <IForceObject>()) != null) { if (m_ObjectExplosions.Contains(forceObject)) { continue; } m_ObjectExplosions.Add(forceObject); } // OverlapSphere can return objects that are in a different room. Perform a cast to ensure the object is within the explosion range. if (m_LineOfSight) { // Add a slight vertical offset to prevent a floor collider from getting in the way of the cast. var position = m_Transform.TransformPoint(0, 0.1f, 0); var direction = m_CollidersHit[i].transform.position - position; if (Physics.Raycast(position - direction.normalized * 0.1f, direction, out m_RaycastHit, direction.magnitude, m_ImpactLayers, QueryTriggerInteraction.Ignore) && !(m_RaycastHit.transform.IsChildOf(m_CollidersHit[i].transform) #if FIRST_PERSON_CONTROLLER // The cast should not hit any colliders who are a child of the camera. || m_RaycastHit.transform.gameObject.GetCachedParentComponent <FirstPersonController.Character.FirstPersonObjects>() != null #endif )) { // If the collider is part of a character then ensure the head can't be hit. var parentAnimator = m_CollidersHit[i].transform.gameObject.GetCachedParentComponent <Animator>(); if (parentAnimator != null && parentAnimator.isHuman) { var head = parentAnimator.GetBoneTransform(HumanBodyBones.Head); direction = head.position - position; if (Physics.Raycast(position, direction, out m_RaycastHit, direction.magnitude, m_ImpactLayers, QueryTriggerInteraction.Ignore) && !m_RaycastHit.transform.IsChildOf(m_CollidersHit[i].transform) && !m_CollidersHit[i].transform.IsChildOf(m_RaycastHit.transform) && m_RaycastHit.transform.IsChildOf(m_Transform) #if FIRST_PERSON_CONTROLLER // The cast should not hit any colliders who are a child of the camera. && m_RaycastHit.transform.gameObject.GetCachedParentComponent <FirstPersonController.Character.FirstPersonObjects>() == null #endif ) { continue; } } else { continue; } } } // The shield can absorb some (or none) of the damage from the explosion. var hitDamageAmount = damageAmount; #if ULTIMATE_CHARACTER_CONTROLLER_MELEE ShieldCollider shieldCollider; if ((shieldCollider = m_CollidersHit[i].transform.gameObject.GetCachedComponent <ShieldCollider>()) != null) { hitDamageAmount = shieldCollider.Shield.Damage(this, hitDamageAmount); } #endif // ClosestPoint only works with a subset of collider types. Vector3 closestPoint; if (m_CollidersHit[i] is BoxCollider || m_CollidersHit[i] is SphereCollider || m_CollidersHit[i] is CapsuleCollider || (m_CollidersHit[i] is MeshCollider && (m_CollidersHit[i] as MeshCollider).convex)) { closestPoint = m_CollidersHit[i].ClosestPoint(m_Transform.position); } else { closestPoint = m_CollidersHit[i].ClosestPointOnBounds(m_Transform.position); } var hitDirection = closestPoint - m_Transform.position; // Allow a custom event to be received. EventHandler.ExecuteEvent <float, Vector3, Vector3, GameObject, object, Collider>(m_CollidersHit[i].transform.gameObject, "OnObjectImpact", hitDamageAmount, closestPoint, hitDirection * m_ImpactForce, originator, this, m_CollidersHit[i]); if (m_OnImpactEvent != null) { m_OnImpactEvent.Invoke(hitDamageAmount, closestPoint, hitDirection * m_ImpactForce, originator); } // If the shield didn't absorb all of the damage then it should be applied to the character. if (hitDamageAmount > 0) { // If the Health component exists it will apply an explosive force to the character/character in addition to deducting the health. // Otherwise just apply the force to the character/rigidbody. if ((health = m_CollidersHit[i].gameObject.GetCachedParentComponent <Health>()) != null) { // The further out the collider is, the less it is damaged. var damageModifier = Mathf.Max(1 - (hitDirection.magnitude / m_Radius), 0.01f); health.Damage(hitDamageAmount * damageModifier, m_Transform.position, hitDirection.normalized, impactForce * damageModifier, impactForceFrames, m_Radius, originator, this, null); } else if (forceObject != null) { var damageModifier = Mathf.Max(1 - (hitDirection.magnitude / m_Radius), 0.01f); forceObject.AddForce(hitDirection.normalized * impactForce * damageModifier); } else if ((colliderRigidbody = m_CollidersHit[i].gameObject.GetCachedComponent <Rigidbody>()) != null) { colliderRigidbody.AddExplosionForce(impactForce * MathUtility.RigidbodyForceMultiplier, m_Transform.position, m_Radius); } } } m_ObjectExplosions.Clear(); // An audio clip can play when the object explodes. m_ExplosionAudioClipSet.PlayAudioClip(m_GameObject); Scheduler.Schedule(m_Lifespan, Destroy); }
/// <summary> /// The object has taken been damaged. /// </summary> /// <param name="amount">The amount of damage taken.</param> /// <param name="position">The position of the damage.</param> /// <param name="direction">The direction that the object took damage from.</param> /// <param name="forceMagnitude">The magnitude of the force that is applied to the object.</param> /// <param name="frames">The number of frames to add the force to.</param> /// <param name="radius">The radius of the explosive damage. If 0 then a non-explosive force will be used.</param> /// <param name="attacker">The GameObject that did the damage.</param> /// <param name="attackerObject">The object that did the damage.</param> /// <param name="hitCollider">The Collider that was hit.</param> public virtual void OnDamage(float amount, Vector3 position, Vector3 direction, float forceMagnitude, int frames, float radius, GameObject attacker, object attackerObject, Collider hitCollider) { #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) { m_NetworkHealthMonitor.OnDamage(amount, position, direction, forceMagnitude, frames, radius, attacker, hitCollider); } #endif // 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 (radius == 0 && direction != Vector3.zero && hitCollider != null) { Hitbox hitbox; if (m_ColliderHitboxMap != null && m_ColliderHitboxMap.Count > 0) { if (m_ColliderHitboxMap.TryGetValue(hitCollider, out hitbox)) { 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 (hitCollider is CapsuleCollider) { distance = (hitCollider as CapsuleCollider).radius; } else if (hitCollider is SphereCollider) { distance = (hitCollider as 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(position, 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 == 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)) { amount *= hitbox.DamageMultiplier; 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(amount, m_ShieldAttribute.Value - m_ShieldAttribute.MinValue); amount -= shieldAmount; m_ShieldAttribute.Value -= 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(amount, m_HealthAttribute.Value - m_HealthAttribute.MinValue); } var force = direction * forceMagnitude; if (forceMagnitude > 0) { // Apply a force to the object. if (m_ForceObject != null) { m_ForceObject.AddForce(force, frames); } else { // Apply a force to the rigidbody if the object isn't a character. if (m_Rigidbody != null && !m_Rigidbody.isKinematic) { if (radius == 0) { m_Rigidbody.AddForceAtPosition(force * MathUtility.RigidbodyForceMultiplier, position); } else { m_Rigidbody.AddExplosionForce(force.magnitude * MathUtility.RigidbodyForceMultiplier, position, radius); } } } } // Let other interested objects know that the object took damage. EventHandler.ExecuteEvent <float, Vector3, Vector3, GameObject, Collider>(m_GameObject, "OnHealthDamage", amount, position, force, attacker, hitCollider); if (m_OnDamageEvent != null) { m_OnDamageEvent.Invoke(amount, position, force, attacker); } // 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.IsLocalPlayer()) { #endif Die(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); } }