using System.Collections.Generic; using UnityEngine; using DG.Tweening; using System; namespace NBF { /// /// 抛竿输入参数 /// [Serializable] public struct ThrowInput { [Range(0, 1)] public float power; // 力度 0-1 [Range(5, 50)] public float lureWeight; // 钓饵重量 g [Range(0.1f, 1f)] public float lineDiameter; // 线直径 mm public Vector3 windDirection; public float windStrength; } /// /// 路亚抛竿轨迹控制器 /// 使用预计算轨迹 + DOTween动画,实现确定性抛竿 /// public class LureThrowTrajectory : IDisposable { // 物理常量 private const float AirDensity = 1.225f; // 默认参数 protected float _minThrowPower = 15f; protected float _maxThrowPower = 45f; private float _dragCoefficient = 0.2f; private float _lureArea = 0.001f; private float _timeStep = 0.02f; private int _maxSteps = 500; private Sequence _throwSequence; private List _trajectory; public bool IsPlaying => _throwSequence != null && _throwSequence.IsPlaying(); public List Trajectory => _trajectory; /// /// 计算抛竿轨迹 /// public List CalculateTrajectory(ThrowInput input, Vector3 startPos, Vector3 throwDirection) { _trajectory = new List(); Vector3 position = startPos; Vector3 direction = throwDirection.normalized; float throwPower = Mathf.Lerp(_minThrowPower, _maxThrowPower, input.power); Vector3 velocity = direction * throwPower; float lureMass = input.lureWeight / 1000f; Vector3 windDir = input.windDirection.normalized; float windStrength = input.windStrength; float currentTime = 0f; int steps = 0; while (currentTime < 10f && steps < _maxSteps) { if (position.y <= 0f) break; // 风力影响 float windInfluenceFactor = Mathf.Clamp01(currentTime / 1.5f); Vector3 windVelocity = windDir * windStrength * windInfluenceFactor; Vector3 relVelocity = velocity - windVelocity; // 空气阻力 float dragMag = 0.5f * AirDensity * relVelocity.sqrMagnitude * _dragCoefficient * _lureArea; // 钓线阻力 float lineLength = Vector3.Distance( new Vector3(position.x, 0, position.z), new Vector3(startPos.x, 0, startPos.z)); float lineRadius = input.lineDiameter / 2000f; float lineArea = lineLength * (lineRadius * 2f); float lineDragMag = 0.5f * AirDensity * velocity.sqrMagnitude * _dragCoefficient * lineArea; Vector3 lineDragForce = -velocity.normalized * lineDragMag; Vector3 dragForce = -relVelocity.normalized * dragMag; Vector3 totalForce = dragForce + lineDragForce; Vector3 acceleration = Physics.gravity + totalForce / lureMass; velocity += acceleration * _timeStep; position += velocity * _timeStep; _trajectory.Add(position); currentTime += _timeStep; steps++; } return _trajectory; } /// /// 执行抛竿动画 /// public void ExecuteThrow( Transform lureTransform, Rigidbody lureRigidbody, List trajectory, float duration, Action onComplete = null) { if (trajectory == null || trajectory.Count < 2) { onComplete?.Invoke(); return; } // 停止之前的动画 Kill(); // 设置为运动学模式 lureRigidbody.isKinematic = true; lureRigidbody.useGravity = false; // 创建路径动画 _throwSequence = DOTween.Sequence(); // 使用 DOPath 沿路径移动 var pathArray = trajectory.ToArray(); _throwSequence.Append( lureTransform.DOPath(pathArray, duration, PathType.Linear, PathMode.Full3D) .SetEase(Ease.Linear) .SetLookAt(0.01f) // 让Lure朝向运动方向 ); _throwSequence.OnComplete(() => { // 动画结束,恢复物理 lureRigidbody.isKinematic = false; lureRigidbody.useGravity = true; lureRigidbody.linearVelocity = Vector3.zero; lureRigidbody.angularVelocity = Vector3.zero; onComplete?.Invoke(); }); } /// /// 计算合适的飞行时间(基于轨迹长度) /// public float CalculateDuration(List trajectory, float speedFactor = 1f) { if (trajectory == null || trajectory.Count < 2) return 1f; float totalLength = 0f; for (int i = 1; i < trajectory.Count; i++) { totalLength += Vector3.Distance(trajectory[i - 1], trajectory[i]); } // 根据轨迹长度计算时间,越长越慢 return Mathf.Clamp(totalLength / (20f * speedFactor), 0.5f, 3f); } /// /// 使用简化轨迹(减少点位,优化性能) /// public List SimplifyTrajectory(List points, float tolerance = 0.1f) { if (points == null || points.Count < 3) return new List(points ?? new List()); var result = new List(); SimplifySection(points, 0, points.Count - 1, tolerance, result); result.Add(points[points.Count - 1]); return result; } private void SimplifySection(List points, int start, int end, float tolerance, List result) { if (end <= start + 1) return; float maxDistance = -1f; int index = -1; for (int i = start + 1; i < end; i++) { float distance = PerpendicularDistance(points[i], points[start], points[end]); if (distance > maxDistance) { maxDistance = distance; index = i; } } if (maxDistance > tolerance) { SimplifySection(points, start, index, tolerance, result); result.Add(points[index]); SimplifySection(points, index, end, tolerance, result); } } private float PerpendicularDistance(Vector3 point, Vector3 lineStart, Vector3 lineEnd) { if (lineStart == lineEnd) return Vector3.Distance(point, lineStart); Vector3 projected = Vector3.Project(point - lineStart, lineEnd - lineStart); return Vector3.Distance(point, lineStart + projected); } public void Kill() { if (_throwSequence != null) { _throwSequence.Kill(); _throwSequence = null; } } public void Dispose() { Kill(); _trajectory?.Clear(); } /// /// 设置抛竿力度范围 /// public void SetPowerRange(float min, float max) { _minThrowPower = min; _maxThrowPower = max; } } }