using System; using System.Collections.Generic; using UnityEngine; using Random = UnityEngine.Random; namespace HQFPSWeapons { public class FirstPersonCamera : MonoBehaviour { #region Internal #pragma warning disable 0649 [Serializable] private class SpringsModule { public float SpringLerpSpeed = 25f; [Space] [Group] public SpringSettings ForceSprings = SpringSettings.Default; [Group] public SpringSettings HeadbobSprings = SpringSettings.Default; } [Serializable] private struct HeadbobsModule { [Group] public CameraHeadBob WalkHeadbob; [Group] public CameraHeadBob CrouchHeadbob; [Group] public CameraHeadBob RunHeadbob; } [Serializable] private struct ShakesModule { public Spring.Data ShakeSpringSettings; public CameraShakeSettings ExplosionShake; public CameraShakeSettings DeathShake; } [Serializable] private struct FallImpactModule { [MinMax(0f, 50f)] public Vector2 FallImpactRange; public SpringForce PosForce; public SpringForce RotForce; } [Serializable] private struct JumpForceModule { public SpringForce PosForce; public SpringForce RotForce; } [Serializable] private struct GettingHitForceModule { [Range(0f, 10f)] public float PosForce; [Range(0f, 10f)] public float RotForce; } #pragma warning restore 0649 #endregion public CameraHeadBob AimHeadBob { get; set; } public Camera UnityCamera { get { return m_Camera; } } [BHeader("General", true)] [SerializeField] private Camera m_Camera = null; [SerializeField] private Player m_Player = null; [Space] [SerializeField, Group()] private SpringsModule m_Springs = null; [SerializeField, Group()] private HeadbobsModule m_Headbobs = new HeadbobsModule(); [SerializeField, Group()] private ShakesModule m_CamShakes = new ShakesModule(); [SerializeField, Group()] private FallImpactModule m_FallImpact = new FallImpactModule(); [SerializeField, Group()] private JumpForceModule m_JumpForce = new JumpForceModule(); [SerializeField, Group()] private GettingHitForceModule m_GettingHitForce = new GettingHitForceModule(); // Springs private Spring m_PositionSpring_Force; private Spring m_RotationSpring_Force; private Spring m_PositionSpring_Headbob; private Spring m_RotationSpring_Headbob; private Spring m_PositionShakeSpring; private Spring m_RotationShakeSpring; private Spring m_PositionRecoilSpring; private Spring m_RotationRecoilSpring; // Headbob private CameraHeadBob m_CurrentHeadbob; private float m_CurrentBobParam; private Vector3 m_FadeOutBob_Pos; private Vector3 m_FadeOutBob_Rot; private Vector3 m_FadeInBob_Pos; private Vector3 m_FadeInBob_Rot; private float m_FadeOutBobMult; private float m_FadeInBobMult; private float m_FadeInStartTime; private int m_LastFootDown; // Shakes private List m_Shakes = new List(); public bool Raycast(float maxDistance, LayerMask mask, out RaycastHit hitInfo, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.Ignore) { return Physics.Raycast(new Ray(m_Camera.transform.position, m_Camera.transform.forward), out hitInfo, maxDistance, mask, queryTriggerInteraction); } public void AdjustRecoilSettings(Spring.Data rotSpringData, Spring.Data posSpringData) { m_PositionRecoilSpring.Adjust(posSpringData); m_RotationRecoilSpring.Adjust(rotSpringData); } public void ApplyRecoil(RecoilForce force, float forceMultiplier = 1f) { force.PlayRecoilForce(forceMultiplier, m_RotationRecoilSpring, m_PositionRecoilSpring); } public void AddPositionForce(Vector3 positionForce, int distribution = 1) { m_FadeInBobMult = 0f; m_FadeInStartTime = Time.time + 1f; if (distribution <= 1) m_PositionSpring_Force.AddForce(positionForce); else m_PositionSpring_Force.AddDistributedForce(positionForce, distribution); } public void AddRotationForce(Vector3 rotationForce, int distribution = 1) { m_FadeInBobMult = 0f; m_FadeInStartTime = Time.time + 1f; if (distribution <= 1) m_RotationSpring_Force.AddForce(rotationForce); else m_RotationSpring_Force.AddDistributedForce(rotationForce, distribution); } public void DoShake(CameraShakeSettings shake, float scale) { m_Shakes.Add(new CameraShake(shake, m_PositionShakeSpring, m_RotationShakeSpring, scale)); } public void AddExplosionShake(float scale) { m_Shakes.Add(new CameraShake(m_CamShakes.ExplosionShake, m_PositionShakeSpring, m_RotationShakeSpring, scale)); } public void OnPlayerDeath() { m_Shakes.Add(new CameraShake(m_CamShakes.DeathShake, m_PositionShakeSpring, m_RotationShakeSpring, 1f)); } private void Awake() { // Force Springs m_PositionSpring_Force = new Spring(Spring.Type.OverrideLocalPosition, m_Camera.transform); m_PositionSpring_Force.Adjust(m_Springs.ForceSprings.Position.Stiffness, m_Springs.ForceSprings.Position.Damping); m_RotationSpring_Force = new Spring(Spring.Type.OverrideLocalRotation, m_Camera.transform); m_RotationSpring_Force.Adjust(m_Springs.ForceSprings.Rotation.Stiffness, m_Springs.ForceSprings.Rotation.Damping); m_PositionSpring_Force.LerpSpeed = m_Springs.SpringLerpSpeed; m_RotationSpring_Force.LerpSpeed = m_Springs.SpringLerpSpeed; //Headbob Springs m_PositionSpring_Headbob = new Spring(Spring.Type.AddToLocalPosition, m_Camera.transform); m_PositionSpring_Headbob.Adjust(m_Springs.HeadbobSprings.Position.Stiffness, m_Springs.HeadbobSprings.Position.Damping); m_RotationSpring_Headbob = new Spring(Spring.Type.AddToLocalRotation, m_Camera.transform); m_RotationSpring_Headbob.Adjust(m_Springs.HeadbobSprings.Rotation.Stiffness, m_Springs.HeadbobSprings.Rotation.Damping); m_PositionSpring_Headbob.LerpSpeed = m_Springs.SpringLerpSpeed; m_RotationSpring_Headbob.LerpSpeed = m_Springs.SpringLerpSpeed; // Shake Springs m_PositionShakeSpring = new Spring(Spring.Type.AddToLocalPosition, m_Camera.transform); m_PositionShakeSpring.Adjust(m_CamShakes.ShakeSpringSettings); m_RotationShakeSpring = new Spring(Spring.Type.AddToLocalRotation, m_Camera.transform); m_RotationShakeSpring.Adjust(m_CamShakes.ShakeSpringSettings); m_PositionShakeSpring.LerpSpeed = m_Springs.SpringLerpSpeed; m_RotationShakeSpring.LerpSpeed = m_Springs.SpringLerpSpeed; //Recoil Springs m_PositionRecoilSpring = new Spring(Spring.Type.AddToLocalPosition, m_Camera.transform); m_PositionRecoilSpring.Adjust(new Vector3(0.02f, 0.02f, 0.02f), new Vector3(0.3f, 0.3f, 0.3f)); m_RotationRecoilSpring = new Spring(Spring.Type.AddToLocalRotation, m_Camera.transform); m_RotationRecoilSpring.Adjust(new Vector3(0.02f, 0.02f, 0.02f), new Vector3(0.3f, 0.3f, 0.3f)); m_PositionRecoilSpring.LerpSpeed = m_Springs.SpringLerpSpeed; m_RotationRecoilSpring.LerpSpeed = m_Springs.SpringLerpSpeed; m_Player.FallImpact.AddListener(OnFallImpact); m_Player.Jump.AddStartListener(OnStart_Jump); m_Player.Aim.AddStartListener(OnAimStart); m_Player.MoveCycleEnded.AddListener(OnStepTaken); m_Player.MoveCycle.AddChangeListener(OnMovCycleChanged); m_Player.ChangeHealth.AddListener(OnPlayerHealthChanged); m_Player.Death.AddListener(OnPlayerDeath); ShakeManager.ShakeEvent.AddListener(OnShakeEvent); } private void OnDestroy() { ShakeManager.ShakeEvent.RemoveListener(OnShakeEvent); } private void OnAimStart() { m_CurrentBobParam = 0f; } private void FixedUpdate() { m_PositionSpring_Force.FixedUpdate(); m_RotationSpring_Force.FixedUpdate(); m_PositionSpring_Headbob.FixedUpdate(); m_RotationSpring_Headbob.FixedUpdate(); m_PositionShakeSpring.FixedUpdate(); m_RotationShakeSpring.FixedUpdate(); m_PositionRecoilSpring.FixedUpdate(); m_RotationRecoilSpring.FixedUpdate(); UpdateHeadbobs(Time.fixedDeltaTime); UpdateShakes(); } private void Update() { m_PositionSpring_Force.Update(); m_RotationSpring_Force.Update(); m_PositionSpring_Headbob.Update(); m_RotationSpring_Headbob.Update(); m_PositionShakeSpring.Update(); m_RotationShakeSpring.Update(); m_PositionRecoilSpring.Update(); m_RotationRecoilSpring.Update(); } private void UpdateHeadbobs(float deltaTime) { var previousHeadbob = m_CurrentHeadbob; if (m_Player.Run.Active && m_Player.Velocity.Val.sqrMagnitude > 0.2f) m_CurrentHeadbob = m_Headbobs.RunHeadbob; else if (m_Player.Aim.Active) m_CurrentHeadbob = AimHeadBob; else if (m_Player.Crouch.Active) m_CurrentHeadbob = m_Headbobs.CrouchHeadbob; else if (m_Player.Walk.Active) m_CurrentHeadbob = m_Headbobs.WalkHeadbob; else { m_CurrentHeadbob = null; Easings.Interpolate(ref m_FadeInBobMult, 1f, deltaTime, true); } if (previousHeadbob != m_CurrentHeadbob && previousHeadbob != null) { m_FadeOutBob_Pos = m_FadeInBob_Pos * m_FadeInBobMult + m_FadeOutBob_Pos * m_FadeOutBobMult; m_FadeOutBob_Rot = m_FadeInBob_Rot * m_FadeInBobMult + m_FadeOutBob_Rot * m_FadeOutBobMult; m_FadeInBobMult = 0f; m_FadeOutBobMult = 1f; } if (m_CurrentHeadbob != null && (m_Player.MoveInput.Val != Vector2.zero || m_Player.Aim.Active)) Easings.Interpolate(ref m_FadeInBobMult, 1f, deltaTime); Easings.Interpolate(ref m_FadeOutBobMult, 1f, deltaTime, true); m_FadeOutBob_Pos = Vector3.Lerp(m_FadeOutBob_Pos, Vector3.zero, deltaTime * 0.02f); m_FadeOutBob_Rot = Vector3.Lerp(m_FadeOutBob_Rot, Vector3.zero, deltaTime * 0.02f); if (m_Player.Aim.Active && AimHeadBob != null) m_CurrentBobParam += deltaTime * AimHeadBob.HeadBobSpeed; else { m_CurrentBobParam = m_Player.MoveCycle.Val * Mathf.PI; if (m_LastFootDown != 0) m_CurrentBobParam += Mathf.PI; } if (Time.time < m_FadeInStartTime) m_FadeInBobMult = 0f; if (m_CurrentHeadbob != null) { Vector3 posBobAmplitude = Vector3.zero; Vector3 rotBobAmplitude = Vector3.zero; // Update position bob posBobAmplitude.x = m_CurrentHeadbob.PosAmplitude.x * -0.00001f; m_FadeInBob_Pos.x = Mathf.Cos(m_CurrentBobParam) * posBobAmplitude.x; posBobAmplitude.y = m_CurrentHeadbob.PosAmplitude.y * 0.00001f; m_FadeInBob_Pos.y = Mathf.Cos(m_CurrentBobParam * 2) * posBobAmplitude.y; posBobAmplitude.z = m_CurrentHeadbob.PosAmplitude.z * 0.00001f; m_FadeInBob_Pos.z = Mathf.Cos(m_CurrentBobParam) * posBobAmplitude.z; // Update rotation bob rotBobAmplitude.x = m_CurrentHeadbob.RotationAmplitude.x * 0.001f; m_FadeInBob_Rot.x = Mathf.Cos(m_CurrentBobParam * 2) * rotBobAmplitude.x; rotBobAmplitude.y = m_CurrentHeadbob.RotationAmplitude.y * 0.001f; m_FadeInBob_Rot.y = Mathf.Cos(m_CurrentBobParam) * rotBobAmplitude.y; rotBobAmplitude.z = m_CurrentHeadbob.RotationAmplitude.z * 0.001f; m_FadeInBob_Rot.z = Mathf.Cos(m_CurrentBobParam) * rotBobAmplitude.z; } else { m_FadeInBob_Pos = Vector3.Lerp(m_FadeInBob_Pos, Vector3.zero, deltaTime); m_FadeInBob_Rot = Vector3.Lerp(m_FadeInBob_Rot, Vector3.zero, deltaTime); } m_PositionSpring_Headbob.AddForce(m_FadeInBob_Pos * m_FadeInBobMult + m_FadeOutBob_Pos * m_FadeOutBobMult); m_RotationSpring_Headbob.AddForce(m_FadeInBob_Rot * m_FadeInBobMult + m_FadeOutBob_Rot * m_FadeOutBobMult); } private void OnShakeEvent(ShakeEventData shake) { if (shake.ShakeType == ShakeType.Explosion) { float distToExplosionSqr = (transform.position - shake.Position).sqrMagnitude; float explosionRadiusSqr = shake.Radius * shake.Radius; if (explosionRadiusSqr - distToExplosionSqr > 0f) { float distanceFactor = 1f - Mathf.Clamp01(distToExplosionSqr / explosionRadiusSqr); AddExplosionShake(distanceFactor * shake.Scale); } } } private void UpdateShakes() { if (m_Shakes.Count == 0) return; int i = 0; while (true) { if (m_Shakes[i].IsDone) m_Shakes.RemoveAt(i); else { m_Shakes[i].Update(); i++; } if (i >= m_Shakes.Count) break; } } private void OnPlayerHealthChanged(HealthEventData healthEventData) { if (healthEventData.Delta < -8f) { Vector3 posForce = healthEventData.HitDirection == Vector3.zero ? Random.onUnitSphere : healthEventData.HitDirection.normalized; posForce *= Mathf.Abs(healthEventData.Delta / 80f); Vector3 rotForce = Random.onUnitSphere; AddPositionForce(m_Camera.transform.InverseTransformVector(posForce) * m_GettingHitForce.PosForce); AddRotationForce(rotForce * m_GettingHitForce.RotForce); } } private void OnFallImpact(float impactVelocity) { float impactVelocityAbs = Mathf.Abs(impactVelocity); if (impactVelocityAbs > m_FallImpact.FallImpactRange.x) { float multiplier = Mathf.Clamp01(impactVelocityAbs / m_FallImpact.FallImpactRange.y); AddPositionForce(m_Camera.transform.InverseTransformVector(m_FallImpact.PosForce.Force) * multiplier, m_FallImpact.PosForce.Distribution); AddRotationForce(m_FallImpact.RotForce.Force * multiplier, m_FallImpact.RotForce.Distribution); } } private void OnStart_Jump() { AddPositionForce(m_JumpForce.PosForce.Force, m_JumpForce.PosForce.Distribution); AddRotationForce(m_JumpForce.RotForce.Force, m_JumpForce.RotForce.Distribution); } private void OnMovCycleChanged(float cycle) { if (cycle == 0f) m_LastFootDown = 0; else if (m_Player.MoveCycle.PrevVal == 0f) { m_FadeOutBob_Pos = m_FadeInBob_Pos * m_FadeInBobMult + m_FadeOutBob_Pos * m_FadeOutBobMult; m_FadeOutBob_Rot = m_FadeInBob_Rot * m_FadeInBobMult + m_FadeOutBob_Rot * m_FadeOutBobMult; m_FadeInBobMult = 0f; m_FadeOutBobMult = 1f; } } private void OnStepTaken() { m_LastFootDown = m_LastFootDown == 0 ? 1 : 0; } private void OnValidate() { if (m_PositionSpring_Force != null && m_RotationSpring_Force != null) { m_PositionSpring_Force.Adjust(m_Springs.ForceSprings.Position.Stiffness, m_Springs.ForceSprings.Position.Damping); m_RotationSpring_Force.Adjust(m_Springs.ForceSprings.Rotation.Stiffness, m_Springs.ForceSprings.Rotation.Damping); } } } [Serializable] public struct SpringSettings { public static SpringSettings Default { get { return new SpringSettings() { Position = Spring.Data.Default, Rotation = Spring.Data.Default }; } } public Spring.Data Position, Rotation; } }