/// <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); } }
/// <summary> /// Determine any targets that are within the crosshairs raycast. /// </summary> private void Update() { var crosshairsColor = m_DefaultColor; var crosshairsRay = m_Camera.ScreenPointToRay(m_CenterRectTransform.position); Transform target = null; // Prevent the ray between the character and the camera from causing a false collision. if (!m_CharacterLocomotion.FirstPersonPerspective) { var direction = m_CharacterTransform.InverseTransformPoint(crosshairsRay.origin); direction.y = 0; crosshairsRay.origin = crosshairsRay.GetPoint(direction.magnitude); } var hitCount = Physics.SphereCastNonAlloc(crosshairsRay, m_CollisionRadius, m_RaycastHits, m_CameraController.LookDirectionDistance, m_CharacterLayerManager.IgnoreInvisibleLayers, QueryTriggerInteraction.Ignore); #if UNITY_EDITOR if (hitCount == m_MaxCollisionCount) { Debug.LogWarning("Warning: The crosshairs detected the maximum number of objects. Consider increasing the Max Collision Count on the Crosshairs Monitor."); } #endif if (hitCount > 0) { for (int i = 0; i < hitCount; ++i) { var closestRaycastHit = QuickSelect.SmallestK(m_RaycastHits, hitCount, i, m_RaycastHitComparer); var closestRaycastHitTransform = closestRaycastHit.transform; // The crosshairs cannot hit the character that is attached to the camera. if (closestRaycastHitTransform.IsChildOf(m_CharacterTransform)) { continue; } if (MathUtility.InLayerMask(closestRaycastHitTransform.gameObject.layer, m_CharacterLayerManager.EnemyLayers)) { target = closestRaycastHitTransform; crosshairsColor = m_TargetColor; } break; } } if (m_AimAssist != null) { m_AimAssist.SetTarget(target); } if (m_EquippedItem != null) { m_CurrentCrosshairsSpread = Mathf.SmoothDamp(m_CurrentCrosshairsSpread, m_TargetCrosshairsSpread, ref m_CrosshairsSpreadVelocity, m_EquippedItem.QuadrantSpreadDamping); } m_CenterCrosshairsImage.color = crosshairsColor; if (m_LeftCrosshairsImage != null && m_LeftCrosshairsImage.enabled) { m_LeftCrosshairsImage.color = crosshairsColor; PositionSprite(m_LeftRectTransform, -m_EquippedItem.QuadrantOffset - m_CurrentCrosshairsSpread, 0); } if (m_TopCrosshairsImage != null && m_TopCrosshairsImage.enabled) { m_TopCrosshairsImage.color = crosshairsColor; PositionSprite(m_TopRectTransform, 0, m_EquippedItem.QuadrantOffset + m_CurrentCrosshairsSpread); } if (m_RightCrosshairsImage != null && m_RightCrosshairsImage.enabled) { m_RightCrosshairsImage.color = crosshairsColor; PositionSprite(m_RightRectTransform, m_EquippedItem.QuadrantOffset + m_CurrentCrosshairsSpread, 0); } if (m_BottomCrosshairsImage != null && m_BottomCrosshairsImage.enabled) { m_BottomCrosshairsImage.color = crosshairsColor; PositionSprite(m_BottomRectTransform, 0, -m_EquippedItem.QuadrantOffset - m_CurrentCrosshairsSpread); } var enableImage = !m_Aiming || (m_EquippedItem != null && m_EquippedItem.ShowCrosshairsOnAim); if (enableImage != m_EnableImage) { m_EnableImage = enableImage; EnableCrosshairsImage(enableImage); } }
/// <summary> /// Update the rotation forces. /// </summary> public override void UpdateRotation() { var updateNormalRotation = m_Stopping; var targetNormal = m_Stopping ? (m_StopGravityDirection.sqrMagnitude > 0 ? -m_StopGravityDirection : -m_CharacterLocomotion.GravityDirection) : m_CharacterLocomotion.Up; if (!m_Stopping) { // If the depth offset isn't zero then use two raycasts to determine the ground normal. This will allow a long character (such as a horse) to correctly // adjust to a slope. if (m_DepthOffset != 0) { var frontPoint = m_Transform.position; bool frontHit; RaycastHit raycastHit; if ((frontHit = Physics.Raycast(m_Transform.TransformPoint(0, m_CharacterLocomotion.Radius, m_DepthOffset * Mathf.Sign(m_CharacterLocomotion.InputVector.y)), -m_CharacterLocomotion.Up, out raycastHit, m_Distance + m_CharacterLocomotion.Radius, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore))) { frontPoint = raycastHit.point; targetNormal = raycastHit.normal; } if (Physics.Raycast(m_Transform.TransformPoint(0, m_CharacterLocomotion.Radius, m_DepthOffset * -Mathf.Sign(m_CharacterLocomotion.InputVector.y)), -m_CharacterLocomotion.Up, out raycastHit, m_Distance + m_CharacterLocomotion.Radius, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore)) { if (frontHit) { if (m_NormalizeDirection) { var backPoint = raycastHit.point; var direction = (frontPoint - backPoint).normalized; targetNormal = Vector3.Cross(direction, Vector3.Cross(m_CharacterLocomotion.Up, direction)).normalized; } else { targetNormal = (targetNormal + raycastHit.normal).normalized; } } else { targetNormal = raycastHit.normal; } } m_CharacterLocomotion.GravityDirection = -targetNormal; updateNormalRotation = true; } else { var hitCount = m_CharacterLocomotion.Cast(-m_CharacterLocomotion.Up * m_Distance, m_CharacterLocomotion.PlatformMovement + m_CharacterLocomotion.Up * m_CharacterLocomotion.ColliderSpacing, ref m_CombinedRaycastHits, ref m_ColliderIndexMap); // The character hit the ground if any hit points are below the collider. for (int i = 0; i < hitCount; ++i) { var closestRaycastHit = QuickSelect.SmallestK(m_CombinedRaycastHits, hitCount, i, m_RaycastHitComparer); // The hit point has to be under the collider for the character to align to it. var activeCollider = m_CharacterLocomotion.ColliderCount > 1 ? m_CharacterLocomotion.Colliders[m_ColliderIndexMap[closestRaycastHit]] : m_CharacterLocomotion.Colliders[0]; if (!MathUtility.IsUnderCollider(m_Transform, activeCollider, closestRaycastHit.point)) { continue; } targetNormal = m_CharacterLocomotion.GravityDirection = -closestRaycastHit.normal; updateNormalRotation = true; break; } } } // The rotation is affected by aligning to the ground or having a different up rotation from gravity. if (updateNormalRotation) { Rotate(targetNormal); } }
/// <summary> /// Determine any targets that are within the crosshairs raycast. /// </summary> private void Update() { var crosshairsColor = m_DefaultColor; var crosshairsRay = m_Camera.ScreenPointToRay(m_CenterRectTransform.position); Transform target = null; // Prevent the ray between the character and the camera from causing a false collision. if (!m_CharacterLocomotion.FirstPersonPerspective) { var direction = m_CharacterTransform.InverseTransformPoint(crosshairsRay.origin); direction.y = 0; crosshairsRay.origin = crosshairsRay.GetPoint(direction.magnitude); } #if UNITY_EDITOR // Visualize the direction of the look direction. if (m_DebugDrawLookRay) { Debug.DrawRay(crosshairsRay.origin, crosshairsRay.direction * m_CameraController.LookDirectionDistance, Color.white); } #endif var hitCount = Physics.SphereCastNonAlloc(crosshairsRay, m_CollisionRadius, m_RaycastHits, m_CameraController.LookDirectionDistance, m_CharacterLayerManager.IgnoreInvisibleLayers, m_TriggerInteraction); #if UNITY_EDITOR if (hitCount == m_MaxCollisionCount) { Debug.LogWarning("Warning: The crosshairs detected the maximum number of objects. Consider increasing the Max Collision Count on the Crosshairs Monitor."); } #endif if (hitCount > 0) { for (int i = 0; i < hitCount; ++i) { var closestRaycastHit = QuickSelect.SmallestK(m_RaycastHits, hitCount, i, m_RaycastHitComparer); var closestRaycastHitTransform = closestRaycastHit.transform; // The crosshairs cannot hit the character that is attached to the camera. if (closestRaycastHitTransform.IsChildOf(m_CharacterTransform)) { continue; } if (MathUtility.InLayerMask(closestRaycastHitTransform.gameObject.layer, m_CharacterLayerManager.EnemyLayers)) { target = closestRaycastHitTransform; crosshairsColor = m_TargetColor; } break; } } if (m_AutoAssignAimAssistTarget && m_AimAssist != null) { m_AimAssist.SetTarget(target); } // The crosshairs can move with the cursor position. if (m_MoveWithCursor && Cursor.visible) { m_RectTransform.position = new Vector3(m_PlayerInput.GetMousePosition().x, m_PlayerInput.GetMousePosition().y, 0); } // The spread of the crosshairs can change based on how quickly the character is moving. if (m_MovementSpread > 0) { var velocity = Mathf.Clamp01(m_CharacterLocomotion.LocalLocomotionVelocity.sqrMagnitude / m_MaxSpreadVelocitySqrMagnitude); m_SmoothedMovementVelocity = Mathf.SmoothStep(m_SmoothedMovementVelocity, velocity, m_MovementSpreadSpeed * Time.deltaTime); } if (m_EquippedItem != null) { var movementSpread = (((m_MovementSpread / 360) + m_SmoothedMovementVelocity) / 2) * m_EquippedItem.MaxQuadrantSpread; m_CurrentCrosshairsSpread = Mathf.SmoothDamp(m_CurrentCrosshairsSpread, Mathf.Clamp(m_TargetCrosshairsSpread + movementSpread, 0, m_EquippedItem.MaxQuadrantSpread), ref m_CrosshairsSpreadVelocity, m_EquippedItem.QuadrantSpreadDamping); } m_CenterCrosshairsImage.color = crosshairsColor; if (m_LeftCrosshairsImage != null && m_LeftCrosshairsImage.enabled) { m_LeftCrosshairsImage.color = crosshairsColor; PositionSprite(m_LeftRectTransform, -m_EquippedItem.QuadrantOffset - m_CurrentCrosshairsSpread, 0); } if (m_TopCrosshairsImage != null && m_TopCrosshairsImage.enabled) { m_TopCrosshairsImage.color = crosshairsColor; PositionSprite(m_TopRectTransform, 0, m_EquippedItem.QuadrantOffset + m_CurrentCrosshairsSpread); } if (m_RightCrosshairsImage != null && m_RightCrosshairsImage.enabled) { m_RightCrosshairsImage.color = crosshairsColor; PositionSprite(m_RightRectTransform, m_EquippedItem.QuadrantOffset + m_CurrentCrosshairsSpread, 0); } if (m_BottomCrosshairsImage != null && m_BottomCrosshairsImage.enabled) { m_BottomCrosshairsImage.color = crosshairsColor; PositionSprite(m_BottomRectTransform, 0, -m_EquippedItem.QuadrantOffset - m_CurrentCrosshairsSpread); } var enableImage = !m_Aiming || (m_EquippedItem != null && m_EquippedItem.ShowCrosshairsOnAim); if (enableImage != m_EnableImage) { m_EnableImage = enableImage; EnableCrosshairsImage(enableImage); } }