using UnityEngine; using System.Collections.Generic; namespace NBF { public class LureTrajectorySimulator : MonoBehaviour { [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; 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; 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++; } return trajectory; } // 示例调用 private List _trajectory; private int _windIndex; public List Test(Transform cameraTransform) { Vector3[] directions = { Vector3.forward, Vector3.back, Vector3.right, Vector3.left, (Vector3.forward + Vector3.right).normalized, (Vector3.forward + Vector3.left).normalized, (Vector3.back + Vector3.right).normalized, (Vector3.back + Vector3.left).normalized }; FishingLureCastInput baseInput = new FishingLureCastInput { power = 1f, lureWeight = 2.2f, windStrength = 0f, // 风力大小 lineDiameter = 0.14f }; Vector3 startPos = cameraTransform.position; Vector3 throwDir = cameraTransform.forward.normalized; baseInput.windDirection = directions[_windIndex]; _trajectory = CalculateTrajectory(baseInput, startPos, throwDir); _trajectory = SimplifyTrajectoryRDP(_trajectory, 0.1f); _windIndex++; if (_windIndex >= directions.Length) { _windIndex = 0; } var length = CalculateTrajectoryLength(_trajectory); var distance = CalculateHorizontalDistance(_trajectory); Debug.LogError($"轨迹点位数={_trajectory.Count} length={length} distance={distance}"); 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()); return _trajectory; } /// /// 计算轨迹总长度(线的实际长度) /// public float CalculateTrajectoryLength(List trajectory) { if (trajectory == null || trajectory.Count < 2) return 0f; float totalLength = 0f; for (int i = 1; i < trajectory.Count; i++) { totalLength += Vector3.Distance(trajectory[i - 1], trajectory[i]); } return totalLength; } /// /// 计算水平距离(XZ平面上的直线距离) /// public float CalculateHorizontalDistance(List trajectory) { if (trajectory == null || trajectory.Count == 0) return 0f; Vector3 startPoint = trajectory[0]; Vector3 endPoint = trajectory[trajectory.Count - 1]; // 将Y坐标设为相同(地面高度) startPoint.y = 0; endPoint.y = 0; return Vector3.Distance(startPoint, endPoint); } private void OnDrawGizmos() { if (_trajectory == null) return; Gizmos.color = Color.cyan; for (int i = 0; i < _trajectory.Count - 1; i++) { Gizmos.DrawLine(_trajectory[i], _trajectory[i + 1]); } if (_trajectory.Count > 0) { Gizmos.color = Color.green; Gizmos.DrawSphere(_trajectory[0], 0.1f); Gizmos.color = Color.red; Gizmos.DrawSphere(_trajectory[_trajectory.Count - 1], 0.1f); for (int i = 1; i < _trajectory.Count; i += 10) { Gizmos.color = Color.Lerp(Color.green, Color.red, i / (float)_trajectory.Count); Gizmos.DrawSphere(_trajectory[i], 0.05f); } } } 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); } } public static class PhysicsHelper { public const float AirDensity = 1.225f; } }