@@ -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 ) ;