修改鱼线
This commit is contained in:
@@ -22,7 +22,7 @@ namespace NBF
|
||||
|
||||
#region Rod专属
|
||||
|
||||
private bool _stretchRope;
|
||||
private bool _stretchRope = true;
|
||||
|
||||
public bool StretchRope
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace NBF
|
||||
{
|
||||
protected override void Update(PlayerInput self)
|
||||
{
|
||||
self.UpdateInput();
|
||||
self.UpdateMove();
|
||||
}
|
||||
}
|
||||
@@ -46,6 +47,26 @@ namespace NBF
|
||||
|
||||
#region Input
|
||||
|
||||
private void UpdateInput()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Alpha0))
|
||||
{
|
||||
// SetLineLength(lineLength);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Plus) || Input.GetKeyDown(KeyCode.Equals))
|
||||
{
|
||||
Player.HandItem.LineLength += 0.1f;
|
||||
// lineLength += 0.1f;
|
||||
// SetLineLength(lineLength);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Minus))
|
||||
{
|
||||
Player.HandItem.LineLength -= 0.1f;
|
||||
// lineLength -= 0.1f;
|
||||
// SetLineLength(lineLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInputEvent()
|
||||
{
|
||||
InputManager.OnPlayerPerformed += OnPlayerCanceled;
|
||||
|
||||
@@ -55,17 +55,40 @@ public class Rope : MonoBehaviour
|
||||
[SerializeField, Min(0f)] private float groundRadius = 0.01f;
|
||||
[SerializeField, Min(0f)] private float groundCastHeight = 1.0f;
|
||||
[SerializeField, Min(0.01f)] private float groundCastDistance = 2.5f;
|
||||
|
||||
|
||||
[SerializeField, Range(1, 8), Tooltip("每隔多少个节点做一次地面检测;越大越省")]
|
||||
private int groundSampleStep = 3;
|
||||
|
||||
[SerializeField, Tooltip("未采样的点用插值还是直接拷贝邻近采样值")]
|
||||
private bool groundInterpolate = true;
|
||||
|
||||
|
||||
[SerializeField, Range(1, 8), Tooltip("每隔多少次FixedUpdate更新一次地面约束")]
|
||||
private int groundUpdateEvery = 2;
|
||||
private int _groundFrameCounter;
|
||||
|
||||
|
||||
[Header("Simple Water Float (Cheap)")]
|
||||
[SerializeField, Tooltip("绳子落到水面以下时,是否把节点约束回水面")]
|
||||
private bool constrainToWaterSurface = true;
|
||||
|
||||
[SerializeField, Tooltip("静态水面高度;如果你后面接波浪水面,可改成采样函数")]
|
||||
private float waterLevelY = 0f;
|
||||
|
||||
[SerializeField, Min(0f), Tooltip("把线抬到水面上方一点,避免视觉穿插")]
|
||||
private float waterSurfaceOffset = 0.002f;
|
||||
|
||||
[SerializeField, Range(1, 8), Tooltip("每隔多少个节点做一次水面约束采样;越大越省")]
|
||||
private int waterSampleStep = 2;
|
||||
|
||||
[SerializeField, Tooltip("未采样节点是否插值水面高度")]
|
||||
private bool waterInterpolate = true;
|
||||
|
||||
[SerializeField, Range(1, 8), Tooltip("每隔多少次FixedUpdate更新一次水面约束")]
|
||||
private int waterUpdateEvery = 1;
|
||||
|
||||
[SerializeField, Range(0, 8), Tooltip("水面约束后,再做几次长度约束,减少局部折角")]
|
||||
private int waterPostConstraintIterations = 2;
|
||||
|
||||
private int _waterFrameCounter;
|
||||
|
||||
[Header("Render (High Resolution)")] [SerializeField, Min(1), Tooltip("静止时每段物理线段插值加密数量(越大越顺,越耗)")]
|
||||
private int renderSubdivisionsIdle = 6;
|
||||
@@ -146,7 +169,6 @@ public class Rope : MonoBehaviour
|
||||
InitLengthSystem();
|
||||
AllocateAndInitNodes();
|
||||
|
||||
// ✅ 渲染点一次性分配到最大: (maxNodes-1)*idle + 1
|
||||
int maxSubdiv = Mathf.Max(1, renderSubdivisionsIdle);
|
||||
_rCapacity = (maxPhysicsNodes - 1) * maxSubdiv + 1;
|
||||
_rPoints = new Vector3[_rCapacity];
|
||||
@@ -172,6 +194,14 @@ public class Rope : MonoBehaviour
|
||||
|
||||
headMinLen = Mathf.Max(headMinLen, 0.0001f);
|
||||
nodeHysteresis = Mathf.Max(0f, nodeHysteresis);
|
||||
|
||||
groundSampleStep = Mathf.Max(1, groundSampleStep);
|
||||
groundUpdateEvery = Mathf.Max(1, groundUpdateEvery);
|
||||
|
||||
waterSampleStep = Mathf.Max(1, waterSampleStep);
|
||||
waterUpdateEvery = Mathf.Max(1, waterUpdateEvery);
|
||||
waterSurfaceOffset = Mathf.Max(0f, waterSurfaceOffset);
|
||||
waterPostConstraintIterations = Mathf.Clamp(waterPostConstraintIterations, 0, 8);
|
||||
}
|
||||
|
||||
private void InitLengthSystem()
|
||||
@@ -235,7 +265,6 @@ public class Rope : MonoBehaviour
|
||||
public void SetTargetLength(float lengthMeters) => _targetLength = Mathf.Max(0f, lengthMeters);
|
||||
public float GetCurrentLength() => _currentLength;
|
||||
public float GetTargetLength() => _targetLength;
|
||||
|
||||
public float GetLengthSmoothVel() => _lengthSmoothVel;
|
||||
|
||||
public float GetLengthByPoints()
|
||||
@@ -256,20 +285,16 @@ public class Rope : MonoBehaviour
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -279,10 +304,8 @@ public class Rope : MonoBehaviour
|
||||
|
||||
Simulate_VerletFast();
|
||||
|
||||
// anchors
|
||||
LockAnchorsHard();
|
||||
|
||||
// constraints
|
||||
for (int it = 0; it < iterations; it++)
|
||||
SolveDistanceConstraints_HeadOnly_Fast();
|
||||
|
||||
@@ -298,6 +321,20 @@ public class Rope : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
if (constrainToWaterSurface)
|
||||
{
|
||||
_waterFrameCounter++;
|
||||
if (_waterFrameCounter >= waterUpdateEvery)
|
||||
{
|
||||
_waterFrameCounter = 0;
|
||||
ConstrainToWaterSurface();
|
||||
|
||||
// 水面抬升后补几次长度约束,让形状更顺一点
|
||||
for (int it = 0; it < waterPostConstraintIterations; it++)
|
||||
SolveDistanceConstraints_HeadOnly_Fast();
|
||||
}
|
||||
}
|
||||
|
||||
LockAnchorsHard();
|
||||
}
|
||||
|
||||
@@ -307,7 +344,6 @@ public class Rope : MonoBehaviour
|
||||
|
||||
int last = _physicsNodes - 1;
|
||||
|
||||
// 用缓存 transform,避免多次属性链
|
||||
Vector3 s = _startTr.position;
|
||||
Vector3 e = _endTr.position;
|
||||
|
||||
@@ -332,6 +368,20 @@ public class Rope : MonoBehaviour
|
||||
Mathf.Infinity,
|
||||
Time.fixedDeltaTime
|
||||
);
|
||||
|
||||
// 长度变化时额外压一点速度,减少收放线时抖动
|
||||
float delta = Mathf.Abs(_targetLength - _currentLength);
|
||||
if (delta > 0.0001f && lengthChangeVelocityKill > 0f)
|
||||
{
|
||||
float keep = 1f - Mathf.Clamp01(lengthChangeVelocityKill);
|
||||
for (int i = 1; i < _physicsNodes - 1; i++)
|
||||
{
|
||||
Vector3 curr = _pCurr[i];
|
||||
Vector3 prev = _pPrev[i];
|
||||
Vector3 disp = curr - prev;
|
||||
_pPrev[i] = curr - disp * keep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNodesFromLength()
|
||||
@@ -369,7 +419,6 @@ public class Rope : MonoBehaviour
|
||||
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]);
|
||||
@@ -378,7 +427,7 @@ public class Rope : MonoBehaviour
|
||||
{
|
||||
Vector3 pos = s + dir * (physicsSegmentLen * k);
|
||||
_pCurr[k] = pos;
|
||||
_pPrev[k] = pos - inheritDisp; // 保持动感
|
||||
_pPrev[k] = pos - inheritDisp;
|
||||
}
|
||||
|
||||
LockAnchorsHard();
|
||||
@@ -408,13 +457,8 @@ public class Rope : MonoBehaviour
|
||||
_headRestLen = Mathf.Clamp(_headRestLen, headMinLen, physicsSegmentLen * 1.5f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ 更快的 Verlet:去掉 /dt 和 *dt 抵消的无效计算
|
||||
/// </summary>
|
||||
private void Simulate_VerletFast()
|
||||
{
|
||||
// displacement = curr - prev
|
||||
// next = curr + displacement*drag*dampen + gravity*dt^2
|
||||
for (int i = 1; i < _physicsNodes - 1; i++)
|
||||
{
|
||||
Vector3 disp = _pCurr[i] - _pPrev[i];
|
||||
@@ -447,9 +491,6 @@ public class Rope : MonoBehaviour
|
||||
_pPrev[last] = e - endAnchor.linearVelocity * _dt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ 约束:减少临时变量、用 sqrMagnitude + invDist
|
||||
/// </summary>
|
||||
private void SolveDistanceConstraints_HeadOnly_Fast()
|
||||
{
|
||||
int last = _physicsNodes - 1;
|
||||
@@ -466,36 +507,14 @@ public class Rope : MonoBehaviour
|
||||
if (sq < 1e-12f) continue;
|
||||
|
||||
float dist = Mathf.Sqrt(sq);
|
||||
float diff = (dist - rest) / dist; // = 1 - rest/dist
|
||||
float diff = (dist - rest) / dist;
|
||||
Vector3 corr = delta * (diff * stiffness);
|
||||
|
||||
// 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 (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;
|
||||
// }
|
||||
//
|
||||
// _pCurr[i] = p;
|
||||
// }
|
||||
// }
|
||||
|
||||
private void ConstrainToGround()
|
||||
{
|
||||
if (groundMask == 0) return;
|
||||
@@ -503,12 +522,9 @@ public class Rope : MonoBehaviour
|
||||
int last = _physicsNodes - 1;
|
||||
int step = Mathf.Max(1, groundSampleStep);
|
||||
|
||||
// 记录采样点的“最低允许Y”
|
||||
// 不想分配数组就用局部变量滚动插值
|
||||
int prevSampleIdx = 1;
|
||||
float prevMinY = SampleMinY(_pCurr[prevSampleIdx]);
|
||||
|
||||
// 把采样点先处理掉
|
||||
ApplyMinY(prevSampleIdx, prevMinY);
|
||||
|
||||
for (int i = 1 + step; i < last; i += step)
|
||||
@@ -518,7 +534,6 @@ public class Rope : MonoBehaviour
|
||||
|
||||
if (groundInterpolate)
|
||||
{
|
||||
// 在两个采样点之间插值 minY(视觉更平滑)
|
||||
int a = prevSampleIdx;
|
||||
int b = i;
|
||||
int span = b - a;
|
||||
@@ -532,7 +547,6 @@ public class Rope : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接用 prevMinY 填充中间点(更省)
|
||||
for (int idx = prevSampleIdx + 1; idx < i; idx++)
|
||||
ApplyMinY(idx, prevMinY);
|
||||
}
|
||||
@@ -541,7 +555,6 @@ public class Rope : MonoBehaviour
|
||||
prevMinY = nextMinY;
|
||||
}
|
||||
|
||||
// 尾巴剩余部分用最后一个采样值填
|
||||
for (int i = prevSampleIdx + 1; i < last; i++)
|
||||
ApplyMinY(i, prevMinY);
|
||||
}
|
||||
@@ -553,16 +566,84 @@ public class Rope : MonoBehaviour
|
||||
QueryTriggerInteraction.Ignore))
|
||||
return hit.point.y + groundRadius;
|
||||
|
||||
// 没命中就不抬(返回极小值)
|
||||
return float.NegativeInfinity;
|
||||
}
|
||||
|
||||
private void ApplyMinY(int i, float minY)
|
||||
{
|
||||
if (float.IsNegativeInfinity(minY)) return;
|
||||
|
||||
Vector3 p = _pCurr[i];
|
||||
if (p.y < minY) p.y = minY;
|
||||
_pCurr[i] = p;
|
||||
if (p.y < minY)
|
||||
{
|
||||
p.y = minY;
|
||||
_pCurr[i] = p;
|
||||
|
||||
// prev 同步抬上来,避免下一帧又被惯性拉回去造成抖动
|
||||
Vector3 prev = _pPrev[i];
|
||||
if (prev.y < minY) prev.y = minY;
|
||||
_pPrev[i] = prev;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConstrainToWaterSurface()
|
||||
{
|
||||
int last = _physicsNodes - 1;
|
||||
if (last <= 1) return;
|
||||
|
||||
int step = Mathf.Max(1, waterSampleStep);
|
||||
float surfaceY = waterLevelY + waterSurfaceOffset;
|
||||
|
||||
int prevSampleIdx = 1;
|
||||
float prevSurfaceY = surfaceY;
|
||||
|
||||
ApplyWaterSurface(prevSampleIdx, prevSurfaceY);
|
||||
|
||||
for (int i = 1 + step; i < last; i += step)
|
||||
{
|
||||
float nextSurfaceY = surfaceY;
|
||||
ApplyWaterSurface(i, nextSurfaceY);
|
||||
|
||||
if (waterInterpolate)
|
||||
{
|
||||
int a = prevSampleIdx;
|
||||
int b = i;
|
||||
int span = b - a;
|
||||
for (int j = 1; j < span; j++)
|
||||
{
|
||||
int idx = a + j;
|
||||
float t = j / (float)span;
|
||||
float y = Mathf.Lerp(prevSurfaceY, nextSurfaceY, t);
|
||||
ApplyWaterSurface(idx, y);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int idx = prevSampleIdx + 1; idx < i; idx++)
|
||||
ApplyWaterSurface(idx, prevSurfaceY);
|
||||
}
|
||||
|
||||
prevSampleIdx = i;
|
||||
prevSurfaceY = nextSurfaceY;
|
||||
}
|
||||
|
||||
for (int i = prevSampleIdx + 1; i < last; i++)
|
||||
ApplyWaterSurface(i, prevSurfaceY);
|
||||
}
|
||||
|
||||
private void ApplyWaterSurface(int i, float surfaceY)
|
||||
{
|
||||
Vector3 p = _pCurr[i];
|
||||
if (p.y < surfaceY)
|
||||
{
|
||||
p.y = surfaceY;
|
||||
_pCurr[i] = p;
|
||||
|
||||
// 同步 prev,杀掉向下惯性,避免反复穿透水面
|
||||
Vector3 prev = _pPrev[i];
|
||||
if (prev.y < surfaceY) prev.y = surfaceY;
|
||||
_pPrev[i] = prev;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHighResLine_Fast()
|
||||
@@ -586,8 +667,6 @@ public class Rope : MonoBehaviour
|
||||
int needed = (_physicsNodes - 1) * subdiv + 1;
|
||||
if (needed > _rCapacity)
|
||||
{
|
||||
// 理论上不该发生(_rCapacity 用 maxNodes & idle 分配)
|
||||
// 保险扩容一次
|
||||
_rCapacity = needed;
|
||||
_rPoints = new Vector3[_rCapacity];
|
||||
}
|
||||
@@ -615,7 +694,6 @@ public class Rope : MonoBehaviour
|
||||
float t2 = tc.t2[s];
|
||||
float t3 = tc.t3[s];
|
||||
|
||||
// inline CatmullRom(少一次函数调用)
|
||||
Vector3 cr =
|
||||
0.5f * (
|
||||
(2f * p1) +
|
||||
@@ -624,7 +702,6 @@ public class Rope : MonoBehaviour
|
||||
(-p0 + 3f * p1 - 3f * p2 + p3) * t3
|
||||
);
|
||||
|
||||
// Linear Y
|
||||
cr.y = p1.y + (p2.y - p1.y) * t;
|
||||
|
||||
_rPoints[idx++] = cr;
|
||||
@@ -637,16 +714,13 @@ public class Rope : MonoBehaviour
|
||||
_lineRenderer.SetPositions(_rPoints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ 用 sqrMagnitude 比较阈值,避免 sqrt
|
||||
/// </summary>
|
||||
private int PickRenderSubdivisions_Fast()
|
||||
{
|
||||
int idle = Mathf.Max(1, renderSubdivisionsIdle);
|
||||
int moving = Mathf.Max(1, renderSubdivisionsMoving);
|
||||
|
||||
float thr = movingSpeedThreshold;
|
||||
float thrSq = (thr * _dt) * (thr * _dt); // 因为我们用 disp = curr-prev(单位是米/step),所以阈值要乘 dt
|
||||
float thrSq = (thr * _dt) * (thr * _dt);
|
||||
|
||||
float sumSq = 0f;
|
||||
int count = Mathf.Max(1, _physicsNodes - 2);
|
||||
|
||||
Reference in New Issue
Block a user