提交修改

This commit is contained in:
2026-04-06 11:09:05 +08:00
parent 5f7cbfb713
commit 05fa2d6e5e
146 changed files with 101603 additions and 35623 deletions

View File

@@ -1,243 +0,0 @@
using UnityEngine;
using WaveHarmonic.Crest;
namespace NBF
{
public sealed class BobberFloating : MonoBehaviour
{
public WaterRenderer _water;
[SerializeField] Rigidbody _RigidBody;
[Tooltip("要瞄准哪一层水的碰撞层。")] [SerializeField]
CollisionLayer _Layer = CollisionLayer.AfterAnimatedWaves;
[Header("浮力")]
[Header("力强度")]
[Tooltip("对于探测器而言,大致为 100 比 1 的质量与力的比例,以使质心保持在表面附近。对于“对齐法线”,默认值适用于具有默认刚体的默认球体。")]
[SerializeField]
float _BuoyancyForceStrength = 10f;
[Header("扭矩强度")] [Tooltip("使船体方向与水的法线方向一致时所施加扭矩的大小。")] [SerializeField]
float _BuoyancyTorqueStrength = 8f;
[Header("最大力矩")] [Tooltip("将浮力值固定在此数值上。\n\n适用于处理完全浸没的物体。")] [SerializeField]
float _MaximumBuoyancyForce = 100f;
[Header("高度偏移")] [Tooltip("从变换中心到船体底部的高度偏移(如果存在)。\n\n默认值适用于默认球体。该值无需精确测量从中心到底部的距离。")] [SerializeField]
float _CenterToBottomOffset = -1f;
[Tooltip("顺着波浪 “冲浪” 的近似流体动力学效果。")] [Range(0, 1)] [SerializeField]
float _AccelerateDownhill;
[Header("拖拽")] [Tooltip("在水中时使用拖拽功能。\n将此属性添加到刚体所声明的拖拽力上。")] [SerializeField]
Vector3 _Drag = new(2f, 3f, 1f);
[Tooltip("在水中会产生旋转阻力。\n\n将此阻力添加到刚体上已声明的旋转阻力值之上。")] [SerializeField]
float _AngularDrag = 0.2f;
[Tooltip("施加拉力的位置的垂直偏移量。")] [SerializeField]
float _ForceHeightOffset;
[Header("波响应")] [Tooltip("用于物理计算的物体宽度。\n\n此值越大波响应的滤波效果和平滑程度就越高。如果无法对较大波长进行滤波则应增加 LOD 级别。")] [SerializeField]
float _ObjectWidth = 3f;
// Debug
[Space(10)] [SerializeField] DebugFields _Debug = new();
[System.Serializable]
sealed class DebugFields
{
[SerializeField] internal bool _DrawQueries = false;
}
internal const string k_FixedUpdateMarker = "Crest.FloatingObject.FixedUpdate";
static Unity.Profiling.ProfilerMarker s_FixedUpdateMarker = new(k_FixedUpdateMarker);
/// <summary>
/// 这个物体的任何部分是否浸泡在水中?
/// </summary>
public bool InWater { get; private set; }
readonly SampleFlowHelper _SampleFlowHelper = new();
Vector3[] _QueryPoints;
Vector3[] _QueryResultDisplacements;
Vector3[] _QueryResultVelocities;
Vector3[] _QueryResultNormal;
// internal FloatingObjectProbe[] _Probe = new FloatingObjectProbe[] { new() { _Weight = 1f } };
public FloatingObjectProbe[] Probe = new FloatingObjectProbe[] { new() { _Weight = 1f } };
private void Start()
{
if (_RigidBody == null) TryGetComponent(out _RigidBody);
var points = Probe;
// Advanced 还需要为中心增设一个位置。
var length = points.Length;
_QueryPoints = new Vector3[length];
_QueryResultDisplacements = new Vector3[length];
_QueryResultVelocities = new Vector3[length];
_QueryResultNormal = new Vector3[length];
}
private void FixedUpdate()
{
s_FixedUpdateMarker.Begin(this);
var points = Probe;
// 查询
var collisions = _water.AnimatedWavesLod.Provider;
// 更新查询点。
for (var i = 0; i < points.Length; i++)
{
var point = points[i];
_QueryPoints[i] =
transform.TransformPoint(point._Position + new Vector3(0, _RigidBody.centerOfMass.y, 0));
}
_QueryPoints[^1] = transform.position + new Vector3(0, _RigidBody.centerOfMass.y, 0);
collisions.Query(GetHashCode(), _ObjectWidth, _QueryPoints, _QueryResultDisplacements,
_QueryResultNormal, _QueryResultVelocities, _Layer);
//我们可以将表面速度过滤为最近两帧中的较小值。
//存在一种极端情况:当波长被开启 / 关闭时,会产生单帧速度尖峰——
//因为此时水面确实会发生极快的运动。
var surfaceVelocity = _QueryResultVelocities[^1];
_SampleFlowHelper.Sample(transform.position, out var surfaceFlow, minimumLength: _ObjectWidth);
surfaceVelocity += new Vector3(surfaceFlow.x, 0, surfaceFlow.y);
if (_Debug._DrawQueries)
{
Debug.DrawLine(transform.position + 5f * Vector3.up,
transform.position + 5f * Vector3.up + surfaceVelocity, new(1, 1, 1, 0.6f));
}
var height = _QueryResultDisplacements[0].y + _water.SeaLevel;
var bottomDepth = height - transform.position.y - _CenterToBottomOffset;
var normal = _QueryResultNormal[0];
if (_Debug._DrawQueries)
{
var surfPos = transform.position;
surfPos.y = height;
DebugUtility.DrawCross(Debug.DrawLine, surfPos, normal, 1f, Color.red);
}
InWater = bottomDepth > 0f;
if (!InWater)
{
s_FixedUpdateMarker.End();
return;
}
var buoyancy = _BuoyancyForceStrength * bottomDepth * bottomDepth * bottomDepth *
-Physics.gravity.normalized;
if (_MaximumBuoyancyForce < Mathf.Infinity)
{
buoyancy = Vector3.ClampMagnitude(buoyancy, _MaximumBuoyancyForce);
}
_RigidBody.AddForce(buoyancy, ForceMode.Acceleration);
// 在水面上滑行的近似流体动力学
if (_AccelerateDownhill > 0f)
{
_RigidBody.AddForce(_AccelerateDownhill * -Physics.gravity.y * new Vector3(normal.x, 0f, normal.z),
ForceMode.Acceleration);
}
// // 朝向
// // 与水面垂直。默认使用一个垂直方向,但也可以使用单独的垂直方向。
// // 根据船的长度与宽度的比例。这会根据船只的不同而产生不同的旋转效果。
// // dimensions.
// {
// var normalLatitudinal = normal;
//
// if (_Debug._DrawQueries)
// Debug.DrawLine(transform.position, transform.position + 5f * normalLatitudinal, Color.green);
//
// var torqueWidth = Vector3.Cross(transform.up, normalLatitudinal);
// _RigidBody.AddTorque(torqueWidth * _BuoyancyTorqueStrength, ForceMode.Acceleration);
// _RigidBody.AddTorque(-_AngularDrag * _RigidBody.angularVelocity);
// }
// 朝向(改进的扭矩版本)
{
var normalLatitudinal = normal;
if (_Debug._DrawQueries)
Debug.DrawLine(transform.position, transform.position + 5f * normalLatitudinal, Color.green);
// 限制扭矩强度基于物体质量
var massFactor = Mathf.Clamp(_RigidBody.mass / 0.1f, 0.1f, 1f); // 质量越小,扭矩越弱
var adjustedTorqueStrength = _BuoyancyTorqueStrength * massFactor;
var torqueWidth = Vector3.Cross(transform.up, normalLatitudinal);
// 添加最大扭矩限制
var maxTorque = 5f; // 根据需要调整
var finalTorque = Vector3.ClampMagnitude(torqueWidth * adjustedTorqueStrength, maxTorque);
_RigidBody.AddTorque(finalTorque, ForceMode.Acceleration);
_RigidBody.AddTorque(-_AngularDrag * _RigidBody.angularVelocity, ForceMode.Acceleration);
}
// 相对于水进行拖拽操作
if (_Drag != Vector3.zero)
{
var velocityRelativeToWater = _RigidBody.linearVelocity - surfaceVelocity;
var forcePosition = _RigidBody.worldCenterOfMass + _ForceHeightOffset * Vector3.up;
_RigidBody.AddForceAtPosition(
_Drag.x * Vector3.Dot(transform.right, -velocityRelativeToWater) * transform.right, forcePosition,
ForceMode.Acceleration);
_RigidBody.AddForceAtPosition(_Drag.y * Vector3.Dot(Vector3.up, -velocityRelativeToWater) * Vector3.up,
forcePosition, ForceMode.Acceleration);
_RigidBody.AddForceAtPosition(
_Drag.z * Vector3.Dot(transform.forward, -velocityRelativeToWater) * transform.forward,
forcePosition, ForceMode.Acceleration);
}
s_FixedUpdateMarker.End();
}
}
static class DebugUtility
{
public delegate void DrawLine(Vector3 position, Vector3 up, Color color, float duration);
public static void DrawCross(DrawLine draw, Vector3 position, float r, Color color, float duration = 0f)
{
draw(position - Vector3.up * r, position + Vector3.up * r, color, duration);
draw(position - Vector3.right * r, position + Vector3.right * r, color, duration);
draw(position - Vector3.forward * r, position + Vector3.forward * r, color, duration);
}
public static void DrawCross(DrawLine draw, Vector3 position, Vector3 up, float r, Color color,
float duration = 0f)
{
up.Normalize();
var right = Vector3.Normalize(Vector3.Cross(up, Vector3.forward));
var forward = Vector3.Cross(up, right);
draw(position - up * r, position + up * r, color, duration);
draw(position - right * r, position + right * r, color, duration);
draw(position - forward * r, position + forward * r, color, duration);
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: c103db8c4b014d87845103a400b1ace5
timeCreated: 1772159688

View File

@@ -1,83 +0,0 @@
using System;
using DG.Tweening;
using NBF;
using UnityEngine;
namespace Test
{
public class BobberTest : MonoBehaviour
{
public Rigidbody rb;
public FLine line;
public Transform Terrain;
public float lineLength = 1f;
public float floatLength = 0.5f;
public float Tension = 0;
public void Start()
{
line.InitTest(rb);
//有浮漂
line.Lure.SetJointDistance(floatLength);
line.Bobber.SetJointDistance(lineLength - floatLength);
line.SetTargetLength(lineLength - floatLength);
line.SetLureLength(floatLength);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha0))
{
SetLineLength(lineLength);
}
else if (Input.GetKeyDown(KeyCode.Plus) || Input.GetKeyDown(KeyCode.Equals))
{
lineLength += 0.1f;
SetLineLength(lineLength);
}
else if (Input.GetKeyDown(KeyCode.Minus))
{
lineLength -= 0.1f;
SetLineLength(lineLength);
}
else if (Input.GetKeyDown(KeyCode.K))
{
var pos = Terrain.localPosition;
Terrain.DOLocalMoveY(pos.y - 0.02f, 0.2f);
}
else if (Input.GetKeyDown(KeyCode.L))
{
var pos = Terrain.localPosition;
Terrain.DOLocalMoveY(pos.y + 0.02f, 0.2f);
}
}
public void SetLineLength(float lineLength, bool stretchRope = true)
{
Debug.Log($"lineLength={lineLength}");
if (!line) return;
if (line.LineType == LineType.Spinning)
{
//没有浮漂类型
line.Lure.SetJointDistance(lineLength);
if (stretchRope)
{
line.SetTargetLength(Tension > 0f ? 0f : lineLength);
}
}
else
{
//有浮漂
line.Lure.SetJointDistance(floatLength);
line.Bobber.SetJointDistance(lineLength - floatLength);
if (stretchRope)
{
line.SetTargetLength(Tension > 0f ? 0f : lineLength - floatLength);
}
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3444ef411bcf4f7fa35c03ec7d800ff8
timeCreated: 1772525731

View File

@@ -1,223 +0,0 @@
using System;
using UnityEngine;
using WaveHarmonic.Crest;
[ExecuteInEditMode]
[RequireComponent(typeof(Rigidbody))]
public class Buoyancy : MonoBehaviour
{
// public WaterSurface targetSurface;
public WaterRenderer _water;
[Tooltip("物体浮力的近似半径")] public float sphereRadiusApproximation = 0.25f;
[Tooltip("指定由其他变形(波浪、涌浪等)引起的运动乘数。")] public float waveForceMultiplier = 1f;
[Tooltip("指定由水面水流引起的移动的乘数。")] public float currentSpeedMultiplier = 1f;
[Tooltip("指定由介质黏性所引起的阻力的乘数。")] public float dragMultiplier = 1f;
public float defaultRigidbodyDrag = 0.1f;
public float underwaterRigidbodyAngularDrag = 1f;
public float overwaterRigidbodyAngularDrag = 0.05f;
[Tooltip("指定表面张力的数值。数值过高时,物体在水面上弹跳的速度会减慢。")]
public float surfaceTensionDamping = 10f;
[Tooltip("启用后,净力会以随机偏移量施加出来,从而产生角速度。")]
public bool applyForceWithRandomOffset;
[Tooltip("启用调试绘制")] public bool drawDebug;
private Vector3 currentDirection;
private Vector3 waterPosition;
private Vector3 normal;
private Vector3 deformationDirection;
private Rigidbody rigidbodyComponent;
private float h;
private float hNormalized;
private bool _isEnabled = true;
readonly SampleFlowHelper _SampleFlowHelper = new();
private void Start()
{
rigidbodyComponent = GetComponent<Rigidbody>();
rigidbodyComponent.useGravity = false;
rigidbodyComponent.linearDamping = defaultRigidbodyDrag;
if (_water == null)
{
Debug.LogWarning("没有找到水组件");
}
}
public void EnablePhysics(bool set)
{
_isEnabled = set;
}
private void FixedUpdate()
{
if (!_isEnabled)
{
h = 0f;
hNormalized = 0f;
rigidbodyComponent.useGravity = true;
return;
}
if (_water == null)
return;
rigidbodyComponent.useGravity = false;
// 缓存常用值
Vector3 pos = transform.position;
Vector3 velocity = rigidbodyComponent.linearVelocity;
float radius = sphereRadiusApproximation;
float diameter = 2f * radius;
// 1) 当前位置的水面示例图
FetchWaterSurfaceData(pos, out waterPosition, out normal, out currentDirection);
// 水平“波浪推动”方向源自表面法线
deformationDirection = Vector3.ProjectOnPlane(normal, Vector3.up);
// 2) 近似球体的浸没深度0 至 2R
float bottomY = pos.y - radius;
h = Mathf.Clamp(waterPosition.y - bottomY, 0f, diameter);
hNormalized = (diameter > 0f) ? (h / diameter) : 0f;
// 3) 浸没体积(球冠形) V(h) = πh²/3 * (3R - h)
float submergedVolume = MathF.PI * h * h / 3f * (3f * radius - h);
// 4)角向阻尼从空气状态转变为水状态
rigidbodyComponent.angularDamping = Mathf.Lerp(
overwaterRigidbodyAngularDrag,
underwaterRigidbodyAngularDrag,
hNormalized
);
// 5) 力(保持与您原始的数学计算/单位一致)
Vector3 gravityAcceleration = Physics.gravity;
// 注意:保持原样(先将加速度和力相加,然后使用加速度模式)
Vector3 weightVector = rigidbodyComponent.mass * gravityAcceleration;
Vector3 gravityTerm = Vector3.Lerp(gravityAcceleration, weightVector, hNormalized);
// 物理常数(与原文相同)
const float rhoWater = 997f; // kg/m^3
const float rhoAir = 0.001293f; // kg/m^3
const float muAir = 0.0000181f; // Pa·s
const float muWater = 0.001f; // Pa·s
const float dragCoefficient = 0.47f;
// 浮力: -ρ * V * g
Vector3 buoyancyTerm = (-rhoWater) * submergedVolume * gravityAcceleration;
// 线性粘性阻力类似斯托克斯效应6πRμ(-v) -> 通过浸入使空气/水混合
Vector3 dragAir = MathF.PI * 6f * radius * muAir * (-velocity);
Vector3 dragWater = MathF.PI * 6f * radius * muWater * (-velocity);
Vector3 viscousDragTerm = Vector3.Lerp(dragAir, dragWater, hNormalized) * dragMultiplier;
// 合力(仍被视为加速度)
Vector3 netAcceleration = gravityTerm + buoyancyTerm + viscousDragTerm;
// 可选的随机偏移量,用于产生角速度
Vector3 randomOffset = Vector3.zero;
if (applyForceWithRandomOffset)
{
randomOffset = new Vector3(
UnityEngine.Random.Range(-1f, 1f),
UnityEngine.Random.Range(-1f, 1f),
UnityEngine.Random.Range(-1f, 1f)
) * (radius / 5f);
}
rigidbodyComponent.AddForceAtPosition(
netAcceleration,
pos + randomOffset,
ForceMode.Acceleration
);
// 6) 仅在表面过渡区域0 < hN < 1存在额外的力作用。
if (hNormalized > 0f && hNormalized < 1f)
{
Vector3 gravityDir = gravityAcceleration.normalized;
// 表面张力阻尼:抵消沿重力方向的速度
Vector3 verticalVelocity = Vector3.Dot(velocity, gravityDir) * gravityDir;
Vector3 surfaceTensionAccel = -verticalVelocity * surfaceTensionDamping;
rigidbodyComponent.AddForce(surfaceTensionAccel, ForceMode.Acceleration);
rigidbodyComponent.AddForce(deformationDirection * waveForceMultiplier, ForceMode.Acceleration);
rigidbodyComponent.AddForce(currentDirection * currentSpeedMultiplier, ForceMode.Acceleration);
}
// 7) 终端速度夹具(公式相同,只是命名不同)
float area = MathF.PI * radius * radius;
float g = -gravityAcceleration.y; // positive value
float terminalVelocityInAir = Mathf.Sqrt(2f * rigidbodyComponent.mass * g / (rhoAir * area * dragCoefficient));
float terminalVelocityInWater =
Mathf.Sqrt(2f * rigidbodyComponent.mass * g / (rhoWater * area * dragCoefficient));
float terminalVelocity = Mathf.Lerp(terminalVelocityInAir, terminalVelocityInWater, hNormalized);
float speed = rigidbodyComponent.linearVelocity.magnitude;
if (speed > terminalVelocity && speed > 1e-6f)
{
rigidbodyComponent.linearVelocity = rigidbodyComponent.linearVelocity / speed * terminalVelocity;
}
}
private Vector3 FetchWaterSurfaceData(Vector3 point, out Vector3 positionWS, out Vector3 normalWS,
out Vector3 currentDirectionWS)
{
currentDirectionWS = Vector3.zero;
positionWS = Vector3.zero;
normalWS = Vector3.up;
return point;
}
public Vector3 GetCurrentWaterPosition()
{
return waterPosition;
}
public float GetNormalizedHeightOfSphereBelowSurface()
{
return hNormalized;
}
private void OnDrawGizmosSelected()
{
if (drawDebug)
{
Gizmos.color = Color.magenta;
Gizmos.DrawLine(base.transform.position, base.transform.position + normal);
Gizmos.color = Color.green;
Gizmos.DrawLine(base.transform.position, base.transform.position + deformationDirection * 10f);
Gizmos.color = Color.red;
Gizmos.DrawLine(base.transform.position, base.transform.position + currentDirection);
Gizmos.color = Color.yellow;
Gizmos.DrawSphere(base.transform.position, sphereRadiusApproximation);
// 绘制 Rigidbody 的重心点位
Vector3 centerOfMassWorld = transform.TransformPoint(rigidbodyComponent != null ? rigidbodyComponent.centerOfMass : Vector3.zero);
Gizmos.color = Color.cyan;
Gizmos.DrawSphere(centerOfMassWorld, 0.1f);
Gizmos.DrawLine(centerOfMassWorld, centerOfMassWorld + Vector3.up * 0.5f);
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6f273ba33ebe49cd8b31b92db8288a90
timeCreated: 1772356495

View File

@@ -0,0 +1,337 @@
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);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1a6c0b4a2030426c807730fe3837b9b9
timeCreated: 1775379112