@@ -14,23 +14,29 @@ public class Rope : MonoBehaviour
[Header("Physics (Dynamic Nodes, Fixed Segment Len)")] [ SerializeField , Min ( 0.01f ) , Tooltip ( "物理每段固定长度(越小越细致越耗)" ) ]
private float physicsSegmentLen = 0.15f ;
[SerializeField, Range(2, 200)] private int minPhysicsNodes = 1 2;
[SerializeField, Range(2, 200)] private int minPhysicsNodes = 2 ;
[SerializeField, Range(2, 400), Tooltip("物理节点上限(仅用于性能保护;与“最大长度不限制”不是一回事)")]
private int maxPhysicsNodes = 1 20;
private int maxPhysicsNodes = 20 0 ;
[SerializeField] private float gravityStrength = 2 .0f;
[SerializeField] private float gravityStrength = 6 .0f;
[SerializeField, Range(0f, 1f)] private float velocityDampen = 0.95f ;
[SerializeField, Range(0.0f, 1.0f), Tooltip("约束修正强度, 越大越硬。0.6~0.9 常用")]
private float stiffness = 0.8f ;
[SerializeField, Range(1, 80), Tooltip("迭代次数。鱼线 10~30 通常够用 ")]
private int iterations = 2 0;
[SerializeField, Range(1, 80), Tooltip("迭代次数")]
private int iterations = 1 0;
[SerializeField, Range(0, 16), Tooltip("主求解后追加的硬长度约束次数。只负责把 poly 拉回到 rest total, 不改变可变长度逻辑")]
private int hardTightenIterations = 2 ;
[SerializeField, Range(0, 32), Tooltip("当绳子接近拉直时,按误差自动追加的硬长度约束次数上限")]
private int adaptiveHardTightenMaxIterations = 8 ;
[SerializeField, Min(0f), Tooltip("单段允许的最大超长误差;超过时继续追加硬长度约束")]
private float hardConstraintTolerance = 0.0005f ;
[Header("Length Control (No Min/Max Clamp)")]
[Tooltip("初始总长度(米)。如果为 0, 则用 physicsSegmentLen*(minPhysicsNodes-1) 作为初始长度")]
[SerializeField, Min(0f)]
@@ -40,7 +46,7 @@ public class Rope : MonoBehaviour
private float lengthSmoothTime = 0.15f ;
[Tooltip("当长度在变化时, 额外把速度压掉一些( 防抖) 。0=不额外处理, 1=变化时几乎清速度(建议只在收线生效)")] [ SerializeField , Range ( 0f , 1f ) ]
private float lengthChangeVelocityKill = 0.6 f ;
private float lengthChangeVelocityKill = 0.4 f ;
[Tooltip("允许的最小松弛余量(避免目标长度刚好等于锚点距离时抖动)")] [ SerializeField , Min ( 0f ) ]
private float minSlack = 0.002f ;
@@ -52,9 +58,9 @@ public class Rope : MonoBehaviour
private float nodeHysteresis = 0.05f ;
[Header("Simple Ground/Water Constraint (Cheap)")] [ SerializeField ]
private bool constrainToGround = tru e;
private bool constrainToGround = fals e;
[SerializeField] private LayerMask groundMask = ~ 0 ;
[SerializeField] private LayerMask groundMask = 0 ;
[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 ;
@@ -66,7 +72,7 @@ public class Rope : MonoBehaviour
private bool groundInterpolate = true ;
[SerializeField, Range(1, 8), Tooltip("每隔多少次FixedUpdate更新一次地面约束")]
private int groundUpdateEvery = 2 ;
private int groundUpdateEvery = 1 ;
[SerializeField, Range(0, 8), Tooltip("地面约束后,再做几次长度约束,减少 poly 被地面抬长")]
private int groundPostConstraintIterations = 2 ;
@@ -74,7 +80,7 @@ public class Rope : MonoBehaviour
private int _groundFrameCounter ;
[Header("Simple Water Float (Cheap)")] [ SerializeField , Tooltip ( "绳子落到水面以下时,是否把节点约束回水面" ) ]
private bool constrainToWaterSurface = tru e;
private bool constrainToWaterSurface = fals e;
[SerializeField, Tooltip("静态水面高度;如果你后面接波浪水面,可改成采样函数")]
private float waterLevelY = 0f ;
@@ -129,7 +135,7 @@ public class Rope : MonoBehaviour
private float visibilityViewportPadding = 0.08f ;
[Header("Air Drag (Stable)")] [ SerializeField , Range ( 0f , 5f ) , Tooltip ( "空气阻力( Y向) , 指数衰减, 越大越不飘" ) ]
private float airDrag = 0.9 f ;
private float airDrag = 0.2 f ;
[SerializeField, Range(0f, 2f), Tooltip("横向额外阻力( XZ) , 指数衰减, 越大越不左右飘")]
private float airDragXZ = 0.6f ;
@@ -172,7 +178,7 @@ public class Rope : MonoBehaviour
private bool _isCulledByVisibility ;
private int _tIdleSubdiv = - 1 ;
private int _tMovingSubdiv = - 1 ;
// Catmull t caches( 只缓存 idle/moving 两档,减少每帧重复乘法)
private struct TCaches
@@ -198,12 +204,15 @@ public class Rope : MonoBehaviour
RefreshVisibilityState ( true ) ;
}
private void OnValidate ( )
{
renderSubdivisionsIdle = Mathf . Max ( renderSubdivisionsIdle , 1 ) ;
renderSubdivisionsMoving = Mathf . Max ( renderSubdivisionsMoving , 1 ) ;
iterations = Mathf . Clamp ( iterations , 1 , 80 ) ;
hardTightenIterations = Mathf . Clamp ( hardTightenIterations , 0 , 16 ) ;
adaptiveHardTightenMaxIterations = Mathf . Clamp ( adaptiveHardTightenMaxIterations , 0 , 32 ) ;
hardConstraintTolerance = Mathf . Max ( 0f , hardConstraintTolerance ) ;
groundCastDistance = Mathf . Max ( groundCastDistance , 0.01f ) ;
groundCastHeight = Mathf . Max ( groundCastHeight , 0f ) ;
lineWidth = Mathf . Max ( lineWidth , 0.0001f ) ;
@@ -236,15 +245,6 @@ public class Rope : MonoBehaviour
_endTr = endAnchor ? endAnchor . transform : null ;
}
private bool ShouldAlwaysSimulate ( )
{
if ( ! localOwnerAlwaysSimulate )
return false ;
// var owner = _rod?.PlayerItem?.Owner;
// return owner == null || owner.IsSelf;
return true ;
}
private Transform GetActiveCameraTransform ( )
{
@@ -298,7 +298,7 @@ public class Rope : MonoBehaviour
private void RefreshVisibilityState ( bool force = false )
{
if ( ! cullRemoteRopeWhenInvisible | | ShouldAlwaysSimulate ( ) )
if ( ! cullRemoteRopeWhenInvisible )
{
_isCulledByVisibility = false ;
if ( _lineRenderer )
@@ -526,6 +526,7 @@ public class Rope : MonoBehaviour
}
SolveHardDistanceConstraints ( hardTightenIterations ) ;
SolveHardDistanceConstraintsAdaptive ( ) ;
LockAnchorsHard ( ) ;
if ( constrainToGround )
@@ -566,16 +567,7 @@ public class Rope : MonoBehaviour
EnsureRenderCaches ( ) ;
int last = _physicsNodes - 1 ;
Vector3 s = _startTr . position ;
Vector3 e = _endTr . position ;
_pCurr [ 0 ] = s ;
_pCurr [ last ] = e ;
// _pPrev[0] = s;
// _pPrev[last] = e;
DrawHighResLine_Fast ( ) ;
DrawHighResLine_Fast ( _startTr . position , _endTr . position , last ) ;
}
private void UpdateLengthSmooth ( )
@@ -592,9 +584,10 @@ public class Rope : MonoBehaviour
Time . fixedDeltaTime
) ;
// 长度变化时额外压一点速度,减少收放线时抖动
float delta = Mathf . Abs ( _targetLength - _currentLength ) ;
if ( delta > 0.0001f & & lengthChangeVelocityKill > 0f )
// 仅在收线(目标长度小于当前长度)时额外压速度;
// 放线时不要压速度,否则新增节点下落会出现“顿一下再突然加速”。
float reelInDelta = _currentLength - _targetLength ;
if ( reelInDelta > 0.0001f & & lengthChangeVelocityKill > 0f )
{
float keep = 1f - Mathf . Clamp01 ( lengthChangeVelocityKill ) ;
for ( int i = 1 ; i < _physicsNodes - 1 ; i + + )
@@ -728,6 +721,21 @@ public class Rope : MonoBehaviour
}
}
private void SolveHardDistanceConstraintsAdaptive ( )
{
if ( adaptiveHardTightenMaxIterations < = 0 | | hardConstraintTolerance < = 0f )
return ;
for ( int it = 0 ; it < adaptiveHardTightenMaxIterations ; it + + )
{
if ( GetMaxPositiveSegmentDelta ( ) < = hardConstraintTolerance )
break ;
LockAnchorsHard ( ) ;
SolveDistanceConstraints_HeadOnly_Hard ( ) ;
}
}
private void SolveDistanceConstraints_HeadOnly_Hard ( )
{
SolveDistanceConstraints_HeadOnly_Bidirectional ( 1f ) ;
@@ -744,7 +752,8 @@ public class Rope : MonoBehaviour
SolveDistanceConstraintsSweep_Fast ( last - 1 , - 1 , - 1 , last , sweepStiffness ) ;
}
private void SolveDistanceConstraintsSweep_Fast ( int start , int endExclusive , int step , int last , float sweepStiffness )
private void SolveDistanceConstraintsSweep_Fast ( int start , int endExclusive , int step , int last ,
float sweepStiffness )
{
for ( int i = start ; i ! = endExclusive ; i + = step )
{
@@ -781,6 +790,21 @@ public class Rope : MonoBehaviour
}
}
private float GetMaxPositiveSegmentDelta ( )
{
float maxDelta = 0f ;
for ( int i = 1 ; i < _physicsNodes ; i + + )
{
float rest = ( i = = 1 ) ? _headRestLen : physicsSegmentLen ;
float segLen = Vector3 . Distance ( _pCurr [ i - 1 ] , _pCurr [ i ] ) ;
float delta = segLen - rest ;
if ( delta > maxDelta )
maxDelta = delta ;
}
return maxDelta ;
}
private void ConstrainToGround ( )
{
if ( groundMask = = 0 ) return ;
@@ -932,7 +956,7 @@ public class Rope : MonoBehaviour
}
}
private void DrawHighResLine_Fast ( )
private void DrawHighResLine_Fast ( Vector3 renderStart , Vector3 renderEnd , int last )
{
if ( _pCurr = = null | | _physicsNodes < 2 ) return ;
@@ -943,7 +967,9 @@ public class Rope : MonoBehaviour
if ( ! smooth )
{
_lineRenderer . positionCount = _physicsNodes ;
_lineRenderer . SetPositions ( _pCurr ) ;
for ( int i = 0 ; i < = last ; i + + )
_rPoints [ i ] = GetRenderPoint ( i , last , renderStart , renderEnd ) ;
_lineRenderer . SetPositions ( _rPoints ) ;
return ;
}
@@ -958,7 +984,6 @@ public class Rope : MonoBehaviour
}
int idx = 0 ;
int last = _physicsNodes - 1 ;
for ( int seg = 0 ; seg < last ; seg + + )
{
@@ -969,10 +994,10 @@ public class Rope : MonoBehaviour
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 ] ;
Vector3 p0 = GetRenderPoint ( i0 , last , renderStart , renderEnd ) ;
Vector3 p1 = GetRenderPoint ( i1 , last , renderStart , renderEnd ) ;
Vector3 p2 = GetRenderPoint ( i2 , last , renderStart , renderEnd ) ;
Vector3 p3 = GetRenderPoint ( i3 , last , renderStart , renderEnd ) ;
for ( int s = 0 ; s < subdiv ; s + + )
{
@@ -995,12 +1020,21 @@ public class Rope : MonoBehaviour
}
}
_rPoints [ idx + + ] = _pCurr [ last ] ;
_rPoints [ idx + + ] = renderEnd ;
_lineRenderer . positionCount = idx ;
_lineRenderer . SetPositions ( _rPoints ) ;
}
private Vector3 GetRenderPoint ( int index , int last , Vector3 renderStart , Vector3 renderEnd )
{
if ( index < = 0 )
return renderStart ;
if ( index > = last )
return renderEnd ;
return _pCurr [ index ] ;
}
private static float ClampMonotonic ( float value , float p0 , float p1 , float p2 , float p3 )
{
bool rising = p0 < = p1 & & p1 < = p2 & & p2 < = p3 ;
@@ -1060,4 +1094,4 @@ public class Rope : MonoBehaviour
for ( int i = 0 ; i < _physicsNodes ; i + + )
Gizmos . DrawSphere ( _pCurr [ i ] , 0.01f ) ;
}
}
}