using System.Collections.Generic; using NBC; using UnityEngine; namespace NBF { [System.Serializable] public struct FishingLureCastInput { [Range(0, 1)] public float power; [Range(0, 50)] public float windStrength; // 单位 m/s public Vector3 windDirection; [Range(5, 30)] public float lureWeight; // 单位 g [Range(0.1f, 1f)] public float lineDiameter; // 单位:毫米,0.1mm ~ 1mm } /// /// 路亚抛竿轨迹动画 /// public class LureThrowAnim : NTask { private FPlayer _player; [Header("基础参数")] public int maxSimulationSteps = 1000; public float simulationDuration = 10f; public float timeStep = 0.02f; [Header("抛投力度")] public float minThrowPower = 15f; public float maxThrowPower = 45f; [Header("空气阻力参数")] [Tooltip("阻力系数,非流线钓饵建议 0.8~1.2")] public float dragCoefficient = 0.2f; [Tooltip("迎风面积,0.005m² ≈ 5cm²")] public float lureArea = 0.001f; private FWaterDisplacement _lureHookWaterDisplacement; private List _trajectory; public float totalDuration = 1.5f; // 总飞行时间 public float arriveThreshold = 0.01f; private float elapsedTime = 0f; private bool isMoving = false; private Vector3 startOffset = Vector3.zero; private Transform _transform; public float CurrentDistanceTraveled { get; private set; } = 0f; private float _throwStartLineLenght = 0f; public LureThrowAnim(FPlayer player) { _player = player; } protected override void OnStart() { _throwStartLineLenght = _player.Data.lineLength; _lureHookWaterDisplacement = _player.Gears.Rod.LureHookWaterDisplacement; float angle = 25f; float radians = angle * Mathf.Deg2Rad; Vector3 forwardUp = _player.transform.forward * Mathf.Cos(radians) + _player.transform.up * Mathf.Sin(radians); // // 施加力 // lureHookWaterDisplacement.rigidbody.AddForce(forwardUpDirection * 500, ForceMode.Impulse); _transform = _lureHookWaterDisplacement.transform; _lureHookWaterDisplacement.rigidbody.isKinematic = true; _lureHookWaterDisplacement.rigidbody.useGravity = false; FishingLureCastInput baseInput = new FishingLureCastInput { power = 1f, lureWeight = 2.2f, windStrength = 0f, // 风力大小 lineDiameter = 0.14f }; Vector3 startPos = _player.transform.position + Vector3.up * 2.5f; // Vector3 startPos = _player.Gears.Rod.rodAsset.lineConnector.position; Vector3 throwDir = forwardUp.normalized; var trajectory = CalculateTrajectory(baseInput, startPos, throwDir); _trajectory = trajectory; _trajectory = SimplifyTrajectoryRDP(trajectory, 0.1f); // SceneSettings.Instance.LineRenderer.startWidth = 0.1f; // SceneSettings.Instance.LineRenderer.endWidth = 0.1f; // SceneSettings.Instance.LineRenderer.positionCount = _trajectory.Count; // SceneSettings.Instance.LineRenderer.useWorldSpace = true; // SceneSettings.Instance.LineRenderer.SetPositions(_trajectory.ToArray()); elapsedTime = 0f; isMoving = true; CurrentDistanceTraveled = 0f; // 如果不在起点,先移动过去 if ((_transform.position - _trajectory[0]).magnitude > arriveThreshold) { startOffset = _trajectory[0] - _transform.position; } else { startOffset = Vector3.zero; } } protected override NTaskStatus OnProcess() { OnMove(); return base.OnProcess(); } private void MoveDone() { var rb = _lureHookWaterDisplacement.rigidbody; // 强制同步位置和朝向 rb.position = rb.transform.position; rb.rotation = rb.transform.rotation; rb.isKinematic = false; // 停止物理残留影响 rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; rb.useGravity = true; Finish(); } private void OnMove() { if (!isMoving || _trajectory == null || _trajectory.Count < 2) return; // Debug.LogError("OnMove"); // 初始补位阶段(瞬移或平滑补过去) if (startOffset.magnitude > arriveThreshold) { float moveSpeed = startOffset.magnitude / 0.1f; Vector3 move = startOffset.normalized * moveSpeed * Time.deltaTime; if (move.magnitude >= startOffset.magnitude) { _transform.position = _trajectory[0]; startOffset = Vector3.zero; } else { _transform.position += move; startOffset -= move; } return; } elapsedTime += Time.deltaTime; float t = Mathf.Clamp01(elapsedTime / totalDuration); // 插值路径 int segmentCount = _trajectory.Count - 1; float totalSegmentLength = segmentCount; float pathT = t * totalSegmentLength; int index = Mathf.FloorToInt(pathT); if (index >= _trajectory.Count - 1) { _transform.position = _trajectory[^1]; isMoving = false; CurrentDistanceTraveled = CalculateTotalDistance(_trajectory); MoveDone(); return; } float localT = pathT - index; Vector3 p0 = _trajectory[index]; Vector3 p1 = _trajectory[index + 1]; _transform.position = Vector3.Lerp(p0, p1, localT); var distance = Vector3.Distance(_lureHookWaterDisplacement.transform.position, _player.Gears.Rod.rodAsset.lineConnector.position); if (distance > _throwStartLineLenght) { _throwStartLineLenght = distance; _player.Data.lineLength = _throwStartLineLenght + 0.5f; } // _throwStartLineLenght } float CalculateTotalDistance(List points) { float dist = 0f; for (int i = 1; i < points.Count; i++) dist += Vector3.Distance(points[i - 1], points[i]); return dist; } public List CalculateTrajectory(FishingLureCastInput input, Vector3 startPosition, Vector3 castDirection) { List trajectory = new List(); Vector3 position = startPosition; Vector3 direction = castDirection.normalized; float throwPower = Mathf.Lerp(minThrowPower, maxThrowPower, input.power); Vector3 velocity = direction * throwPower; float lureMass = input.lureWeight / 1000f; // 转 kg Vector3 windDir = input.windDirection.normalized; float windStrength = input.windStrength; float currentTime = 0f; int steps = 0; totalDuration = 0; while (currentTime < simulationDuration && steps < maxSimulationSteps) { if (position.y <= 0f) break; // 模拟风力逐渐生效 float windInfluenceFactor = Mathf.Clamp01(currentTime / 1.5f); // 1.5秒内增长 Vector3 windVelocity = windDir * windStrength * windInfluenceFactor; // 真实空气阻力模型 Vector3 relVelocity = velocity - windVelocity; // 空气阻力 float dragMag = 0.5f * PhysicsHelper.AirDensity * relVelocity.sqrMagnitude * dragCoefficient * lureArea; // --- 钓线空气阻力模拟 --- // 假设飞行中展开的线长度近似为当前位置的XZ平面长度 float lineLength = Vector3.Distance(new Vector3(position.x, 0, position.z), new Vector3(startPosition.x, 0, startPosition.z)); float lineRadius = input.lineDiameter / 2000f; // mm转m再除以2得到半径 // 钓线的迎风面积估算:长度 * 直径 float lineArea = lineLength * (lineRadius * 2f); // 近似为圆柱体侧面积的一部分 // 简化模型:线的附加空气阻力方向与当前速度方向相反 float lineDragMag = 0.5f * PhysicsHelper.AirDensity * velocity.sqrMagnitude * dragCoefficient * lineArea; Vector3 lineDragForce = -velocity.normalized * lineDragMag; Vector3 dragForce = -relVelocity.normalized * dragMag; // 合力 = 重力 + 空气阻力 // Vector3 acceleration = (Physics.gravity + dragForce / lureMass); Vector3 totalForce = dragForce + lineDragForce; // 合力 = 重力 + 空气阻力 + 线阻力 Vector3 acceleration = (Physics.gravity + totalForce / lureMass); velocity += acceleration * timeStep; position += velocity * timeStep; trajectory.Add(position); currentTime += timeStep; steps++; } totalDuration = currentTime; return trajectory; } public static List SimplifyTrajectoryRDP(List points, float tolerance) { if (points == null || points.Count < 3) return new List(points); List result = new List(); SimplifySection(points, 0, points.Count - 1, tolerance, result); result.Add(points[points.Count - 1]); return result; } private static void SimplifySection(List points, int start, int end, float tolerance, List result) { if (end <= start + 1) return; float maxDistance = -1f; int index = -1; Vector3 startPoint = points[start]; Vector3 endPoint = points[end]; for (int i = start + 1; i < end; i++) { float distance = PerpendicularDistance(points[i], startPoint, endPoint); 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 static float PerpendicularDistance(Vector3 point, Vector3 lineStart, Vector3 lineEnd) { if (lineStart == lineEnd) return Vector3.Distance(point, lineStart); Vector3 projected = Vector3.Project(point - lineStart, lineEnd - lineStart); Vector3 closest = lineStart + projected; return Vector3.Distance(point, closest); } } }