using UnityEngine; namespace NBF { public class ParabolaPlayerThrowAnimation : IPlayerThrowAnimation { private const int TrajectorySampleCount = 24; private readonly float _minThrowDistance; private readonly float _maxThrowDistance; private readonly float _throwDuration; private readonly float _throwArcHeight; private readonly float _targetHeightOffset; private readonly AnimationCurve _throwHeightCurve; private readonly Vector3[] _lastTrajectoryPoints = new Vector3[TrajectorySampleCount + 1]; private bool _hasLastTrajectory; private float _chargedProgress; private float _castElapsedTime; private Vector3 _castStartPos; private Vector3 _castTargetPos; private LureController _castingLure; public bool IsPlaying => _castingLure != null; public ParabolaPlayerThrowAnimation( float minThrowDistance = 6f, float maxThrowDistance = 25f, float throwDuration = 0.65f, float throwArcHeight = 4f, float targetHeightOffset = 0f, AnimationCurve throwHeightCurve = null) { _minThrowDistance = minThrowDistance; _maxThrowDistance = maxThrowDistance; _throwDuration = throwDuration; _throwArcHeight = throwArcHeight; _targetHeightOffset = targetHeightOffset; _throwHeightCurve = throwHeightCurve ?? AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); } public void Play(ThrowAnimationRequest request) { if (request.Lure == null) { return; } Stop(snapToTarget: false); _castingLure = request.Lure; _chargedProgress = Mathf.Clamp01(request.ChargedProgress); _castElapsedTime = 0f; var lureBody = request.Lure.RBody; _castStartPos = request.StartPosition; Vector3 forward = request.Forward; forward.y = 0f; if (forward.sqrMagnitude < 0.001f) { forward = Vector3.forward; } float distance = Mathf.Lerp(_minThrowDistance, _maxThrowDistance, _chargedProgress); _castTargetPos = _castStartPos + forward.normalized * distance; _castTargetPos.y = _castStartPos.y + _targetHeightOffset; CacheTrajectoryPoints(); lureBody.isKinematic = true; lureBody.useGravity = false; lureBody.linearVelocity = Vector3.zero; lureBody.angularVelocity = Vector3.zero; lureBody.position = _castStartPos; } public void Tick(float deltaTime) { DrawLastTrajectory(); UpdateCastAnimation(deltaTime); } public void Stop(bool snapToTarget) { if (_castingLure == null) { return; } var lureBody = _castingLure.RBody; if (snapToTarget) { _castingLure.transform.position = _castTargetPos; lureBody.position = _castTargetPos; } lureBody.linearVelocity = Vector3.zero; lureBody.angularVelocity = Vector3.zero; lureBody.useGravity = true; lureBody.isKinematic = false; _castingLure = null; } private void UpdateCastAnimation(float deltaTime) { if (_castingLure == null) { return; } float duration = Mathf.Max(_throwDuration, 0.01f); _castElapsedTime += deltaTime; float progress = Mathf.Clamp01(_castElapsedTime / duration); _castingLure.transform.position = EvaluateTrajectoryPosition(progress); if (progress >= 1f) { Stop(snapToTarget: true); } } private void CacheTrajectoryPoints() { for (int i = 0; i <= TrajectorySampleCount; i++) { float progress = i / (float)TrajectorySampleCount; _lastTrajectoryPoints[i] = EvaluateTrajectoryPosition(progress); } _hasLastTrajectory = true; } private Vector3 EvaluateTrajectoryPosition(float progress) { Vector3 position = Vector3.Lerp(_castStartPos, _castTargetPos, progress); float arc = _throwHeightCurve.Evaluate(progress) * _throwArcHeight * _chargedProgress; position.y += arc; return position; } private void DrawLastTrajectory() { if (!_hasLastTrajectory) { return; } for (int i = 1; i <= TrajectorySampleCount; i++) { Debug.DrawLine(_lastTrajectoryPoints[i - 1], _lastTrajectoryPoints[i], Color.yellow); } Debug.DrawRay(_lastTrajectoryPoints[TrajectorySampleCount], Vector3.up * 0.3f, Color.cyan); } } }