255 lines
9.6 KiB
C#
255 lines
9.6 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using Ashwild.Inventory;
|
|
|
|
namespace Ashwild.Player
|
|
{
|
|
[DisallowMultipleComponent]
|
|
public class PlayerToolHolder : MonoBehaviour
|
|
{
|
|
// ============================================================
|
|
// Tunables
|
|
// ============================================================
|
|
|
|
[Header("References")]
|
|
[SerializeField] private Transform toolHolder;
|
|
|
|
[Header("Weapon bob")]
|
|
[SerializeField] private bool enableWeaponBob = true;
|
|
[SerializeField] private float bobFrequency = 8f;
|
|
[SerializeField] private float bobAmplitudeY = 0.04f;
|
|
[SerializeField] private float bobAmplitudeX = 0.03f;
|
|
[SerializeField] private float bobSmooth = 8f;
|
|
[SerializeField] private float sprintBobMultiplier = 1.4f;
|
|
[SerializeField] private float crouchBobMultiplier = 0.5f;
|
|
|
|
[Header("Weapon sway")]
|
|
[SerializeField] private bool enableWeaponSway = true;
|
|
[SerializeField] private float swayAmount = 0.02f;
|
|
[SerializeField] private float swaySmooth = 6f;
|
|
[SerializeField] private float swayMaxAmount = 0.06f;
|
|
|
|
[Header("Idle tilt")]
|
|
[SerializeField] private bool enableIdleTilt = true;
|
|
[SerializeField] private float idleTiltFrequency = 0.4f;
|
|
[SerializeField] private float idleTiltAmplitudeZ = 1.5f;
|
|
[SerializeField] private float idleTiltAmplitudeX = 0.8f;
|
|
[SerializeField] private float idleTiltSmooth = 4f;
|
|
|
|
[Header("Equip / Unequip")]
|
|
[SerializeField] private float equipDropOffset = -0.5f;
|
|
[SerializeField] private float equipTiltAngle = -15f;
|
|
[SerializeField] private float equipDuration = 0.25f;
|
|
[SerializeField] private float unequipDuration = 0.12f;
|
|
|
|
// ============================================================
|
|
// Wiring
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
// Runtime
|
|
// ============================================================
|
|
|
|
private Vector2 lookInput;
|
|
private float currentSpeed;
|
|
private bool isSprinting;
|
|
private bool isCrouching;
|
|
private bool isGrounded = true;
|
|
|
|
private Vector3 toolHolderBasePos;
|
|
private Quaternion toolHolderBaseRot;
|
|
private Vector3 currentSway;
|
|
private float bobTimer;
|
|
private float idleTimer;
|
|
private float currentBobIntensity;
|
|
|
|
private float equipAnimProgress = 1f;
|
|
private float equipAnimTimer;
|
|
private bool isUnequipping;
|
|
private float unequipAnimProgress = 1f;
|
|
private float unequipAnimTimer;
|
|
private Action onUnequipComplete;
|
|
|
|
// ============================================================
|
|
// Public API (consumed by HotbarController)
|
|
// ============================================================
|
|
|
|
public void PlayEquipAnimation()
|
|
{
|
|
equipAnimProgress = 0f;
|
|
equipAnimTimer = 0f;
|
|
}
|
|
|
|
public void PlayUnequipAnimation(Action onComplete)
|
|
{
|
|
isUnequipping = true;
|
|
unequipAnimProgress = 0f;
|
|
unequipAnimTimer = 0f;
|
|
onUnequipComplete = onComplete;
|
|
}
|
|
|
|
// ============================================================
|
|
// Lifecycle
|
|
// ============================================================
|
|
|
|
private void Awake()
|
|
{
|
|
if (toolHolder != null)
|
|
{
|
|
toolHolderBasePos = toolHolder.localPosition;
|
|
toolHolderBaseRot = toolHolder.localRotation;
|
|
}
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
PlayerEvents.LookInput += OnLookInput;
|
|
PlayerEvents.VelocityChanged += OnVelocityChanged;
|
|
PlayerEvents.SprintChanged += OnSprintChanged;
|
|
PlayerEvents.CrouchChanged += OnCrouchChanged;
|
|
PlayerEvents.GroundedChanged += OnGroundedChanged;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
PlayerEvents.LookInput -= OnLookInput;
|
|
PlayerEvents.VelocityChanged -= OnVelocityChanged;
|
|
PlayerEvents.SprintChanged -= OnSprintChanged;
|
|
PlayerEvents.CrouchChanged -= OnCrouchChanged;
|
|
PlayerEvents.GroundedChanged -= OnGroundedChanged;
|
|
}
|
|
|
|
// ============================================================
|
|
// Event handlers
|
|
// ============================================================
|
|
|
|
private void OnLookInput(Vector2 v) => lookInput = v;
|
|
private void OnVelocityChanged(Vector3 v) => currentSpeed = v.magnitude;
|
|
private void OnSprintChanged(bool b) => isSprinting = b;
|
|
private void OnCrouchChanged(bool b) => isCrouching = b;
|
|
private void OnGroundedChanged(bool grounded, float _) => isGrounded = grounded;
|
|
|
|
// ============================================================
|
|
// Main loop
|
|
// ============================================================
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (toolHolder == null) return;
|
|
|
|
bool dead = PlayerEvents.IsDead;
|
|
if (dead)
|
|
{
|
|
currentBobIntensity = 0f;
|
|
currentSway = Vector3.zero;
|
|
toolHolder.localPosition = Vector3.Lerp(toolHolder.localPosition, toolHolderBasePos, Time.deltaTime * bobSmooth);
|
|
toolHolder.localRotation = Quaternion.Slerp(toolHolder.localRotation, toolHolderBaseRot, Time.deltaTime * idleTiltSmooth);
|
|
return;
|
|
}
|
|
|
|
UpdateBobTimers();
|
|
UpdateSway();
|
|
UpdateEquipAnimations();
|
|
ApplyToolHolderPose();
|
|
}
|
|
|
|
private void UpdateBobTimers()
|
|
{
|
|
float dt = Time.deltaTime;
|
|
float target = (isGrounded && currentSpeed > 0.1f) ? 1f : 0f;
|
|
currentBobIntensity = Mathf.Lerp(currentBobIntensity, target, dt * 4f);
|
|
|
|
if (isGrounded && currentSpeed > 0.1f)
|
|
bobTimer += dt * bobFrequency * (currentSpeed / 5f);
|
|
|
|
if (currentBobIntensity < 0.05f) idleTimer += dt;
|
|
else idleTimer = 0f;
|
|
}
|
|
|
|
private void UpdateSway()
|
|
{
|
|
if (enableWeaponSway)
|
|
{
|
|
Vector3 target = new Vector3(-lookInput.x * swayAmount, -lookInput.y * swayAmount, 0f);
|
|
target.x = Mathf.Clamp(target.x, -swayMaxAmount, swayMaxAmount);
|
|
target.y = Mathf.Clamp(target.y, -swayMaxAmount, swayMaxAmount);
|
|
currentSway = Vector3.Lerp(currentSway, target, Time.deltaTime * swaySmooth);
|
|
}
|
|
else
|
|
{
|
|
currentSway = Vector3.Lerp(currentSway, Vector3.zero, Time.deltaTime * swaySmooth);
|
|
}
|
|
}
|
|
|
|
private void UpdateEquipAnimations()
|
|
{
|
|
if (isUnequipping)
|
|
{
|
|
unequipAnimTimer += Time.deltaTime;
|
|
float t = Mathf.Clamp01(unequipAnimTimer / unequipDuration);
|
|
unequipAnimProgress = t * t;
|
|
if (t >= 1f)
|
|
{
|
|
isUnequipping = false;
|
|
PlayerEvents.RaiseUnequipAnimComplete();
|
|
onUnequipComplete?.Invoke();
|
|
onUnequipComplete = null;
|
|
}
|
|
}
|
|
|
|
if (equipAnimTimer < equipDuration)
|
|
{
|
|
equipAnimTimer += Time.deltaTime;
|
|
float t = Mathf.Clamp01(equipAnimTimer / equipDuration);
|
|
const float c1 = 1.70158f;
|
|
const float c3 = c1 + 1f;
|
|
equipAnimProgress = 1f + c3 * Mathf.Pow(t - 1f, 3f) + c1 * Mathf.Pow(t - 1f, 2f);
|
|
if (t >= 1f) PlayerEvents.RaiseEquipAnimComplete();
|
|
}
|
|
else
|
|
{
|
|
equipAnimProgress = 1f;
|
|
}
|
|
}
|
|
|
|
private void ApplyToolHolderPose()
|
|
{
|
|
Vector3 targetPos = toolHolderBasePos;
|
|
float mult = isCrouching ? crouchBobMultiplier : (isSprinting ? sprintBobMultiplier : 1f);
|
|
|
|
if (enableWeaponBob)
|
|
{
|
|
targetPos.y += Mathf.Sin(bobTimer) * bobAmplitudeY * mult * currentBobIntensity;
|
|
targetPos.x += Mathf.Cos(bobTimer * 0.5f) * bobAmplitudeX * mult * currentBobIntensity;
|
|
}
|
|
targetPos += currentSway;
|
|
|
|
bool isAnimating = isUnequipping || equipAnimProgress < 1f;
|
|
float animFactor = isUnequipping ? unequipAnimProgress : (1f - equipAnimProgress);
|
|
|
|
if (isAnimating)
|
|
{
|
|
targetPos.y += animFactor * equipDropOffset;
|
|
toolHolder.localPosition = targetPos;
|
|
}
|
|
else
|
|
{
|
|
toolHolder.localPosition = Vector3.Lerp(toolHolder.localPosition, targetPos, Time.deltaTime * bobSmooth);
|
|
}
|
|
|
|
if (enableIdleTilt || isAnimating)
|
|
{
|
|
float idleWeight = 1f - currentBobIntensity;
|
|
float phase = idleTimer * idleTiltFrequency * Mathf.PI * 2f;
|
|
float tiltZ = Mathf.Sin(phase) * idleTiltAmplitudeZ * idleWeight;
|
|
float tiltX = Mathf.Cos(phase * 0.6f) * idleTiltAmplitudeX * idleWeight;
|
|
if (isAnimating) tiltZ += animFactor * equipTiltAngle;
|
|
|
|
Quaternion targetTilt = toolHolderBaseRot * Quaternion.Euler(tiltX, 0f, tiltZ);
|
|
if (isAnimating) toolHolder.localRotation = targetTilt;
|
|
else toolHolder.localRotation = Quaternion.Slerp(toolHolder.localRotation, targetTilt, Time.deltaTime * idleTiltSmooth);
|
|
}
|
|
}
|
|
}
|
|
}
|