diff --git a/Assets/ResRaw/Prefabs/Line/fishing line float set.prefab b/Assets/ResRaw/Prefabs/Line/fishing line float set.prefab
index 71d345089..0ba4526bc 100644
--- a/Assets/ResRaw/Prefabs/Line/fishing line float set.prefab
+++ b/Assets/ResRaw/Prefabs/Line/fishing line float set.prefab
@@ -47,13 +47,14 @@ MonoBehaviour:
m_EditorClassIdentifier:
startAnchor: {fileID: 0}
endAnchor: {fileID: 54298866000586118}
+ LineMultiple: 1
physicsSegmentLen: 0.1
minPhysicsNodes: 2
maxPhysicsNodes: 200
gravityStrength: 6
velocityDampen: 0.95
stiffness: 0.8
- iterations: 20
+ iterations: 10
initialLength: 0
lengthSmoothTime: 0.15
lengthChangeVelocityKill: 0.4
@@ -662,13 +663,14 @@ MonoBehaviour:
m_EditorClassIdentifier:
startAnchor: {fileID: 54298866000586118}
endAnchor: {fileID: 54679398375713381}
+ LineMultiple: 1
physicsSegmentLen: 0.2
minPhysicsNodes: 2
maxPhysicsNodes: 120
gravityStrength: 6
velocityDampen: 0.95
stiffness: 0.8
- iterations: 20
+ iterations: 10
initialLength: 0
lengthSmoothTime: 0.15
lengthChangeVelocityKill: 0.6
diff --git a/Assets/Scripts/Fishing/Rope/Rope.cs b/Assets/Scripts/Fishing/Rope/Rope.cs
index c82121df7..083610996 100644
--- a/Assets/Scripts/Fishing/Rope/Rope.cs
+++ b/Assets/Scripts/Fishing/Rope/Rope.cs
@@ -8,6 +8,9 @@ public class Rope : MonoBehaviour
[Header("Anchors")] [SerializeField] public Rigidbody startAnchor;
[SerializeField] public Rigidbody endAnchor;
+ /// 鱼线宽度倍数
+ public int LineMultiple = 1;
+
[Header("Physics (Dynamic Nodes, Fixed Segment Len)")] [SerializeField, Min(0.01f), Tooltip("物理每段固定长度(越小越细致越耗)")]
private float physicsSegmentLen = 0.15f;
@@ -65,7 +68,7 @@ public class Rope : MonoBehaviour
[SerializeField, Tooltip("是否使用 Catmull-Rom 平滑(开启更顺,但更耗)")]
private bool smooth = true;
- [SerializeField, Min(0.0001f)] private float lineWidth = 0.002f;
+ [SerializeField, Min(0.0001f)] private float lineWidth = 0.001f;
[Header("Air Drag (Stable)")] [SerializeField, Range(0f, 5f), Tooltip("空气阻力(Y向),指数衰减,越大越不飘")]
private float airDrag = 0.9f;
@@ -75,14 +78,14 @@ public class Rope : MonoBehaviour
private LineRenderer _lineRenderer;
- // physics (注意:数组固定为 maxPhysicsNodes,physicsNodes 表示有效长度)
+ // physics
private int _physicsNodes;
private Vector3[] _pCurr;
private Vector3[] _pPrev;
- // render
+ // render (一次性分配到最大,后续不再 new)
private Vector3[] _rPoints;
- private int _rCountCached = -1;
+ private int _rCapacity;
private Vector3 _gravity;
@@ -97,17 +100,47 @@ public class Rope : MonoBehaviour
// node stability
private int _lastDesiredNodes = 0;
+ // caches
+ private Transform _startTr;
+ private Transform _endTr;
+
+ // precomputed
+ private float _dt;
+ private float _dt2;
+ private float _kY;
+ private float _kXZ;
+
private FRod _rod;
public void Init(FRod rod) => _rod = rod;
+ // Catmull t caches(只缓存 idle/moving 两档,减少每帧重复乘法)
+ private struct TCaches
+ {
+ public float[] t;
+ public float[] t2;
+ public float[] t3;
+ }
+ private TCaches _tIdle;
+ private TCaches _tMoving;
+
private void Awake()
{
_lineRenderer = GetComponent();
_gravity = new Vector3(0f, -gravityStrength, 0f);
+ _startTr = startAnchor ? startAnchor.transform : null;
+ _endTr = endAnchor ? endAnchor.transform : null;
+
InitLengthSystem();
AllocateAndInitNodes();
- RebuildRenderBufferIfNeeded(renderSubdivisionsIdle);
+
+ // ✅ 渲染点一次性分配到最大: (maxNodes-1)*idle + 1
+ int maxSubdiv = Mathf.Max(1, renderSubdivisionsIdle);
+ _rCapacity = (maxPhysicsNodes - 1) * maxSubdiv + 1;
+ _rPoints = new Vector3[_rCapacity];
+
+ BuildTCaches(renderSubdivisionsIdle, ref _tIdle);
+ BuildTCaches(renderSubdivisionsMoving, ref _tMoving);
}
private void OnValidate()
@@ -127,7 +160,6 @@ public class Rope : MonoBehaviour
headMinLen = Mathf.Max(headMinLen, 0.0001f);
nodeHysteresis = Mathf.Max(0f, nodeHysteresis);
-
}
private void InitLengthSystem()
@@ -141,7 +173,6 @@ public class Rope : MonoBehaviour
{
_physicsNodes = Mathf.Clamp(ComputeDesiredNodesStable(_currentLength), 2, maxPhysicsNodes);
- // ✅ 永久分配到最大,运行时不再 new,避免GC卡顿
_pCurr = new Vector3[maxPhysicsNodes];
_pPrev = new Vector3[maxPhysicsNodes];
@@ -181,10 +212,7 @@ public class Rope : MonoBehaviour
if (desired == _lastDesiredNodes)
return desired;
- // 边界(lastDesiredNodes 对应的长度临界)
float boundary = (_lastDesiredNodes - 1) * physicsSegmentLen;
-
- // 在临界附近就不切换,避免来回跳
if (Mathf.Abs(lengthMeters - boundary) < nodeHysteresis)
return _lastDesiredNodes;
@@ -192,11 +220,7 @@ public class Rope : MonoBehaviour
return desired;
}
- public void SetTargetLength(float lengthMeters)
- {
- _targetLength = Mathf.Max(0f, lengthMeters);
- }
-
+ public void SetTargetLength(float lengthMeters) => _targetLength = Mathf.Max(0f, lengthMeters);
public float GetCurrentLength() => _currentLength;
public float GetTargetLength() => _targetLength;
@@ -204,29 +228,36 @@ public class Rope : MonoBehaviour
{
if (!startAnchor || !endAnchor) return;
+ // cache dt
+ _dt = Time.fixedDeltaTime;
+ if (_dt < 1e-6f) _dt = 1e-6f;
+ _dt2 = _dt * _dt;
+
+ // gravity
_gravity.y = -gravityStrength;
+ // drag caches(exp 比较贵,但这里每 FixedUpdate 一次,OK)
+ _kY = Mathf.Exp(-airDrag * _dt);
+ _kXZ = Mathf.Exp(-airDragXZ * _dt);
+
UpdateLengthSmooth();
- UpdateNodesFromLength(); // ✅ 一次性增减,无GC
+ UpdateNodesFromLength();
UpdateHeadRestLenFromCurrentLength();
- // simulate
- Simulate();
+ Simulate_VerletFast();
- // 确保端点正确后再迭代
+ // anchors
LockAnchorsHard();
// constraints
for (int it = 0; it < iterations; it++)
- SolveDistanceConstraints_HeadOnly();
+ SolveDistanceConstraints_HeadOnly_Fast();
- // 迭代后锁一次足够
LockAnchorsHard();
if (constrainToGround)
ConstrainToGround();
- // 约束后再锁一次(保险)
LockAnchorsHard();
}
@@ -234,23 +265,23 @@ public class Rope : MonoBehaviour
{
if (!startAnchor || !endAnchor || _pCurr == null || _physicsNodes < 2) return;
- // 端点“跟手”:渲染前强行同步 transform,消除慢一拍
int last = _physicsNodes - 1;
- Vector3 s = startAnchor.transform.position;
- Vector3 e = endAnchor.transform.position;
+
+ // 用缓存 transform,避免多次属性链
+ Vector3 s = _startTr.position;
+ Vector3 e = _endTr.position;
_pCurr[0] = s;
_pPrev[0] = s;
_pCurr[last] = e;
_pPrev[last] = e;
- DrawHighResLine();
+ DrawHighResLine_Fast();
}
private void UpdateLengthSmooth()
{
float minFeasible = 0.01f;
-
float desired = Mathf.Max(_targetLength, minFeasible);
_currentLength = Mathf.SmoothDamp(
@@ -269,22 +300,12 @@ public class Rope : MonoBehaviour
desired = Mathf.Clamp(desired, 2, maxPhysicsNodes);
if (desired == _physicsNodes) return;
- if (desired > _physicsNodes)
- {
- AddNodesAtStart(desired - _physicsNodes);
- }
- else
- {
- RemoveNodesAtStart(_physicsNodes - desired);
- }
+ if (desired > _physicsNodes) AddNodesAtStart(desired - _physicsNodes);
+ else RemoveNodesAtStart(_physicsNodes - desired);
_physicsNodes = desired;
-
- // 渲染缓存按最大 subdiv 预留即可
- RebuildRenderBufferIfNeeded(renderSubdivisionsIdle);
}
-
private void AddNodesAtStart(int addCount)
{
if (addCount <= 0) return;
@@ -294,35 +315,30 @@ public class Rope : MonoBehaviour
addCount = newCount - oldCount;
if (addCount <= 0) return;
- // 把 [1..oldCount-1] 整体往后挪 addCount,给 [1..addCount] 腾位置
- // oldCount-1 个元素(包含尾端锚点位,尾端后面会被锁)
Array.Copy(_pCurr, 1, _pCurr, 1 + addCount, oldCount - 1);
Array.Copy(_pPrev, 1, _pPrev, 1 + addCount, oldCount - 1);
- Vector3 s = startAnchor.position;
+ Vector3 s = _startTr ? _startTr.position : startAnchor.position;
- // 方向:用“挪完后的第一个动态点”指向来估计
Vector3 dir = Vector3.down;
int firstOld = 1 + addCount;
- if (firstOld < 1 + addCount + (oldCount - 2) && oldCount >= 2)
- {
- Vector3 toOld1 = (_pCurr[firstOld] - s);
- if (toOld1.sqrMagnitude > 1e-6f) dir = toOld1.normalized;
- }
-
- // 继承速度
- float dt = Time.fixedDeltaTime;
- Vector3 inheritVel = Vector3.zero;
if (oldCount >= 2 && firstOld < maxPhysicsNodes)
{
- inheritVel = (_pCurr[firstOld] - _pPrev[firstOld]) / Mathf.Max(dt, 1e-6f);
+ Vector3 toOld1 = (_pCurr[firstOld] - s);
+ float sq = toOld1.sqrMagnitude;
+ if (sq > 1e-6f) dir = toOld1 / Mathf.Sqrt(sq);
}
+ // inherit displacement (Verlet)
+ Vector3 inheritDisp = Vector3.zero;
+ if (oldCount >= 2 && firstOld < maxPhysicsNodes)
+ inheritDisp = (_pCurr[firstOld] - _pPrev[firstOld]);
+
for (int k = 1; k <= addCount; k++)
{
Vector3 pos = s + dir * (physicsSegmentLen * k);
_pCurr[k] = pos;
- _pPrev[k] = pos - inheritVel * dt;
+ _pPrev[k] = pos - inheritDisp; // 保持动感
}
LockAnchorsHard();
@@ -337,8 +353,6 @@ public class Rope : MonoBehaviour
removeCount = oldCount - newCount;
if (removeCount <= 0) return;
- // 把 [1+removeCount .. 1+removeCount+(newCount-2)-1] 挪到 [1..newCount-2]
- // 中间动态节点数量 = newCount-2
Array.Copy(_pCurr, 1 + removeCount, _pCurr, 1, newCount - 2);
Array.Copy(_pPrev, 1 + removeCount, _pPrev, 1, newCount - 2);
@@ -354,29 +368,27 @@ public class Rope : MonoBehaviour
_headRestLen = Mathf.Clamp(_headRestLen, headMinLen, physicsSegmentLen * 1.5f);
}
- private void Simulate()
+ ///
+ /// ✅ 更快的 Verlet:去掉 /dt 和 *dt 抵消的无效计算
+ ///
+ private void Simulate_VerletFast()
{
- float dt = Time.fixedDeltaTime;
- float invDt = 1f / Mathf.Max(dt, 1e-6f);
-
- // 指数衰减:更稳定好调
- float kY = Mathf.Exp(-airDrag * dt);
- float kXZ = Mathf.Exp(-airDragXZ * dt);
-
+ // displacement = curr - prev
+ // next = curr + displacement*drag*dampen + gravity*dt^2
for (int i = 1; i < _physicsNodes - 1; i++)
{
- Vector3 vel = (_pCurr[i] - _pPrev[i]) * invDt;
+ Vector3 disp = _pCurr[i] - _pPrev[i];
- vel.x *= kXZ;
- vel.z *= kXZ;
- vel.y *= kY;
+ disp.x *= _kXZ;
+ disp.z *= _kXZ;
+ disp.y *= _kY;
- vel *= velocityDampen;
+ disp *= velocityDampen;
- Vector3 next = _pCurr[i] + vel * dt;
+ Vector3 next = _pCurr[i] + disp + _gravity * _dt2;
_pPrev[i] = _pCurr[i];
- _pCurr[i] = next + _gravity * (dt * dt);
+ _pCurr[i] = next;
}
}
@@ -384,21 +396,25 @@ public class Rope : MonoBehaviour
{
if (!startAnchor || !endAnchor || _pCurr == null || _pPrev == null || _physicsNodes < 2) return;
- float dt = Time.fixedDeltaTime;
- Vector3 s = startAnchor.position;
- Vector3 e = endAnchor.position;
+ Vector3 s = _startTr ? _startTr.position : startAnchor.position;
+ Vector3 e = _endTr ? _endTr.position : endAnchor.position;
_pCurr[0] = s;
- _pPrev[0] = s - startAnchor.linearVelocity * dt;
+ _pPrev[0] = s - startAnchor.linearVelocity * _dt;
int last = _physicsNodes - 1;
_pCurr[last] = e;
- _pPrev[last] = e - endAnchor.linearVelocity * dt;
+ _pPrev[last] = e - endAnchor.linearVelocity * _dt;
}
- private void SolveDistanceConstraints_HeadOnly()
+ ///
+ /// ✅ 约束:减少临时变量、用 sqrMagnitude + invDist
+ ///
+ private void SolveDistanceConstraints_HeadOnly_Fast()
{
- for (int i = 0; i < _physicsNodes - 1; i++)
+ int last = _physicsNodes - 1;
+
+ for (int i = 0; i < last; i++)
{
float rest = (i == 0) ? _headRestLen : physicsSegmentLen;
@@ -406,131 +422,151 @@ public class Rope : MonoBehaviour
Vector3 b = _pCurr[i + 1];
Vector3 delta = b - a;
- float dist = delta.magnitude;
- if (dist < 1e-6f) continue;
+ float sq = delta.sqrMagnitude;
+ if (sq < 1e-12f) continue;
- float diff = (dist - rest) / dist;
- Vector3 corr = delta * diff * stiffness;
+ float dist = Mathf.Sqrt(sq);
+ float diff = (dist - rest) / dist; // = 1 - rest/dist
+ Vector3 corr = delta * (diff * stiffness);
- if (i != 0)
- _pCurr[i] += corr * 0.5f;
-
- if (i + 1 != _physicsNodes - 1)
- _pCurr[i + 1] -= corr * 0.5f;
+ // i==0 锚点固定;last 锚点固定
+ if (i != 0) _pCurr[i] = a + corr * 0.5f;
+ if (i + 1 != last) _pCurr[i + 1] = b - corr * 0.5f;
}
}
private void ConstrainToGround()
{
+ if (groundMask == 0) return;
+
+ // RaycastHit 是 struct,这里不会 GC
for (int i = 1; i < _physicsNodes - 1; i++)
{
Vector3 p = _pCurr[i];
+ Vector3 origin = p + Vector3.up * groundCastHeight;
- if (constrainToGround && groundMask != 0)
+ if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, groundCastDistance, groundMask,
+ QueryTriggerInteraction.Ignore))
{
- Vector3 origin = p + Vector3.up * groundCastHeight;
- if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, groundCastDistance, groundMask,
- QueryTriggerInteraction.Ignore))
- {
- float minY = hit.point.y + groundRadius;
- if (p.y < minY) p.y = minY;
- }
+ float minY = hit.point.y + groundRadius;
+ if (p.y < minY) p.y = minY;
}
_pCurr[i] = p;
}
}
- private void DrawHighResLine()
+ private void DrawHighResLine_Fast()
{
if (_pCurr == null || _physicsNodes < 2) return;
- _lineRenderer.startWidth = lineWidth;
- _lineRenderer.endWidth = lineWidth;
+ float w = lineWidth * LineMultiple;
+ _lineRenderer.startWidth = w;
+ _lineRenderer.endWidth = w;
if (!smooth)
{
_lineRenderer.positionCount = _physicsNodes;
- // 只传有效段
- // LineRenderer.SetPositions 会读数组的前 positionCount 个
_lineRenderer.SetPositions(_pCurr);
return;
}
- // 动态降 subdiv:甩动时降低点数,减少CPU开销
- int subdiv = PickRenderSubdivisions();
- RebuildRenderBufferIfNeeded(subdiv);
+ int subdiv = PickRenderSubdivisions_Fast();
+ TCaches tc = (subdiv == renderSubdivisionsMoving) ? _tMoving : _tIdle;
+
+ int needed = (_physicsNodes - 1) * subdiv + 1;
+ if (needed > _rCapacity)
+ {
+ // 理论上不该发生(_rCapacity 用 maxNodes & idle 分配)
+ // 保险扩容一次
+ _rCapacity = needed;
+ _rPoints = new Vector3[_rCapacity];
+ }
int idx = 0;
+ int last = _physicsNodes - 1;
- for (int seg = 0; seg < _physicsNodes - 1; seg++)
+ for (int seg = 0; seg < last; seg++)
{
- Vector3 p0 = _pCurr[Mathf.Max(seg - 1, 0)];
- Vector3 p1 = _pCurr[seg];
- Vector3 p2 = _pCurr[seg + 1];
- Vector3 p3 = _pCurr[Mathf.Min(seg + 2, _physicsNodes - 1)];
+ int i0 = seg - 1; if (i0 < 0) i0 = 0;
+ int i1 = seg;
+ int i2 = seg + 1;
+ int i3 = seg + 2; if (i3 > last) i3 = last;
+
+ Vector3 p0 = _pCurr[i0];
+ Vector3 p1 = _pCurr[i1];
+ Vector3 p2 = _pCurr[i2];
+ Vector3 p3 = _pCurr[i3];
for (int s = 0; s < subdiv; s++)
{
- float t = s / (float)subdiv;
- Vector3 pt = CatmullRom_XZ_LinearY(p0, p1, p2, p3, t);
- _rPoints[idx++] = pt;
+ float t = tc.t[s];
+ float t2 = tc.t2[s];
+ float t3 = tc.t3[s];
+
+ // inline CatmullRom(少一次函数调用)
+ Vector3 cr =
+ 0.5f * (
+ (2f * p1) +
+ (-p0 + p2) * t +
+ (2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
+ (-p0 + 3f * p1 - 3f * p2 + p3) * t3
+ );
+
+ // Linear Y
+ cr.y = p1.y + (p2.y - p1.y) * t;
+
+ _rPoints[idx++] = cr;
}
}
- _rPoints[idx++] = _pCurr[_physicsNodes - 1];
+ _rPoints[idx++] = _pCurr[last];
_lineRenderer.positionCount = idx;
_lineRenderer.SetPositions(_rPoints);
}
- private int PickRenderSubdivisions()
+ ///
+ /// ✅ 用 sqrMagnitude 比较阈值,避免 sqrt
+ ///
+ private int PickRenderSubdivisions_Fast()
{
int idle = Mathf.Max(1, renderSubdivisionsIdle);
int moving = Mathf.Max(1, renderSubdivisionsMoving);
- // 计算平均速度(用 Verlet 差分)
- float dt = Time.fixedDeltaTime;
- float invDt = 1f / Mathf.Max(dt, 1e-6f);
+ float thr = movingSpeedThreshold;
+ float thrSq = (thr * _dt) * (thr * _dt); // 因为我们用 disp = curr-prev(单位是米/step),所以阈值要乘 dt
- float sum = 0f;
+ float sumSq = 0f;
int count = Mathf.Max(1, _physicsNodes - 2);
+
for (int i = 1; i < _physicsNodes - 1; i++)
- sum += (_pCurr[i] - _pPrev[i]).magnitude * invDt;
-
- float avgSpeed = sum / count;
-
- return (avgSpeed > movingSpeedThreshold) ? moving : idle;
- }
-
- private void RebuildRenderBufferIfNeeded(int subdiv)
- {
- int targetCount = (_physicsNodes - 1) * subdiv + 1;
- if (_rPoints == null || _rCountCached != targetCount)
{
- _rPoints = new Vector3[targetCount];
- _rCountCached = targetCount;
+ Vector3 disp = _pCurr[i] - _pPrev[i];
+ sumSq += disp.sqrMagnitude;
}
+
+ float avgSq = sumSq / count;
+
+ return (avgSq > thrSq) ? moving : idle;
}
- private static Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
+ private static void BuildTCaches(int subdiv, ref TCaches caches)
{
- float t2 = t * t;
- float t3 = t2 * t;
+ subdiv = Mathf.Max(1, subdiv);
+ caches.t = new float[subdiv];
+ caches.t2 = new float[subdiv];
+ caches.t3 = new float[subdiv];
- return 0.5f * (
- (2f * p1) +
- (-p0 + p2) * t +
- (2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
- (-p0 + 3f * p1 - 3f * p2 + p3) * t3
- );
- }
-
- private static Vector3 CatmullRom_XZ_LinearY(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
- {
- Vector3 cr = CatmullRom(p0, p1, p2, p3, t);
- cr.y = Mathf.Lerp(p1.y, p2.y, t);
- return cr;
+ float inv = 1f / subdiv;
+ for (int s = 0; s < subdiv; s++)
+ {
+ float t = s * inv;
+ float t2 = t * t;
+ caches.t[s] = t;
+ caches.t2[s] = t2;
+ caches.t3[s] = t2 * t;
+ }
}
private void OnDrawGizmosSelected()