Files
Fishing2/Assets/Scripts/Test/FishingLineTensionModel.cs
2026-04-06 11:09:05 +08:00

337 lines
12 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;
/// <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);
}
}