Files
2026-03-05 18:07:55 +08:00

312 lines
12 KiB
C#
Raw Permalink 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 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
}
/// <summary>
/// 路亚抛竿轨迹动画
/// </summary>
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<Vector3> _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<Vector3> points)
{
float dist = 0f;
for (int i = 1; i < points.Count; i++)
dist += Vector3.Distance(points[i - 1], points[i]);
return dist;
}
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;
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<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);
}
}
}