@@ -41,11 +41,10 @@ public class Rope : MonoBehaviour
[Header("Head Segment Clamp")] [ Tooltip ( "第一段(起点->第1节点) 允许的最小长度, 避免收线时第一段被压到0导致数值炸" ) ] [ SerializeField , Min ( 0.0001f ) ]
private float headMinLen = 0.01f ;
[Header("Collision Filter")]
[SerializeField, Tooltip("只对这些Layer进行物理检测( Raycast/SphereCast等) 。不在这里的层完全不检测。")]
[Header("Collision Filter")] [ SerializeField , Tooltip ( "只对这些Layer进行物理检测( Raycast/SphereCast等) 。不在这里的层完全不检测。" ) ]
private LayerMask collisionMask = ~ 0 ;
[Header("Simple Ground/Water Constraint (Cheap)")] [ SerializeField ]
private bool constrainToGround = true ;
@@ -61,6 +60,32 @@ public class Rope : MonoBehaviour
[Header("Render (High Resolution)")] [ SerializeField , Min ( 1 ) , Tooltip ( "每段物理线段插值加密的数量(越大越顺,越耗)" ) ]
private int renderSubdivisions = 6 ;
[Header("Air / Wind (For Fishing Line Feel)")]
[SerializeField, Range(0f, 5f), Tooltip("空气线性阻力(越大越不飘,空中更自然)")]
private float airDrag = 0.9f ;
[SerializeField, Range(0f, 2f), Tooltip("横向额外阻力(减少左右飘得太夸张)")]
private float airDragXZ = 0.6f ;
[SerializeField, Tooltip("风方向(世界空间)")]
private Vector3 windDir = new Vector3 ( 1f , 0f , 0f ) ;
[SerializeField, Range(0f, 10f), Tooltip("基础风强度( m/s 级别的感觉)")]
private float windStrength = 0.3f ;
[SerializeField, Range(0f, 2f), Tooltip("阵风幅度( 0=无阵风)")]
private float windGust = 0.25f ;
[SerializeField, Range(0.1f, 5f), Tooltip("阵风频率")]
private float windFreq = 1.2f ;
[Header("Bending (Smooth Curve)")]
[SerializeField, Range(0f, 1f), Tooltip("抗折/弯曲刚度( 0=完全不抗折, 0.1~0.3 比较像鱼线)")]
private float bendStiffness = 0.18f ;
[SerializeField, Tooltip("是否使用 Catmull-Rom 平滑(推荐开启)")]
private bool smooth = true ;
@@ -98,7 +123,7 @@ public class Rope : MonoBehaviour
AllocateAndInitNodes ( ) ;
RebuildRenderBufferIfNeeded ( ) ;
}
private FRod _rod ;
public void Init ( FRod rod )
@@ -121,7 +146,7 @@ public class Rope : MonoBehaviour
maxPhysicsNodes = Mathf . Max ( maxPhysicsNodes , minPhysicsNodes ) ;
headMinLen = Mathf . Max ( headMinLen , 0.0001f ) ;
// 如果你希望只用一个mask控制, 避免 groundMask 忘了配
if ( groundMask = = ~ 0 )
groundMask = collisionMask ;
@@ -137,15 +162,6 @@ public class Rope : MonoBehaviour
private void AllocateAndInitNodes ( )
{
// 若锚点存在:最小长度就是两锚点直线距离 + minSlack( 防抖)
// if (startAnchor && endAnchor)
// {
// float minFeasible = Vector3.Distance(startAnchor.position, endAnchor.position) + minSlack;
// minFeasible -= 0.2f;
// currentLength = Mathf.Max(currentLength, minFeasible);
// targetLength = Mathf.Max(targetLength, minFeasible);
// }
physicsNodes = Mathf . Clamp ( ComputeDesiredNodes ( currentLength ) , 2 , maxPhysicsNodes ) ;
pCurr = new Vector3 [ physicsNodes ] ;
pPrev = new Vector3 [ physicsNodes ] ;
@@ -180,7 +196,7 @@ public class Rope : MonoBehaviour
{
targetLength = Mathf . Max ( 0f , lengthMeters ) ;
}
public float GetCurrentLength ( ) = > currentLength ;
public float GetTargetLength ( ) = > targetLength ;
@@ -195,8 +211,8 @@ public class Rope : MonoBehaviour
return 0 ;
}
private void FixedUpdate2 ( )
private void FixedUpdate ( )
{
if ( ! startAnchor | | ! endAnchor )
return ;
@@ -212,6 +228,7 @@ public class Rope : MonoBehaviour
for ( int it = 0 ; it < iterations ; it + + )
{
SolveDistanceConstraints_HeadOnly ( ) ;
SolveBendConstraint ( ) ;
LockAnchorsHard ( ) ;
}
@@ -221,10 +238,19 @@ public class Rope : MonoBehaviour
LockAnchorsHard ( ) ;
}
private void LateUpdate ( )
{
if ( ! startAnchor | | ! endAnchor | | pCurr = = null | | physicsNodes < 2 ) return ;
int last = physicsNodes - 1 ;
Vector3 s = startAnchor . transform . position ;
Vector3 e = endAnchor . transform . position ;
pCurr [ 0 ] = s ; pPrev [ 0 ] = s ; // ✅ 关键:同步 pPrev
pCurr [ last ] = e ; pPrev [ last ] = e ; // ✅ 关键:同步 pPrev
DrawHighResLine ( ) ;
FixedUpdate2 ( ) ;
}
private void UpdateLengthSmooth ( )
@@ -348,18 +374,69 @@ public class Rope : MonoBehaviour
private void Simulate ( )
{
float dt = Time . fixedDeltaTime ;
float invDt = 1f / Mathf . Max ( dt , 1e-6f ) ;
// 风方向归一化( 避免填了0向量导致NaN)
Vector3 wDir = windDir ;
if ( wDir . sqrMagnitude < 1e-6f ) wDir = Vector3 . right ;
wDir . Normalize ( ) ;
for ( int i = 0 ; i < physicsNodes ; i + + )
{
Vector3 v = ( pCurr [ i ] - pPrev [ i ] ) * velocityDampen ;
pPrev [ i ] = pCurr [ i ] ;
// Verlet 速度(由当前位置和上一帧位置推出来)
Vector3 vel = ( pCurr [ i ] - pPrev [ i ] ) * invDt ;
pCurr [ i ] + = v ;
pCurr [ i ] + = gravity * dt ;
// 先做“惯性推进”
Vector3 next = pCurr [ i ] + ( pCurr [ i ] - pPrev [ i ] ) * velocityDampen ;
// 加速度 = 重力 + 空气阻力 + 风(相对速度)
Vector3 acc = gravity ;
// --- 空气阻力(与速度成正比)---
// drag = -vel * airDrag, 并且横向更强一点
Vector3 drag = - vel * airDrag ;
drag . x * = ( 1f + airDragXZ ) ;
drag . z * = ( 1f + airDragXZ ) ;
acc + = drag ;
// --- 风(让线在空中不那么“只会垂直掉”)---
if ( i ! = 0 & & i ! = physicsNodes - 1 & & windStrength > 0f )
{
float t = Time . time ;
float gust = 1f + Mathf . Sin ( t * windFreq + i * 0.35f ) * windGust ;
// windVel: 风希望空气把线速度拉向这个“风速”
Vector3 windVel = wDir * ( windStrength * gust ) ;
// 相对风:让加速度朝 (windVel - vel) 方向
// 系数越大,越“被风带着走”
acc + = ( windVel - vel ) * 0.5f ;
}
// Verlet: 位置 += acc * dt^2
pPrev [ i ] = pCurr [ i ] ;
pCurr [ i ] = next + acc * ( dt * dt ) ;
}
// 物理步末尾硬锁端点
LockAnchorsHard ( ) ;
}
// private void Simulate()
// {
// float dt = Time.fixedDeltaTime;
//
// for (int i = 0; i < physicsNodes; i++)
// {
// Vector3 v = (pCurr[i] - pPrev[i]) * velocityDampen;
// pPrev[i] = pCurr[i];
//
// pCurr[i] += v;
// pCurr[i] += gravity * dt;
// }
//
// LockAnchorsHard();
// }
private void LockAnchorsHard ( )
{
@@ -367,7 +444,7 @@ public class Rope : MonoBehaviour
float dt = Time . fixedDeltaTime ;
Vector3 s = startAnchor . position ;
Vector3 e = endAnchor . position ;
pCurr [ 0 ] = s ;
pPrev [ 0 ] = s - startAnchor . linearVelocity * dt ;
@@ -399,6 +476,35 @@ public class Rope : MonoBehaviour
pCurr [ i + 1 ] - = corr * 0.5f ;
}
}
private void SolveBendConstraint ( )
{
if ( bendStiffness < = 0f ) return ;
if ( physicsNodes < 3 ) return ;
// bendStiffness 在迭代里用太大很容易爆,先做一个安全钳制
float kBase = Mathf . Clamp01 ( bendStiffness ) ;
for ( int i = 1 ; i < physicsNodes - 1 ; i + + )
{
// 端点不要动(你本来就没动,这里保持)
if ( i = = 0 | | i = = physicsNodes - 1 ) continue ;
Vector3 mid = ( pCurr [ i - 1 ] + pCurr [ i + 1 ] ) * 0.5f ;
float k = kBase ;
if ( i < = 2 ) k * = 1.25f ; // 靠近竿尖稍微更“直”一点
Vector3 old = pCurr [ i ] ;
Vector3 newPos = Vector3 . Lerp ( old , mid , k ) ;
Vector3 delta = newPos - old ;
// ✅ 关键:同样把 pPrev 挪过去,避免“凭空制造速度”
pCurr [ i ] = newPos ;
pPrev [ i ] + = delta ;
}
}
private void ConstrainToGroundAndWater ( )
{