Files
Fishing2/Assets/Scripts/Utils/FishingTrajectoryCalculator.cs
2025-05-10 12:49:47 +08:00

257 lines
9.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<Vector3> CalculateTrajectory(FishingLureCastInput input, Vector3 startPosition,
Vector3 castDirection)
{
List<Vector3> trajectory = new List<Vector3>();
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<Vector3> _trajectory;
private int _windIndex;
public List<Vector3> 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;
}
/// <summary>
/// 计算轨迹总长度(线的实际长度)
/// </summary>
public float CalculateTrajectoryLength(List<Vector3> 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;
}
/// <summary>
/// 计算水平距离XZ平面上的直线距离
/// </summary>
public float CalculateHorizontalDistance(List<Vector3> 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<Vector3> SimplifyTrajectoryRDP(List<Vector3> points, float tolerance)
{
if (points == null || points.Count < 3)
return new List<Vector3>(points);
List<Vector3> result = new List<Vector3>();
SimplifySection(points, 0, points.Count - 1, tolerance, result);
result.Add(points[points.Count - 1]);
return result;
}
private static void SimplifySection(List<Vector3> points, int start, int end, float tolerance,
List<Vector3> 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;
}
}