修改鱼线逻辑
This commit is contained in:
@@ -41538,8 +41538,8 @@ MonoBehaviour:
|
|||||||
Dependencies: []
|
Dependencies: []
|
||||||
Tags:
|
Tags:
|
||||||
- Name: main/plyaer.bundle
|
- Name: main/plyaer.bundle
|
||||||
Hash: 3f8f7efec7b92de0aa2fee96a290c14b
|
Hash: e4ec05a4b781eddaae2f07fc44c008f4
|
||||||
Size: 378598645
|
Size: 378599896
|
||||||
Assets:
|
Assets:
|
||||||
- Path: Assets/ResRaw/Prefabs/Line/fishing line float set.prefab
|
- Path: Assets/ResRaw/Prefabs/Line/fishing line float set.prefab
|
||||||
Address: Plyaer/fishing line float set
|
Address: Plyaer/fishing line float set
|
||||||
|
|||||||
@@ -50,31 +50,30 @@ MonoBehaviour:
|
|||||||
physicsSegmentLen: 0.1
|
physicsSegmentLen: 0.1
|
||||||
minPhysicsNodes: 2
|
minPhysicsNodes: 2
|
||||||
maxPhysicsNodes: 200
|
maxPhysicsNodes: 200
|
||||||
gravityStrength: 2
|
gravityStrength: 6
|
||||||
velocityDampen: 0.95
|
velocityDampen: 0.95
|
||||||
stiffness: 0.8
|
stiffness: 0.8
|
||||||
iterations: 20
|
iterations: 20
|
||||||
initialLength: 0
|
initialLength: 0
|
||||||
lengthSmoothTime: 0.15
|
lengthSmoothTime: 0.15
|
||||||
lengthChangeVelocityKill: 0.6
|
lengthChangeVelocityKill: 0.4
|
||||||
minSlack: 0.002
|
minSlack: 0.002
|
||||||
headMinLen: 0.01
|
headMinLen: 0.01
|
||||||
collisionMask:
|
nodeHysteresis: 0.05
|
||||||
serializedVersion: 2
|
|
||||||
m_Bits: 24
|
|
||||||
constrainToGround: 1
|
constrainToGround: 1
|
||||||
groundMask:
|
groundMask:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_Bits: 4513599
|
m_Bits: 24
|
||||||
groundRadius: 0.01
|
groundRadius: 0.01
|
||||||
groundCastHeight: 1
|
groundCastHeight: 1
|
||||||
groundCastDistance: 2.5
|
groundCastDistance: 2.5
|
||||||
constrainToWater: 0
|
renderSubdivisionsIdle: 6
|
||||||
waterHeight: 0
|
renderSubdivisionsMoving: 2
|
||||||
waterRadius: 0.01
|
movingSpeedThreshold: 2
|
||||||
renderSubdivisions: 6
|
|
||||||
smooth: 1
|
smooth: 1
|
||||||
lineWidth: 0.005
|
lineWidth: 0.001
|
||||||
|
airDrag: 0.2
|
||||||
|
airDragXZ: 0.6
|
||||||
--- !u!120 &991521994724602848
|
--- !u!120 &991521994724602848
|
||||||
LineRenderer:
|
LineRenderer:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -663,10 +662,10 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
startAnchor: {fileID: 54298866000586118}
|
startAnchor: {fileID: 54298866000586118}
|
||||||
endAnchor: {fileID: 54679398375713381}
|
endAnchor: {fileID: 54679398375713381}
|
||||||
physicsSegmentLen: 0.1
|
physicsSegmentLen: 0.2
|
||||||
minPhysicsNodes: 2
|
minPhysicsNodes: 2
|
||||||
maxPhysicsNodes: 120
|
maxPhysicsNodes: 120
|
||||||
gravityStrength: 2
|
gravityStrength: 6
|
||||||
velocityDampen: 0.95
|
velocityDampen: 0.95
|
||||||
stiffness: 0.8
|
stiffness: 0.8
|
||||||
iterations: 20
|
iterations: 20
|
||||||
@@ -675,22 +674,21 @@ MonoBehaviour:
|
|||||||
lengthChangeVelocityKill: 0.6
|
lengthChangeVelocityKill: 0.6
|
||||||
minSlack: 0.002
|
minSlack: 0.002
|
||||||
headMinLen: 0.01
|
headMinLen: 0.01
|
||||||
collisionMask:
|
nodeHysteresis: 0.05
|
||||||
serializedVersion: 2
|
|
||||||
m_Bits: 0
|
|
||||||
constrainToGround: 1
|
constrainToGround: 1
|
||||||
groundMask:
|
groundMask:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_Bits: 4513599
|
m_Bits: 8
|
||||||
groundRadius: 0.01
|
groundRadius: 0.01
|
||||||
groundCastHeight: 1
|
groundCastHeight: 1
|
||||||
groundCastDistance: 2.5
|
groundCastDistance: 2.5
|
||||||
constrainToWater: 0
|
renderSubdivisionsIdle: 6
|
||||||
waterHeight: 0
|
renderSubdivisionsMoving: 2
|
||||||
waterRadius: 0.01
|
movingSpeedThreshold: 2
|
||||||
renderSubdivisions: 6
|
|
||||||
smooth: 1
|
smooth: 1
|
||||||
lineWidth: 0.005
|
lineWidth: 0.001
|
||||||
|
airDrag: 0.9
|
||||||
|
airDragXZ: 0.6
|
||||||
--- !u!120 &484878994603287356
|
--- !u!120 &484878994603287356
|
||||||
LineRenderer:
|
LineRenderer:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using NBF;
|
using System;
|
||||||
|
using NBF;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
[RequireComponent(typeof(LineRenderer))]
|
[RequireComponent(typeof(LineRenderer))]
|
||||||
@@ -12,16 +13,16 @@ public class Rope : MonoBehaviour
|
|||||||
|
|
||||||
[SerializeField, Range(2, 200)] private int minPhysicsNodes = 12;
|
[SerializeField, Range(2, 200)] private int minPhysicsNodes = 12;
|
||||||
|
|
||||||
[SerializeField, Range(2, 400)] [Tooltip("物理节点上限(仅用于性能保护;与“最大长度不限制”不是一回事)")]
|
[SerializeField, Range(2, 400), Tooltip("物理节点上限(仅用于性能保护;与“最大长度不限制”不是一回事)")]
|
||||||
private int maxPhysicsNodes = 120;
|
private int maxPhysicsNodes = 120;
|
||||||
|
|
||||||
[SerializeField] private float gravityStrength = 2.0f;
|
[SerializeField] private float gravityStrength = 2.0f;
|
||||||
[SerializeField, Range(0f, 1f)] private float velocityDampen = 0.95f;
|
[SerializeField, Range(0f, 1f)] private float velocityDampen = 0.95f;
|
||||||
|
|
||||||
[SerializeField, Range(0.0f, 1.0f)] [Tooltip("约束修正强度,越大越硬。0.6~0.9 常用")]
|
[SerializeField, Range(0.0f, 1.0f), Tooltip("约束修正强度,越大越硬。0.6~0.9 常用")]
|
||||||
private float stiffness = 0.8f;
|
private float stiffness = 0.8f;
|
||||||
|
|
||||||
[SerializeField, Range(1, 80)] [Tooltip("迭代次数。鱼线 10~30 通常够用")]
|
[SerializeField, Range(1, 80), Tooltip("迭代次数。鱼线 10~30 通常够用")]
|
||||||
private int iterations = 20;
|
private int iterations = 20;
|
||||||
|
|
||||||
[Header("Length Control (No Min/Max Clamp)")]
|
[Header("Length Control (No Min/Max Clamp)")]
|
||||||
@@ -32,7 +33,7 @@ public class Rope : MonoBehaviour
|
|||||||
[Tooltip("长度变化平滑时间(越小越跟手,越大越稳)")] [SerializeField, Min(0.0001f)]
|
[Tooltip("长度变化平滑时间(越小越跟手,越大越稳)")] [SerializeField, Min(0.0001f)]
|
||||||
private float lengthSmoothTime = 0.15f;
|
private float lengthSmoothTime = 0.15f;
|
||||||
|
|
||||||
[Tooltip("当长度在变化时,额外把速度压掉一些(防抖)。0=不额外处理,1=变化时几乎清速度")] [SerializeField, Range(0f, 1f)]
|
[Tooltip("当长度在变化时,额外把速度压掉一些(防抖)。0=不额外处理,1=变化时几乎清速度(建议只在收线生效)")] [SerializeField, Range(0f, 1f)]
|
||||||
private float lengthChangeVelocityKill = 0.6f;
|
private float lengthChangeVelocityKill = 0.6f;
|
||||||
|
|
||||||
[Tooltip("允许的最小松弛余量(避免目标长度刚好等于锚点距离时抖动)")] [SerializeField, Min(0f)]
|
[Tooltip("允许的最小松弛余量(避免目标长度刚好等于锚点距离时抖动)")] [SerializeField, Min(0f)]
|
||||||
@@ -41,9 +42,8 @@ public class Rope : MonoBehaviour
|
|||||||
[Header("Head Segment Clamp")] [Tooltip("第一段(起点->第1节点)允许的最小长度,避免收线时第一段被压到0导致数值炸")] [SerializeField, Min(0.0001f)]
|
[Header("Head Segment Clamp")] [Tooltip("第一段(起点->第1节点)允许的最小长度,避免收线时第一段被压到0导致数值炸")] [SerializeField, Min(0.0001f)]
|
||||||
private float headMinLen = 0.01f;
|
private float headMinLen = 0.01f;
|
||||||
|
|
||||||
|
[Header("Node Count Stability")] [SerializeField, Tooltip("节点数切换迟滞(米)。避免长度在临界点抖动导致节点数来回跳 -> 卡顿")]
|
||||||
[Header("Collision Filter")] [SerializeField, Tooltip("只对这些Layer进行物理检测(Raycast/SphereCast等)。不在这里的层完全不检测。")]
|
private float nodeHysteresis = 0.05f;
|
||||||
private LayerMask collisionMask = ~0;
|
|
||||||
|
|
||||||
[Header("Simple Ground/Water Constraint (Cheap)")] [SerializeField]
|
[Header("Simple Ground/Water Constraint (Cheap)")] [SerializeField]
|
||||||
private bool constrainToGround = true;
|
private bool constrainToGround = true;
|
||||||
@@ -53,87 +53,67 @@ public class Rope : MonoBehaviour
|
|||||||
[SerializeField, Min(0f)] private float groundCastHeight = 1.0f;
|
[SerializeField, Min(0f)] private float groundCastHeight = 1.0f;
|
||||||
[SerializeField, Min(0.01f)] private float groundCastDistance = 2.5f;
|
[SerializeField, Min(0.01f)] private float groundCastDistance = 2.5f;
|
||||||
|
|
||||||
[SerializeField] private bool constrainToWater = false;
|
[Header("Render (High Resolution)")] [SerializeField, Min(1), Tooltip("静止时每段物理线段插值加密数量(越大越顺,越耗)")]
|
||||||
[SerializeField] private float waterHeight = 0f;
|
private int renderSubdivisionsIdle = 6;
|
||||||
[SerializeField, Min(0f)] private float waterRadius = 0.01f;
|
|
||||||
|
|
||||||
[Header("Render (High Resolution)")] [SerializeField, Min(1), Tooltip("每段物理线段插值加密的数量(越大越顺,越耗)")]
|
[SerializeField, Min(1), Tooltip("甩动时每段物理线段插值加密数量(动态降LOD以防卡顿)")]
|
||||||
private int renderSubdivisions = 6;
|
private int renderSubdivisionsMoving = 2;
|
||||||
|
|
||||||
|
[SerializeField, Min(0f), Tooltip("平均速度超过该阈值认为在甩动(用于动态降 subdiv)")]
|
||||||
[Header("Air / Wind (For Fishing Line Feel)")]
|
private float movingSpeedThreshold = 2.0f;
|
||||||
[SerializeField, Range(0f, 5f), Tooltip("空气线性阻力(越大越不飘,空中更自然)")]
|
|
||||||
private float airDrag = 0.9f;
|
|
||||||
|
|
||||||
[SerializeField, Range(0f, 2f), Tooltip("横向额外阻力(减少左右飘得太夸张)")]
|
[SerializeField, Tooltip("是否使用 Catmull-Rom 平滑(开启更顺,但更耗)")]
|
||||||
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;
|
private bool smooth = true;
|
||||||
|
|
||||||
[SerializeField, Min(0.0001f)] private float lineWidth = 0.002f;
|
[SerializeField, Min(0.0001f)] private float lineWidth = 0.002f;
|
||||||
|
|
||||||
private LineRenderer lr;
|
[Header("Air Drag (Stable)")] [SerializeField, Range(0f, 5f), Tooltip("空气阻力(Y向),指数衰减,越大越不飘")]
|
||||||
|
private float airDrag = 0.9f;
|
||||||
|
|
||||||
// physics
|
[SerializeField, Range(0f, 2f), Tooltip("横向额外阻力(XZ),指数衰减,越大越不左右飘")]
|
||||||
private int physicsNodes;
|
private float airDragXZ = 0.6f;
|
||||||
private Vector3[] pCurr;
|
|
||||||
private Vector3[] pPrev;
|
private LineRenderer _lineRenderer;
|
||||||
|
|
||||||
|
// physics (注意:数组固定为 maxPhysicsNodes,physicsNodes 表示有效长度)
|
||||||
|
private int _physicsNodes;
|
||||||
|
private Vector3[] _pCurr;
|
||||||
|
private Vector3[] _pPrev;
|
||||||
|
|
||||||
// render
|
// render
|
||||||
private Vector3[] rPoints;
|
private Vector3[] _rPoints;
|
||||||
private int rCountCached = -1;
|
private int _rCountCached = -1;
|
||||||
|
|
||||||
private Vector3 gravity;
|
private Vector3 _gravity;
|
||||||
|
|
||||||
// length control runtime
|
// length control runtime
|
||||||
private float targetLength;
|
private float _targetLength;
|
||||||
private float currentLength;
|
private float _currentLength;
|
||||||
private float lengthSmoothVel;
|
private float _lengthSmoothVel;
|
||||||
|
|
||||||
// Only-head-change trick:
|
// rest length head
|
||||||
// Total rest length = headRestLen + (physicsNodes - 2) * physicsSegmentLen
|
private float _headRestLen;
|
||||||
private float headRestLen;
|
|
||||||
|
|
||||||
|
// node stability
|
||||||
|
private int _lastDesiredNodes = 0;
|
||||||
|
|
||||||
|
private FRod _rod;
|
||||||
|
public void Init(FRod rod) => _rod = rod;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
lr = GetComponent<LineRenderer>();
|
_lineRenderer = GetComponent<LineRenderer>();
|
||||||
gravity = new Vector3(0f, -gravityStrength, 0f);
|
_gravity = new Vector3(0f, -gravityStrength, 0f);
|
||||||
|
|
||||||
InitLengthSystem();
|
InitLengthSystem();
|
||||||
AllocateAndInitNodes();
|
AllocateAndInitNodes();
|
||||||
RebuildRenderBufferIfNeeded();
|
RebuildRenderBufferIfNeeded(renderSubdivisionsIdle);
|
||||||
}
|
|
||||||
|
|
||||||
private FRod _rod;
|
|
||||||
|
|
||||||
public void Init(FRod rod)
|
|
||||||
{
|
|
||||||
_rod = rod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
renderSubdivisions = Mathf.Max(renderSubdivisions, 1);
|
renderSubdivisionsIdle = Mathf.Max(renderSubdivisionsIdle, 1);
|
||||||
|
renderSubdivisionsMoving = Mathf.Max(renderSubdivisionsMoving, 1);
|
||||||
iterations = Mathf.Clamp(iterations, 1, 80);
|
iterations = Mathf.Clamp(iterations, 1, 80);
|
||||||
groundCastDistance = Mathf.Max(groundCastDistance, 0.01f);
|
groundCastDistance = Mathf.Max(groundCastDistance, 0.01f);
|
||||||
groundCastHeight = Mathf.Max(groundCastHeight, 0f);
|
groundCastHeight = Mathf.Max(groundCastHeight, 0f);
|
||||||
@@ -146,35 +126,33 @@ public class Rope : MonoBehaviour
|
|||||||
maxPhysicsNodes = Mathf.Max(maxPhysicsNodes, minPhysicsNodes);
|
maxPhysicsNodes = Mathf.Max(maxPhysicsNodes, minPhysicsNodes);
|
||||||
|
|
||||||
headMinLen = Mathf.Max(headMinLen, 0.0001f);
|
headMinLen = Mathf.Max(headMinLen, 0.0001f);
|
||||||
|
nodeHysteresis = Mathf.Max(0f, nodeHysteresis);
|
||||||
// 如果你希望只用一个mask控制,避免 groundMask 忘了配
|
|
||||||
if (groundMask == ~0)
|
|
||||||
groundMask = collisionMask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitLengthSystem()
|
private void InitLengthSystem()
|
||||||
{
|
{
|
||||||
// 没有 min/max 长度限制:初始长度只做一个“非负”保障
|
|
||||||
float defaultLen = physicsSegmentLen * (Mathf.Max(minPhysicsNodes, 2) - 1);
|
float defaultLen = physicsSegmentLen * (Mathf.Max(minPhysicsNodes, 2) - 1);
|
||||||
currentLength = (initialLength > 0f) ? initialLength : defaultLen;
|
_currentLength = (initialLength > 0f) ? initialLength : defaultLen;
|
||||||
targetLength = currentLength;
|
_targetLength = _currentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AllocateAndInitNodes()
|
private void AllocateAndInitNodes()
|
||||||
{
|
{
|
||||||
physicsNodes = Mathf.Clamp(ComputeDesiredNodes(currentLength), 2, maxPhysicsNodes);
|
_physicsNodes = Mathf.Clamp(ComputeDesiredNodesStable(_currentLength), 2, maxPhysicsNodes);
|
||||||
pCurr = new Vector3[physicsNodes];
|
|
||||||
pPrev = new Vector3[physicsNodes];
|
// ✅ 永久分配到最大,运行时不再 new,避免GC卡顿
|
||||||
|
_pCurr = new Vector3[maxPhysicsNodes];
|
||||||
|
_pPrev = new Vector3[maxPhysicsNodes];
|
||||||
|
|
||||||
// 初始从起点往下排
|
|
||||||
Vector3 start = startAnchor ? startAnchor.position : transform.position;
|
Vector3 start = startAnchor ? startAnchor.position : transform.position;
|
||||||
Vector3 dir = Vector3.down;
|
Vector3 dir = Vector3.down;
|
||||||
|
|
||||||
for (int i = 0; i < physicsNodes; i++)
|
for (int i = 0; i < _physicsNodes; i++)
|
||||||
{
|
{
|
||||||
Vector3 pos = start + dir * (physicsSegmentLen * i);
|
Vector3 pos = start + dir * (physicsSegmentLen * i);
|
||||||
pCurr[i] = pos;
|
_pCurr[i] = pos;
|
||||||
pPrev[i] = pos;
|
_pPrev[i] = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateHeadRestLenFromCurrentLength();
|
UpdateHeadRestLenFromCurrentLength();
|
||||||
@@ -185,190 +163,195 @@ public class Rope : MonoBehaviour
|
|||||||
|
|
||||||
private int ComputeDesiredNodes(float lengthMeters)
|
private int ComputeDesiredNodes(float lengthMeters)
|
||||||
{
|
{
|
||||||
// nodes = floor(length/segLen)+1
|
|
||||||
int desired = Mathf.FloorToInt(Mathf.Max(0f, lengthMeters) / physicsSegmentLen) + 1;
|
int desired = Mathf.FloorToInt(Mathf.Max(0f, lengthMeters) / physicsSegmentLen) + 1;
|
||||||
desired = Mathf.Clamp(desired, minPhysicsNodes, maxPhysicsNodes);
|
desired = Mathf.Clamp(desired, minPhysicsNodes, maxPhysicsNodes);
|
||||||
return desired;
|
return desired;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>设置目标总长度(米)。不做最小/最大长度限制(最小可行由锚点距离决定)。</summary>
|
private int ComputeDesiredNodesStable(float lengthMeters)
|
||||||
public void SetTargetLength(float lengthMeters)
|
|
||||||
{
|
{
|
||||||
targetLength = Mathf.Max(0f, lengthMeters);
|
int desired = ComputeDesiredNodes(lengthMeters);
|
||||||
}
|
|
||||||
|
|
||||||
|
if (_lastDesiredNodes == 0)
|
||||||
public float GetCurrentLength() => currentLength;
|
|
||||||
public float GetTargetLength() => targetLength;
|
|
||||||
|
|
||||||
public float GetAnchorDistance()
|
|
||||||
{
|
|
||||||
if (startAnchor != null && endAnchor != null)
|
|
||||||
{
|
{
|
||||||
return Vector3.Distance(startAnchor.position, endAnchor.position);
|
_lastDesiredNodes = desired;
|
||||||
|
return desired;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
if (desired == _lastDesiredNodes)
|
||||||
|
return desired;
|
||||||
|
|
||||||
|
// 边界(lastDesiredNodes 对应的长度临界)
|
||||||
|
float boundary = (_lastDesiredNodes - 1) * physicsSegmentLen;
|
||||||
|
|
||||||
|
// 在临界附近就不切换,避免来回跳
|
||||||
|
if (Mathf.Abs(lengthMeters - boundary) < nodeHysteresis)
|
||||||
|
return _lastDesiredNodes;
|
||||||
|
|
||||||
|
_lastDesiredNodes = desired;
|
||||||
|
return desired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetTargetLength(float lengthMeters)
|
||||||
|
{
|
||||||
|
_targetLength = Mathf.Max(0f, lengthMeters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetCurrentLength() => _currentLength;
|
||||||
|
public float GetTargetLength() => _targetLength;
|
||||||
|
|
||||||
private void FixedUpdate()
|
private void FixedUpdate()
|
||||||
{
|
{
|
||||||
if (!startAnchor || !endAnchor)
|
if (!startAnchor || !endAnchor) return;
|
||||||
return;
|
|
||||||
|
|
||||||
gravity.y = -gravityStrength;
|
_gravity.y = -gravityStrength;
|
||||||
|
|
||||||
UpdateLengthSmooth(); // 只保证 >= 锚点直线距离 + minSlack
|
UpdateLengthSmooth();
|
||||||
UpdateNodesFromLength(); // 只从头部增/减节点
|
UpdateNodesFromLength(); // ✅ 一次性增减,无GC
|
||||||
UpdateHeadRestLenFromCurrentLength(); // 第一段补余量 => 变化集中在头部
|
UpdateHeadRestLenFromCurrentLength();
|
||||||
|
|
||||||
|
// simulate
|
||||||
Simulate();
|
Simulate();
|
||||||
|
|
||||||
|
// 确保端点正确后再迭代
|
||||||
|
LockAnchorsHard();
|
||||||
|
|
||||||
|
// constraints
|
||||||
for (int it = 0; it < iterations; it++)
|
for (int it = 0; it < iterations; it++)
|
||||||
{
|
|
||||||
SolveDistanceConstraints_HeadOnly();
|
SolveDistanceConstraints_HeadOnly();
|
||||||
SolveBendConstraint();
|
|
||||||
LockAnchorsHard();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (constrainToWater || constrainToGround)
|
// 迭代后锁一次足够
|
||||||
ConstrainToGroundAndWater();
|
LockAnchorsHard();
|
||||||
|
|
||||||
|
if (constrainToGround)
|
||||||
|
ConstrainToGround();
|
||||||
|
|
||||||
|
// 约束后再锁一次(保险)
|
||||||
LockAnchorsHard();
|
LockAnchorsHard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void LateUpdate()
|
private void LateUpdate()
|
||||||
{
|
{
|
||||||
if (!startAnchor || !endAnchor || pCurr == null || physicsNodes < 2) return;
|
if (!startAnchor || !endAnchor || _pCurr == null || _physicsNodes < 2) return;
|
||||||
|
|
||||||
int last = physicsNodes - 1;
|
// 端点“跟手”:渲染前强行同步 transform,消除慢一拍
|
||||||
|
int last = _physicsNodes - 1;
|
||||||
Vector3 s = startAnchor.transform.position;
|
Vector3 s = startAnchor.transform.position;
|
||||||
Vector3 e = endAnchor.transform.position;
|
Vector3 e = endAnchor.transform.position;
|
||||||
|
|
||||||
pCurr[0] = s; pPrev[0] = s; // ✅ 关键:同步 pPrev
|
_pCurr[0] = s;
|
||||||
pCurr[last] = e; pPrev[last] = e; // ✅ 关键:同步 pPrev
|
_pPrev[0] = s;
|
||||||
|
_pCurr[last] = e;
|
||||||
|
_pPrev[last] = e;
|
||||||
|
|
||||||
DrawHighResLine();
|
DrawHighResLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateLengthSmooth()
|
private void UpdateLengthSmooth()
|
||||||
{
|
{
|
||||||
// float anchorDist = Vector3.Distance(startAnchor.position, endAnchor.position);
|
|
||||||
// float minFeasible = anchorDist + minSlack;
|
|
||||||
float minFeasible = 0.01f;
|
float minFeasible = 0.01f;
|
||||||
|
|
||||||
// ✅ 最小长度 = 起点终点直线距离(+slack),最大不限制
|
float desired = Mathf.Max(_targetLength, minFeasible);
|
||||||
float desired = Mathf.Max(targetLength, minFeasible);
|
|
||||||
|
|
||||||
float prevLen = currentLength;
|
_currentLength = Mathf.SmoothDamp(
|
||||||
|
_currentLength,
|
||||||
currentLength = Mathf.SmoothDamp(
|
|
||||||
currentLength,
|
|
||||||
desired,
|
desired,
|
||||||
ref lengthSmoothVel,
|
ref _lengthSmoothVel,
|
||||||
lengthSmoothTime,
|
lengthSmoothTime,
|
||||||
Mathf.Infinity,
|
Mathf.Infinity,
|
||||||
Time.fixedDeltaTime
|
Time.fixedDeltaTime
|
||||||
);
|
);
|
||||||
|
|
||||||
float lenDelta = Mathf.Abs(currentLength - prevLen);
|
|
||||||
if (lenDelta > 1e-5f && lengthChangeVelocityKill > 0f && pPrev != null)
|
|
||||||
{
|
|
||||||
float kill = Mathf.Clamp01(lengthChangeVelocityKill);
|
|
||||||
for (int i = 1; i < physicsNodes - 1; i++)
|
|
||||||
pPrev[i] = Vector3.Lerp(pPrev[i], pCurr[i], kill);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateNodesFromLength()
|
private void UpdateNodesFromLength()
|
||||||
{
|
{
|
||||||
int desired = ComputeDesiredNodes(currentLength);
|
int desired = ComputeDesiredNodesStable(_currentLength);
|
||||||
if (desired == physicsNodes) return;
|
desired = Mathf.Clamp(desired, 2, maxPhysicsNodes);
|
||||||
|
if (desired == _physicsNodes) return;
|
||||||
|
|
||||||
while (physicsNodes < desired)
|
if (desired > _physicsNodes)
|
||||||
AddNodeAtStart();
|
|
||||||
|
|
||||||
while (physicsNodes > desired)
|
|
||||||
RemoveNodeAtStart();
|
|
||||||
|
|
||||||
RebuildRenderBufferIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddNodeAtStart()
|
|
||||||
{
|
|
||||||
int newCount = Mathf.Min(physicsNodes + 1, maxPhysicsNodes);
|
|
||||||
if (newCount == physicsNodes) return;
|
|
||||||
|
|
||||||
Vector3[] newCurr = new Vector3[newCount];
|
|
||||||
Vector3[] newPrev = new Vector3[newCount];
|
|
||||||
|
|
||||||
newCurr[0] = pCurr[0];
|
|
||||||
newPrev[0] = pPrev[0];
|
|
||||||
|
|
||||||
for (int i = 2; i < newCount; i++)
|
|
||||||
{
|
{
|
||||||
newCurr[i] = pCurr[i - 1];
|
AddNodesAtStart(desired - _physicsNodes);
|
||||||
newPrev[i] = pPrev[i - 1];
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemoveNodesAtStart(_physicsNodes - desired);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 s = startAnchor.position;
|
_physicsNodes = desired;
|
||||||
Vector3 dir = Vector3.down;
|
|
||||||
|
|
||||||
if (physicsNodes >= 2)
|
// 渲染缓存按最大 subdiv 预留即可
|
||||||
|
RebuildRenderBufferIfNeeded(renderSubdivisionsIdle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddNodesAtStart(int addCount)
|
||||||
|
{
|
||||||
|
if (addCount <= 0) return;
|
||||||
|
|
||||||
|
int oldCount = _physicsNodes;
|
||||||
|
int newCount = Mathf.Min(oldCount + addCount, maxPhysicsNodes);
|
||||||
|
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 dir = Vector3.down;
|
||||||
|
int firstOld = 1 + addCount;
|
||||||
|
if (firstOld < 1 + addCount + (oldCount - 2) && oldCount >= 2)
|
||||||
{
|
{
|
||||||
Vector3 toOld1 = (pCurr[1] - s);
|
Vector3 toOld1 = (_pCurr[firstOld] - s);
|
||||||
if (toOld1.sqrMagnitude > 1e-6f) dir = toOld1.normalized;
|
if (toOld1.sqrMagnitude > 1e-6f) dir = toOld1.normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 pos = s + dir * physicsSegmentLen;
|
// 继承速度
|
||||||
newCurr[1] = pos;
|
float dt = Time.fixedDeltaTime;
|
||||||
newPrev[1] = pos;
|
Vector3 inheritVel = Vector3.zero;
|
||||||
|
if (oldCount >= 2 && firstOld < maxPhysicsNodes)
|
||||||
|
{
|
||||||
|
inheritVel = (_pCurr[firstOld] - _pPrev[firstOld]) / Mathf.Max(dt, 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
pCurr = newCurr;
|
for (int k = 1; k <= addCount; k++)
|
||||||
pPrev = newPrev;
|
{
|
||||||
physicsNodes = newCount;
|
Vector3 pos = s + dir * (physicsSegmentLen * k);
|
||||||
|
_pCurr[k] = pos;
|
||||||
|
_pPrev[k] = pos - inheritVel * dt;
|
||||||
|
}
|
||||||
|
|
||||||
LockAnchorsHard();
|
LockAnchorsHard();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveNodeAtStart()
|
private void RemoveNodesAtStart(int removeCount)
|
||||||
{
|
{
|
||||||
int newCount = Mathf.Max(physicsNodes - 1, 2);
|
if (removeCount <= 0) return;
|
||||||
if (newCount == physicsNodes) return;
|
|
||||||
|
|
||||||
Vector3[] newCurr = new Vector3[newCount];
|
int oldCount = _physicsNodes;
|
||||||
Vector3[] newPrev = new Vector3[newCount];
|
int newCount = Mathf.Max(oldCount - removeCount, 2);
|
||||||
|
removeCount = oldCount - newCount;
|
||||||
|
if (removeCount <= 0) return;
|
||||||
|
|
||||||
newCurr[0] = pCurr[0];
|
// 把 [1+removeCount .. 1+removeCount+(newCount-2)-1] 挪到 [1..newCount-2]
|
||||||
newPrev[0] = pPrev[0];
|
// 中间动态节点数量 = newCount-2
|
||||||
|
Array.Copy(_pCurr, 1 + removeCount, _pCurr, 1, newCount - 2);
|
||||||
for (int i = 1; i < newCount - 1; i++)
|
Array.Copy(_pPrev, 1 + removeCount, _pPrev, 1, newCount - 2);
|
||||||
{
|
|
||||||
newCurr[i] = pCurr[i + 1];
|
|
||||||
newPrev[i] = pPrev[i + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
newCurr[newCount - 1] = pCurr[physicsNodes - 1];
|
|
||||||
newPrev[newCount - 1] = pPrev[physicsNodes - 1];
|
|
||||||
|
|
||||||
pCurr = newCurr;
|
|
||||||
pPrev = newPrev;
|
|
||||||
physicsNodes = newCount;
|
|
||||||
|
|
||||||
LockAnchorsHard();
|
LockAnchorsHard();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateHeadRestLenFromCurrentLength()
|
private void UpdateHeadRestLenFromCurrentLength()
|
||||||
{
|
{
|
||||||
int fixedSegCount = Mathf.Max(0, physicsNodes - 2);
|
int fixedSegCount = Mathf.Max(0, _physicsNodes - 2);
|
||||||
float baseLen = fixedSegCount * physicsSegmentLen;
|
float baseLen = fixedSegCount * physicsSegmentLen;
|
||||||
|
|
||||||
headRestLen = currentLength - baseLen;
|
_headRestLen = _currentLength - baseLen;
|
||||||
|
_headRestLen = Mathf.Clamp(_headRestLen, headMinLen, physicsSegmentLen * 1.5f);
|
||||||
// 第一段允许在一个合理范围内变动(太长会像橡皮筋,太短会炸)
|
|
||||||
headRestLen = Mathf.Clamp(headRestLen, headMinLen, physicsSegmentLen * 1.5f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Simulate()
|
private void Simulate()
|
||||||
@@ -376,91 +359,51 @@ public class Rope : MonoBehaviour
|
|||||||
float dt = Time.fixedDeltaTime;
|
float dt = Time.fixedDeltaTime;
|
||||||
float invDt = 1f / Mathf.Max(dt, 1e-6f);
|
float invDt = 1f / Mathf.Max(dt, 1e-6f);
|
||||||
|
|
||||||
// 风方向归一化(避免填了0向量导致NaN)
|
// 指数衰减:更稳定好调
|
||||||
Vector3 wDir = windDir;
|
float kY = Mathf.Exp(-airDrag * dt);
|
||||||
if (wDir.sqrMagnitude < 1e-6f) wDir = Vector3.right;
|
float kXZ = Mathf.Exp(-airDragXZ * dt);
|
||||||
wDir.Normalize();
|
|
||||||
|
|
||||||
for (int i = 0; i < physicsNodes; i++)
|
for (int i = 1; i < _physicsNodes - 1; i++)
|
||||||
{
|
{
|
||||||
// Verlet 速度(由当前位置和上一帧位置推出来)
|
Vector3 vel = (_pCurr[i] - _pPrev[i]) * invDt;
|
||||||
Vector3 vel = (pCurr[i] - pPrev[i]) * invDt;
|
|
||||||
|
|
||||||
// 先做“惯性推进”
|
vel.x *= kXZ;
|
||||||
Vector3 next = pCurr[i] + (pCurr[i] - pPrev[i]) * velocityDampen;
|
vel.z *= kXZ;
|
||||||
|
vel.y *= kY;
|
||||||
|
|
||||||
// 加速度 = 重力 + 空气阻力 + 风(相对速度)
|
vel *= velocityDampen;
|
||||||
Vector3 acc = gravity;
|
|
||||||
|
|
||||||
// --- 空气阻力(与速度成正比)---
|
Vector3 next = _pCurr[i] + vel * dt;
|
||||||
// drag = -vel * airDrag,并且横向更强一点
|
|
||||||
Vector3 drag = -vel * airDrag;
|
|
||||||
drag.x *= (1f + airDragXZ);
|
|
||||||
drag.z *= (1f + airDragXZ);
|
|
||||||
acc += drag;
|
|
||||||
|
|
||||||
// --- 风(让线在空中不那么“只会垂直掉”)---
|
_pPrev[i] = _pCurr[i];
|
||||||
if (i != 0 && i != physicsNodes - 1 && windStrength > 0f)
|
_pCurr[i] = next + _gravity * (dt * dt);
|
||||||
{
|
|
||||||
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()
|
private void LockAnchorsHard()
|
||||||
{
|
{
|
||||||
if (!startAnchor || !endAnchor || pCurr == null || pPrev == null || physicsNodes < 2) return;
|
if (!startAnchor || !endAnchor || _pCurr == null || _pPrev == null || _physicsNodes < 2) return;
|
||||||
|
|
||||||
float dt = Time.fixedDeltaTime;
|
float dt = Time.fixedDeltaTime;
|
||||||
Vector3 s = startAnchor.position;
|
Vector3 s = startAnchor.position;
|
||||||
Vector3 e = endAnchor.position;
|
Vector3 e = endAnchor.position;
|
||||||
|
|
||||||
pCurr[0] = s;
|
_pCurr[0] = s;
|
||||||
pPrev[0] = s - startAnchor.linearVelocity * dt;
|
_pPrev[0] = s - startAnchor.linearVelocity * dt;
|
||||||
|
|
||||||
int last = physicsNodes - 1;
|
int last = _physicsNodes - 1;
|
||||||
pCurr[last] = e;
|
_pCurr[last] = e;
|
||||||
pPrev[last] = e - endAnchor.linearVelocity * dt;
|
_pPrev[last] = e - endAnchor.linearVelocity * dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SolveDistanceConstraints_HeadOnly()
|
private void SolveDistanceConstraints_HeadOnly()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < physicsNodes - 1; i++)
|
for (int i = 0; i < _physicsNodes - 1; i++)
|
||||||
{
|
{
|
||||||
float rest = (i == 0) ? headRestLen : physicsSegmentLen;
|
float rest = (i == 0) ? _headRestLen : physicsSegmentLen;
|
||||||
|
|
||||||
Vector3 a = pCurr[i];
|
Vector3 a = _pCurr[i];
|
||||||
Vector3 b = pCurr[i + 1];
|
Vector3 b = _pCurr[i + 1];
|
||||||
|
|
||||||
Vector3 delta = b - a;
|
Vector3 delta = b - a;
|
||||||
float dist = delta.magnitude;
|
float dist = delta.magnitude;
|
||||||
@@ -470,60 +413,23 @@ public class Rope : MonoBehaviour
|
|||||||
Vector3 corr = delta * diff * stiffness;
|
Vector3 corr = delta * diff * stiffness;
|
||||||
|
|
||||||
if (i != 0)
|
if (i != 0)
|
||||||
pCurr[i] += corr * 0.5f;
|
_pCurr[i] += corr * 0.5f;
|
||||||
|
|
||||||
if (i + 1 != physicsNodes - 1)
|
if (i + 1 != _physicsNodes - 1)
|
||||||
pCurr[i + 1] -= corr * 0.5f;
|
_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()
|
private void ConstrainToGround()
|
||||||
{
|
{
|
||||||
int groundLayerMask = collisionMask & groundMask; // ✅ 统一过滤:只检测指定层
|
for (int i = 1; i < _physicsNodes - 1; i++)
|
||||||
|
|
||||||
for (int i = 1; i < physicsNodes - 1; i++)
|
|
||||||
{
|
{
|
||||||
Vector3 p = pCurr[i];
|
Vector3 p = _pCurr[i];
|
||||||
|
|
||||||
if (constrainToWater)
|
if (constrainToGround && groundMask != 0)
|
||||||
{
|
|
||||||
float minY = waterHeight + waterRadius;
|
|
||||||
if (p.y < minY) p.y = minY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (constrainToGround && groundLayerMask != 0)
|
|
||||||
{
|
{
|
||||||
Vector3 origin = p + Vector3.up * groundCastHeight;
|
Vector3 origin = p + Vector3.up * groundCastHeight;
|
||||||
if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, groundCastDistance, groundLayerMask,
|
if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, groundCastDistance, groundMask,
|
||||||
QueryTriggerInteraction.Ignore))
|
QueryTriggerInteraction.Ignore))
|
||||||
{
|
{
|
||||||
float minY = hit.point.y + groundRadius;
|
float minY = hit.point.y + groundRadius;
|
||||||
@@ -531,64 +437,79 @@ public class Rope : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pCurr[i] = p;
|
_pCurr[i] = p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawHighResLine()
|
private void DrawHighResLine()
|
||||||
{
|
{
|
||||||
if (pCurr == null || physicsNodes < 2) return;
|
if (_pCurr == null || _physicsNodes < 2) return;
|
||||||
|
|
||||||
RebuildRenderBufferIfNeeded();
|
_lineRenderer.startWidth = lineWidth;
|
||||||
|
_lineRenderer.endWidth = lineWidth;
|
||||||
lr.startWidth = lineWidth;
|
|
||||||
lr.endWidth = lineWidth;
|
|
||||||
|
|
||||||
if (!smooth)
|
if (!smooth)
|
||||||
{
|
{
|
||||||
lr.positionCount = physicsNodes;
|
_lineRenderer.positionCount = _physicsNodes;
|
||||||
lr.SetPositions(pCurr);
|
// 只传有效段
|
||||||
|
// LineRenderer.SetPositions 会读数组的前 positionCount 个
|
||||||
|
_lineRenderer.SetPositions(_pCurr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 动态降 subdiv:甩动时降低点数,减少CPU开销
|
||||||
|
int subdiv = PickRenderSubdivisions();
|
||||||
|
RebuildRenderBufferIfNeeded(subdiv);
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
|
||||||
for (int seg = 0; seg < physicsNodes - 1; seg++)
|
for (int seg = 0; seg < _physicsNodes - 1; seg++)
|
||||||
{
|
{
|
||||||
Vector3 p0 = pCurr[Mathf.Max(seg - 1, 0)];
|
Vector3 p0 = _pCurr[Mathf.Max(seg - 1, 0)];
|
||||||
Vector3 p1 = pCurr[seg];
|
Vector3 p1 = _pCurr[seg];
|
||||||
Vector3 p2 = pCurr[seg + 1];
|
Vector3 p2 = _pCurr[seg + 1];
|
||||||
Vector3 p3 = pCurr[Mathf.Min(seg + 2, physicsNodes - 1)];
|
Vector3 p3 = _pCurr[Mathf.Min(seg + 2, _physicsNodes - 1)];
|
||||||
|
|
||||||
for (int s = 0; s < renderSubdivisions; s++)
|
for (int s = 0; s < subdiv; s++)
|
||||||
{
|
{
|
||||||
float t = s / (float)renderSubdivisions;
|
float t = s / (float)subdiv;
|
||||||
Vector3 pt = CatmullRom_XZ_LinearY(p0, p1, p2, p3, t);
|
Vector3 pt = CatmullRom_XZ_LinearY(p0, p1, p2, p3, t);
|
||||||
|
_rPoints[idx++] = pt;
|
||||||
// 如果水面约束开启:渲染点也夹一下,避免视觉上又穿回去
|
|
||||||
if (constrainToWater)
|
|
||||||
{
|
|
||||||
float minY = waterHeight + waterRadius;
|
|
||||||
if (pt.y < minY) pt.y = minY;
|
|
||||||
}
|
|
||||||
|
|
||||||
rPoints[idx++] = pt;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rPoints[idx++] = pCurr[physicsNodes - 1];
|
_rPoints[idx++] = _pCurr[_physicsNodes - 1];
|
||||||
|
|
||||||
lr.positionCount = idx;
|
_lineRenderer.positionCount = idx;
|
||||||
lr.SetPositions(rPoints);
|
_lineRenderer.SetPositions(_rPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RebuildRenderBufferIfNeeded()
|
private int PickRenderSubdivisions()
|
||||||
{
|
{
|
||||||
int targetCount = (physicsNodes - 1) * renderSubdivisions + 1;
|
int idle = Mathf.Max(1, renderSubdivisionsIdle);
|
||||||
if (rPoints == null || rCountCached != targetCount)
|
int moving = Mathf.Max(1, renderSubdivisionsMoving);
|
||||||
|
|
||||||
|
// 计算平均速度(用 Verlet 差分)
|
||||||
|
float dt = Time.fixedDeltaTime;
|
||||||
|
float invDt = 1f / Mathf.Max(dt, 1e-6f);
|
||||||
|
|
||||||
|
float sum = 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];
|
_rPoints = new Vector3[targetCount];
|
||||||
rCountCached = targetCount;
|
_rCountCached = targetCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,22 +526,18 @@ public class Rope : MonoBehaviour
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDrawGizmosSelected()
|
|
||||||
{
|
|
||||||
if (pCurr == null) return;
|
|
||||||
Gizmos.color = Color.yellow;
|
|
||||||
for (int i = 0; i < pCurr.Length; i++)
|
|
||||||
Gizmos.DrawSphere(pCurr[i], 0.01f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector3 CatmullRom_XZ_LinearY(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
|
private static Vector3 CatmullRom_XZ_LinearY(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
|
||||||
{
|
{
|
||||||
// XZ 做 Catmull-Rom
|
|
||||||
Vector3 cr = CatmullRom(p0, p1, p2, p3, t);
|
Vector3 cr = CatmullRom(p0, p1, p2, p3, t);
|
||||||
|
|
||||||
// Y 不做样条,改成线性(不会过冲)
|
|
||||||
cr.y = Mathf.Lerp(p1.y, p2.y, t);
|
cr.y = Mathf.Lerp(p1.y, p2.y, t);
|
||||||
|
|
||||||
return cr;
|
return cr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDrawGizmosSelected()
|
||||||
|
{
|
||||||
|
if (_pCurr == null) return;
|
||||||
|
Gizmos.color = Color.yellow;
|
||||||
|
for (int i = 0; i < _physicsNodes; i++)
|
||||||
|
Gizmos.DrawSphere(_pCurr[i], 0.01f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user