337 lines
12 KiB
C#
337 lines
12 KiB
C#
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// 鱼线张力参考模型
|
||
///
|
||
/// 这个脚本不负责真正的鱼线渲染,也不负责完整鱼AI,
|
||
/// 只负责根据“竿尖 - 鱼”的关系,计算一个较合理的张力模型。
|
||
///
|
||
/// 适合先拿来验证:
|
||
/// 1. 当前张力是否合理
|
||
/// 2. 鱼竿弯曲是否合理
|
||
/// 3. 卸力出线是否自然
|
||
/// 4. 断线判定是否符合预期
|
||
/// </summary>
|
||
public class FishingLineTensionModel : MonoBehaviour
|
||
{
|
||
[Header("References")] [Tooltip("鱼竿竿尖挂点")]
|
||
public Transform rodTip;
|
||
|
||
[Tooltip("鱼对象(位置来源)")] public Transform fishTarget;
|
||
|
||
[Tooltip("鱼刚体,用来取速度并施加线的拉力")] public Rigidbody fishRb;
|
||
|
||
[Tooltip("玩家/竿尖所在刚体,可选。若为空则竿尖速度按0处理")] public Rigidbody rodTipRb;
|
||
|
||
[Header("Line State")] [Tooltip("当前放线长度(米)")]
|
||
public float lineLength = 5f;
|
||
|
||
[Tooltip("最短允许线长,防止收线收到过小")] public float minLineLength = 0.5f;
|
||
|
||
[Tooltip("最长允许线长")] public float maxLineLength = 100f;
|
||
|
||
[Tooltip("手动收线速度(米/秒),这里只是参考参数")] public float reelInSpeed = 1.5f;
|
||
|
||
[Tooltip("手动放线速度(米/秒),这里只是参考参数")] public float reelOutSpeed = 3f;
|
||
|
||
[Header("Line Tension")] [Tooltip("鱼线系统等效刚度。越大,拉直后越硬")]
|
||
public float lineStiffness = 120f;
|
||
|
||
[Tooltip("沿鱼线方向的阻尼。鱼外冲越快,附加张力越大")] public float lineDamping = 20f;
|
||
|
||
[Tooltip("接近绷直时保留的一点最小张力,避免完全没手感")] public float minTensionWhenNearTight = 1f;
|
||
|
||
[Tooltip("进入预张力区域的比例。比如0.95表示距离达到线长95%就开始有一点点手感")] [Range(0.7f, 1f)]
|
||
public float nearTightRatio = 0.95f;
|
||
|
||
[Header("Rod Buffer / Flex")] [Tooltip("鱼竿最大承载参考值。张力越接近这个值,竿越弯")]
|
||
public float rodMaxLoad = 40f;
|
||
|
||
[Tooltip("鱼竿最大缓冲长度(米)。竿越弯,等效可多“吃掉”一些长度")]
|
||
public float rodFlexMax = 0.35f;
|
||
|
||
[Header("Drag / Spool")] [Tooltip("绕线轮卸力阈值。张力超过它就开始自动出线")]
|
||
public float dragThreshold = 18f;
|
||
|
||
[Tooltip("超过卸力阈值后,每多1单位张力,对应的自动出线速度倍率")]
|
||
public float dragSpoolFactor = 0.08f;
|
||
|
||
[Header("Break / Damage")] [Tooltip("安全张力。超过它开始累计损伤")]
|
||
public float safeTension = 22f;
|
||
|
||
[Tooltip("绝对极限张力。超过它可以直接判定断线")] public float breakTension = 35f;
|
||
|
||
[Tooltip("超安全张力时的损伤累计速度倍率")] public float lineDamageRate = 1.5f;
|
||
|
||
[Tooltip("累计损伤达到此值后断线")] public float lineDamageLimit = 10f;
|
||
|
||
[Header("Fish Force")] [Tooltip("是否把张力反向施加给鱼刚体")]
|
||
public bool applyForceToFish = true;
|
||
|
||
[Tooltip("对鱼施加拉力时的倍率,用来调手感")] public float forceToFishScale = 1f;
|
||
|
||
[Header("Runtime Debug (Read Only)")] [SerializeField]
|
||
private float currentDistance;
|
||
|
||
[SerializeField] private float currentRelativeSpeedAlongLine;
|
||
[SerializeField] private float currentRodFlexOffset;
|
||
[SerializeField] private float currentOverStretch;
|
||
[SerializeField] private float currentTension;
|
||
[SerializeField] private float currentRodBend01;
|
||
[SerializeField] private float currentLineDamage;
|
||
[SerializeField] private bool isLineBroken;
|
||
[SerializeField] private bool isAutoSpooling;
|
||
|
||
/// <summary> 当前张力,对外只读 </summary>
|
||
public float CurrentTension => currentTension;
|
||
|
||
/// <summary> 当前鱼竿弯曲比例(0~1) </summary>
|
||
public float CurrentRodBend01 => currentRodBend01;
|
||
|
||
/// <summary> 当前是否断线 </summary>
|
||
public bool IsLineBroken => isLineBroken;
|
||
|
||
/// <summary> 当前累计损伤 </summary>
|
||
public float CurrentLineDamage => currentLineDamage;
|
||
|
||
private void Reset()
|
||
{
|
||
lineLength = 5f;
|
||
minLineLength = 0.5f;
|
||
maxLineLength = 100f;
|
||
|
||
lineStiffness = 120f;
|
||
lineDamping = 20f;
|
||
minTensionWhenNearTight = 1f;
|
||
nearTightRatio = 0.95f;
|
||
|
||
rodMaxLoad = 40f;
|
||
rodFlexMax = 0.35f;
|
||
|
||
dragThreshold = 18f;
|
||
dragSpoolFactor = 0.08f;
|
||
|
||
safeTension = 22f;
|
||
breakTension = 35f;
|
||
lineDamageRate = 1.5f;
|
||
lineDamageLimit = 10f;
|
||
|
||
applyForceToFish = true;
|
||
forceToFishScale = 1f;
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
// 这里只演示输入,实际项目你可能会改成输入系统控制
|
||
HandleManualLineInput();
|
||
}
|
||
|
||
private void FixedUpdate()
|
||
{
|
||
if (isLineBroken)
|
||
{
|
||
currentTension = 0f;
|
||
currentRodBend01 = 0f;
|
||
currentRodFlexOffset = 0f;
|
||
currentOverStretch = 0f;
|
||
currentRelativeSpeedAlongLine = 0f;
|
||
isAutoSpooling = false;
|
||
return;
|
||
}
|
||
|
||
if (rodTip == null || fishTarget == null)
|
||
return;
|
||
|
||
// --------------------------------------------------
|
||
// 1. 计算竿尖到鱼的几何关系
|
||
// --------------------------------------------------
|
||
Vector3 delta = fishTarget.position - rodTip.position;
|
||
float distance = delta.magnitude;
|
||
Vector3 lineDir = distance > 0.0001f ? delta / distance : Vector3.forward;
|
||
|
||
currentDistance = distance;
|
||
|
||
// --------------------------------------------------
|
||
// 2. 计算沿鱼线方向的相对速度
|
||
// > 0 代表鱼在远离竿尖,拉力应增加
|
||
// < 0 代表鱼在靠近竿尖,拉力应减小
|
||
// --------------------------------------------------
|
||
Vector3 fishVel = fishRb != null ? fishRb.linearVelocity : Vector3.zero;
|
||
Vector3 tipVel = rodTipRb != null ? rodTipRb.linearVelocity : Vector3.zero;
|
||
|
||
float relativeSpeedAlongLine = Vector3.Dot(fishVel - tipVel, lineDir);
|
||
currentRelativeSpeedAlongLine = relativeSpeedAlongLine;
|
||
|
||
// --------------------------------------------------
|
||
// 3. 根据“上一帧张力”估算当前鱼竿弯曲带来的缓冲长度
|
||
// 张力越大,竿越弯,可额外缓冲一些长度
|
||
// --------------------------------------------------
|
||
currentRodBend01 = rodMaxLoad > 0.0001f
|
||
? Mathf.Clamp01(currentTension / rodMaxLoad)
|
||
: 0f;
|
||
|
||
currentRodFlexOffset = currentRodBend01 * rodFlexMax;
|
||
|
||
// --------------------------------------------------
|
||
// 4. 计算“超限量”
|
||
// 当距离 <= 线长 + 竿缓冲时,认为线没有真正被硬拉伸
|
||
// 当距离 > 线长 + 竿缓冲时,多出来的部分转化为张力
|
||
// --------------------------------------------------
|
||
float effectiveLength = lineLength + currentRodFlexOffset;
|
||
float overStretch = Mathf.Max(0f, distance - effectiveLength);
|
||
currentOverStretch = overStretch;
|
||
|
||
// --------------------------------------------------
|
||
// 5. 计算基础张力
|
||
// T = 刚度项 + 阻尼项
|
||
// --------------------------------------------------
|
||
float tension = 0f;
|
||
|
||
// 刚度项:超过可承受长度才会产生明显张力
|
||
tension += lineStiffness * overStretch;
|
||
|
||
// 阻尼项:只有“往外冲”的速度才增加张力
|
||
if (relativeSpeedAlongLine > 0f)
|
||
{
|
||
tension += lineDamping * relativeSpeedAlongLine;
|
||
}
|
||
|
||
// --------------------------------------------------
|
||
// 6. 接近绷直时给一点最小预张力
|
||
// 避免 D 接近 lineLength 时,手感突然从0跳到有力
|
||
// --------------------------------------------------
|
||
float nearTightDistance = lineLength * nearTightRatio;
|
||
if (distance >= nearTightDistance)
|
||
{
|
||
tension = Mathf.Max(tension, minTensionWhenNearTight);
|
||
}
|
||
|
||
// 线完全松很多时,可直接视为无有效张力
|
||
if (distance < nearTightDistance && overStretch <= 0f)
|
||
{
|
||
tension = 0f;
|
||
}
|
||
|
||
currentTension = Mathf.Max(0f, tension);
|
||
|
||
// --------------------------------------------------
|
||
// 7. 卸力:当张力超过绕线轮设定值时,自动出线
|
||
// 这样大鱼冲刺时不会硬顶到瞬间爆线
|
||
// --------------------------------------------------
|
||
isAutoSpooling = false;
|
||
|
||
if (currentTension > dragThreshold)
|
||
{
|
||
float extraTension = currentTension - dragThreshold;
|
||
float autoSpoolSpeed = extraTension * dragSpoolFactor;
|
||
|
||
lineLength += autoSpoolSpeed * Time.fixedDeltaTime;
|
||
lineLength = Mathf.Clamp(lineLength, minLineLength, maxLineLength);
|
||
|
||
isAutoSpooling = true;
|
||
}
|
||
|
||
// --------------------------------------------------
|
||
// 8. 断线逻辑
|
||
// 8.1 超过极限值:可直接断
|
||
// 8.2 超过安全值:持续累计损伤
|
||
// --------------------------------------------------
|
||
if (currentTension >= breakTension)
|
||
{
|
||
BreakLine();
|
||
return;
|
||
}
|
||
|
||
if (currentTension > safeTension)
|
||
{
|
||
float overload = currentTension - safeTension;
|
||
currentLineDamage += overload * lineDamageRate * Time.fixedDeltaTime;
|
||
|
||
if (currentLineDamage >= lineDamageLimit)
|
||
{
|
||
BreakLine();
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 张力安全时,损伤缓慢恢复一点
|
||
currentLineDamage -= Time.fixedDeltaTime;
|
||
currentLineDamage = Mathf.Max(0f, currentLineDamage);
|
||
}
|
||
|
||
// --------------------------------------------------
|
||
// 9. 把鱼线张力反向施加给鱼
|
||
// 鱼越往外冲,线张力越大,反向拉回鱼的力也越大
|
||
// --------------------------------------------------
|
||
if (applyForceToFish && fishRb != null && currentTension > 0f)
|
||
{
|
||
Vector3 pullForce = -lineDir * currentTension * forceToFishScale;
|
||
fishRb.AddForce(pullForce, ForceMode.Force);
|
||
}
|
||
|
||
// --------------------------------------------------
|
||
// 10. 重新根据当前张力更新鱼竿弯曲值(给外部表现层用)
|
||
// --------------------------------------------------
|
||
currentRodBend01 = rodMaxLoad > 0.0001f
|
||
? Mathf.Clamp01(currentTension / rodMaxLoad)
|
||
: 0f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 示例输入:
|
||
/// R = 收线
|
||
/// F = 放线
|
||
/// 实际项目建议接你自己的输入系统
|
||
/// </summary>
|
||
private void HandleManualLineInput()
|
||
{
|
||
if (isLineBroken)
|
||
return;
|
||
|
||
if (Input.GetKey(KeyCode.R))
|
||
{
|
||
lineLength -= reelInSpeed * Time.deltaTime;
|
||
}
|
||
|
||
if (Input.GetKey(KeyCode.F))
|
||
{
|
||
lineLength += reelOutSpeed * Time.deltaTime;
|
||
}
|
||
|
||
lineLength = Mathf.Clamp(lineLength, minLineLength, maxLineLength);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断线
|
||
/// </summary>
|
||
private void BreakLine()
|
||
{
|
||
isLineBroken = true;
|
||
currentTension = 0f;
|
||
currentRodBend01 = 0f;
|
||
currentRodFlexOffset = 0f;
|
||
currentOverStretch = 0f;
|
||
isAutoSpooling = false;
|
||
|
||
Debug.Log("鱼线断了");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 外部调用:修复鱼线
|
||
/// </summary>
|
||
public void RepairLine(float repairedLength)
|
||
{
|
||
isLineBroken = false;
|
||
currentLineDamage = 0f;
|
||
lineLength = Mathf.Clamp(repairedLength, minLineLength, maxLineLength);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 外部调用:直接设置线长
|
||
/// </summary>
|
||
public void SetLineLength(float length)
|
||
{
|
||
lineLength = Mathf.Clamp(length, minLineLength, maxLineLength);
|
||
}
|
||
} |