252 lines
9.6 KiB
C#
252 lines
9.6 KiB
C#
using System;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using UHFPS.Input;
|
|
using UHFPS.Tools;
|
|
using ThunderWire.Attributes;
|
|
|
|
namespace UHFPS.Runtime
|
|
{
|
|
[InspectorHeader("HeadBob Controller")]
|
|
public class HeadBobController : PlayerComponent
|
|
{
|
|
#region Structures
|
|
[Serializable]
|
|
public struct HeadBob
|
|
{
|
|
[Header("Vertical HeadBob")]
|
|
public float verticalBobSpeed;
|
|
public float verticalBobAmount;
|
|
public float verticalTiltAmount;
|
|
|
|
[Header("Horizontal HeadBob")]
|
|
public float horizontalBobSpeed;
|
|
public float horizontalBobAmount;
|
|
public float horizontalTiltAmount;
|
|
}
|
|
|
|
public struct HeadBobWave
|
|
{
|
|
public float BobTime;
|
|
public float Wave => Mathf.Sin(BobTime);
|
|
|
|
public void Update(float multiplier = 1)
|
|
{
|
|
BobTime += Time.deltaTime * multiplier;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
BobTime = 0;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
[Header("References")]
|
|
public Transform CameraHeadBob;
|
|
public Transform CameraLean;
|
|
|
|
[Header("HeadBob States"), Space(1)]
|
|
[Boxed] public HeadBob WalkingHeadBob = new();
|
|
[Boxed] public HeadBob RunningHeadBob = new();
|
|
[Boxed] public HeadBob CrouchingHeadBob = new();
|
|
[Boxed] public HeadBob AimingHeadBob = new();
|
|
|
|
[Header("Breath Settings")]
|
|
public AnimationCurve BreathCurve = new(new(0, 1), new (1, 1));
|
|
public float BreathSpeed;
|
|
public float BreathAmount;
|
|
|
|
[Header("Jump Settings")]
|
|
public float MinAirTime;
|
|
public float FallKickbackAmount;
|
|
public float MaxFallKickbackAmount;
|
|
public float MaxSidewayKickbackAmount;
|
|
public float FallKickbackTreshold;
|
|
public float KickbackTime;
|
|
|
|
[Header("Lean Settings")]
|
|
public LayerMask LeanMask;
|
|
public float LeanPosition;
|
|
public float LeanTiltAmount;
|
|
public float LeanColliderRadius;
|
|
|
|
[Header("Speed Settings")]
|
|
public float HeadBobSpeed;
|
|
public float HeadBobTiltSpeed;
|
|
public float LeanSpeed;
|
|
public float LeanTiltSpeed;
|
|
|
|
[Header("Blend Settings")]
|
|
public float BobBlendSpeed;
|
|
public float BobStartVelocity;
|
|
|
|
public Vector2 Wave => new Vector2(horizontalBob.Wave, verticalBob.Wave);
|
|
public Vector3 BreathBobBlended { get; private set; }
|
|
public float BreathBobBlend { get; private set; }
|
|
public float Breath { get; private set; }
|
|
|
|
// private
|
|
private HeadBobWave verticalBob = new();
|
|
private HeadBobWave horizontalBob = new();
|
|
|
|
private float magnitude;
|
|
private float breathTime;
|
|
private float bobJumpBlend;
|
|
private float jumpAirTime;
|
|
private float lastYPos;
|
|
|
|
private Vector3 defaultPos;
|
|
private Vector3 defaultRot;
|
|
|
|
private void Awake()
|
|
{
|
|
defaultPos = CameraHeadBob.localPosition;
|
|
defaultRot = CameraHeadBob.localEulerAngles;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (isEnabled)
|
|
{
|
|
UpdateHeadEffects();
|
|
}
|
|
else
|
|
{
|
|
CameraHeadBob.localPosition = Vector3.Lerp(CameraHeadBob.localPosition, defaultPos, Time.deltaTime * HeadBobSpeed);
|
|
CameraHeadBob.localRotation = Quaternion.Slerp(CameraHeadBob.localRotation, Quaternion.Euler(defaultRot), Time.deltaTime * HeadBobTiltSpeed);
|
|
}
|
|
}
|
|
|
|
private void UpdateHeadEffects()
|
|
{
|
|
// get camera effects
|
|
var (headBob, headBobTilt) = EvaluateHeadBob();
|
|
var (leanDir, leanPos) = EvaluateLean();
|
|
Vector3 breath = EvaluateBreath();
|
|
EvaluateJump();
|
|
|
|
// calculate whether to play a breathing or head bobbing animation
|
|
magnitude = PlayerCollider.velocity.magnitude > BobStartVelocity ? 1 : 0;
|
|
BreathBobBlend = Mathf.MoveTowards(BreathBobBlend, magnitude, Time.deltaTime * BobBlendSpeed);
|
|
BreathBobBlended = Vector3.Lerp(breath, headBob, BreathBobBlend);
|
|
|
|
// calculate whether to play a jump or head bobbing/breath animation
|
|
bool isGrounded = PlayerStateMachine.IsGrounded;
|
|
bobJumpBlend = Mathf.MoveTowards(bobJumpBlend, isGrounded ? 0 : 1, Time.deltaTime * BobBlendSpeed);
|
|
|
|
// select bob or jump effect
|
|
Vector3 bobJumpPosBlended = Vector3.Lerp(BreathBobBlended, Vector3.zero, bobJumpBlend);
|
|
Vector3 bobJumpRotBlended = Vector3.Lerp(headBobTilt, Vector3.zero, bobJumpBlend);
|
|
|
|
// apply head bob position and tilt
|
|
CameraHeadBob.localPosition = Vector3.Lerp(CameraHeadBob.localPosition, bobJumpPosBlended, Time.deltaTime * HeadBobSpeed);
|
|
CameraHeadBob.localRotation = Quaternion.Slerp(CameraHeadBob.localRotation, Quaternion.Euler(bobJumpRotBlended), Time.deltaTime * HeadBobTiltSpeed);
|
|
|
|
// calculate the lean tilt value
|
|
float leanBlend = VectorE.InverseLerp(Vector3.zero, leanPos, CameraLean.localPosition);
|
|
Vector3 leanTilt = -1 * leanDir * LeanTiltAmount * leanBlend * Vector3.forward;
|
|
|
|
// calculate the head position offset value
|
|
Vector3 leanDirection = transform.right * leanDir;
|
|
Ray leanRay = new Ray(transform.position, leanDirection);
|
|
|
|
// convert the max lean distance to a multiplier and multiply it with the leanPos value
|
|
if (Physics.SphereCast(leanRay, LeanColliderRadius, out RaycastHit hit, LeanPosition, LeanMask))
|
|
leanPos *= GameTools.Remap(0f, LeanPosition, 0f, 1f, hit.distance);
|
|
|
|
// apply lean position and tilt
|
|
CameraLean.localPosition = Vector3.Lerp(CameraLean.localPosition, leanPos, Time.deltaTime * LeanSpeed);
|
|
CameraLean.localRotation = Quaternion.Slerp(CameraLean.localRotation, Quaternion.Euler(leanTilt), Time.deltaTime * LeanTiltSpeed);
|
|
}
|
|
|
|
private (Vector3 headBob, Vector3 headTilt) EvaluateHeadBob()
|
|
{
|
|
bool idle = PlayerStateMachine.IsCurrent(PlayerStateMachine.IDLE_STATE) || magnitude <= 0;
|
|
bool running = PlayerStateMachine.IsCurrent(PlayerStateMachine.RUN_STATE);
|
|
bool crouching = PlayerStateMachine.IsCurrent(PlayerStateMachine.CROUCH_STATE);
|
|
|
|
HeadBob headBobState = WalkingHeadBob;
|
|
if (running && !crouching) headBobState = RunningHeadBob;
|
|
else if (crouching) headBobState = CrouchingHeadBob;
|
|
|
|
if (!idle)
|
|
{
|
|
verticalBob.Update(headBobState.verticalBobSpeed);
|
|
horizontalBob.Update(headBobState.horizontalBobSpeed);
|
|
}
|
|
else
|
|
{
|
|
verticalBob.Reset();
|
|
horizontalBob.Reset();
|
|
}
|
|
|
|
Vector3 headBobPos = defaultPos;
|
|
headBobPos.y += verticalBob.Wave * headBobState.verticalBobAmount;
|
|
headBobPos.x += horizontalBob.Wave * headBobState.horizontalBobAmount;
|
|
|
|
Vector3 headBobRot = defaultRot;
|
|
headBobRot.x += verticalBob.Wave * headBobState.verticalTiltAmount;
|
|
headBobRot.z += horizontalBob.Wave * headBobState.horizontalTiltAmount;
|
|
|
|
return (headBobPos, headBobRot);
|
|
}
|
|
|
|
private Vector3 EvaluateBreath()
|
|
{
|
|
if (breathTime > BreathCurve[BreathCurve.length - 1].time)
|
|
breathTime = 0f;
|
|
|
|
breathTime += Time.deltaTime * BreathSpeed;
|
|
float breathEval = BreathCurve.Evaluate(breathTime) * BreathAmount;
|
|
Breath = breathEval;
|
|
|
|
Vector3 breathPos = defaultPos;
|
|
breathPos.y = breathEval;
|
|
return breathPos;
|
|
}
|
|
|
|
private void EvaluateJump()
|
|
{
|
|
if (!PlayerStateMachine.IsGrounded)
|
|
{
|
|
jumpAirTime += Time.deltaTime;
|
|
}
|
|
else if (jumpAirTime > MinAirTime)
|
|
{
|
|
float currentYPos = transform.root.position.y;
|
|
float additionalKickback = Mathf.Clamp(lastYPos - currentYPos, 0f, MaxFallKickbackAmount) * FallKickbackTreshold;
|
|
float kickback = FallKickbackAmount + additionalKickback;
|
|
StartCoroutine(DoHeadBobKickback(new Vector3(kickback, UnityEngine.Random.Range(-MaxSidewayKickbackAmount, MaxSidewayKickbackAmount), 0f), KickbackTime));
|
|
jumpAirTime = 0f;
|
|
}
|
|
else
|
|
{
|
|
lastYPos = transform.root.position.y;
|
|
}
|
|
}
|
|
|
|
private (float leanDir, Vector3 leanPos) EvaluateLean()
|
|
{
|
|
float leanDir = InputManager.ReadInput<float>(Controls.LEAN);
|
|
Vector3 leanPos = new Vector3(leanDir * LeanPosition, 0f, 0f);
|
|
return (leanDir, leanPos);
|
|
}
|
|
|
|
IEnumerator DoHeadBobKickback(Vector3 offset, float time)
|
|
{
|
|
Quaternion s = CameraHeadBob.localRotation;
|
|
Quaternion e = CameraHeadBob.localRotation * Quaternion.Euler(offset);
|
|
|
|
float r = 1.0f / time;
|
|
float t = 0.0f;
|
|
|
|
while (t < 1.0f)
|
|
{
|
|
t += Time.deltaTime * r;
|
|
CameraHeadBob.localRotation = Quaternion.Slerp(s, e, t);
|
|
yield return null;
|
|
}
|
|
}
|
|
}
|
|
} |