231 lines
7.7 KiB
C#
231 lines
7.7 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using DG.Tweening;
|
||
using System;
|
||
|
||
namespace NBF
|
||
{
|
||
/// <summary>
|
||
/// 抛竿输入参数
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 路亚抛竿轨迹控制器
|
||
/// 使用预计算轨迹 + DOTween动画,实现确定性抛竿
|
||
/// </summary>
|
||
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<Vector3> _trajectory;
|
||
|
||
public bool IsPlaying => _throwSequence != null && _throwSequence.IsPlaying();
|
||
public List<Vector3> Trajectory => _trajectory;
|
||
|
||
/// <summary>
|
||
/// 计算抛竿轨迹
|
||
/// </summary>
|
||
public List<Vector3> CalculateTrajectory(ThrowInput input, Vector3 startPos, Vector3 throwDirection)
|
||
{
|
||
_trajectory = new List<Vector3>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行抛竿动画
|
||
/// </summary>
|
||
public void ExecuteThrow(
|
||
Transform lureTransform,
|
||
Rigidbody lureRigidbody,
|
||
List<Vector3> 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();
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算合适的飞行时间(基于轨迹长度)
|
||
/// </summary>
|
||
public float CalculateDuration(List<Vector3> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用简化轨迹(减少点位,优化性能)
|
||
/// </summary>
|
||
public List<Vector3> SimplifyTrajectory(List<Vector3> points, float tolerance = 0.1f)
|
||
{
|
||
if (points == null || points.Count < 3) return new List<Vector3>(points ?? new List<Vector3>());
|
||
|
||
var result = new List<Vector3>();
|
||
SimplifySection(points, 0, points.Count - 1, tolerance, result);
|
||
result.Add(points[points.Count - 1]);
|
||
return result;
|
||
}
|
||
|
||
private 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;
|
||
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置抛竿力度范围
|
||
/// </summary>
|
||
public void SetPowerRange(float min, float max)
|
||
{
|
||
_minThrowPower = min;
|
||
_maxThrowPower = max;
|
||
}
|
||
}
|
||
}
|