using UnityEngine; [RequireComponent(typeof(LineRenderer))] public class Rope : MonoBehaviour { [Header("Demo Parameters")] [SerializeField, Min(0)] float mouseOffset = 10f; [Header("Verlet Parameters")] [SerializeField] float nodeDistance = 0.35f; [SerializeField] float nodeColliderRadius = 0.2f; [SerializeField] float gravityStrength = 2; [SerializeField] float totalLength = 10f; [SerializeField, Range(0, 1)] float velocityDampen = 0.95f; [SerializeField, Range(0, 0.99f)] float stiffness = 0.8f; [SerializeField, Range(1, 10)] int iterateCollisionsEvery = 1; [SerializeField, Range(1, 200)] int iterations = 10; [SerializeField] int colliderBufferSize = 1; [Header("Line Renderer")] [SerializeField] float ropeWidth = 0.1f; // 私有变量 Camera cam; Vector3 gravity; Vector3 startLock; Vector3 endLock; bool isStartLocked = false; bool isEndLocked = false; // 数组和缓存 Vector3[] currentNodePositions; Vector3[] previousNodePositions; Collider[] colliderHitBuffer; LineRenderer lineRenderer; GameObject nodeTester; SphereCollider nodeCollider; int totalNodes; float lastTotalLength; // 用于检测长度变化 void Awake() { // 获取组件引用 lineRenderer = GetComponent(); cam = Camera.main; gravity = new Vector3(0, -gravityStrength, 0); // 初始化节点测试器 nodeTester = new GameObject("Node Tester"); nodeTester.layer = 8; nodeCollider = nodeTester.AddComponent(); nodeCollider.radius = nodeColliderRadius; // 初始化长度跟踪 lastTotalLength = totalLength; InitializeRope(); } void InitializeRope() { // 计算节点数量 totalNodes = Mathf.FloorToInt(totalLength / nodeDistance) + 1; float remainingLength = totalLength % nodeDistance; // 如果剩余长度大于0,增加一个节点 if (remainingLength > 0 && totalLength > nodeDistance) { totalNodes++; } // 初始化或调整数组大小 System.Array.Resize(ref currentNodePositions, totalNodes); System.Array.Resize(ref previousNodePositions, totalNodes); colliderHitBuffer = new Collider[colliderBufferSize]; // 初始化节点位置 Vector3 startPos = transform.position; for (int i = 0; i < totalNodes; i++) { // 如果是最后一个节点且有剩余长度,使用剩余长度 float distance = (i == totalNodes - 1 && remainingLength > 0) ? remainingLength : nodeDistance; // 如果数组已有数据,保持现有位置,否则初始化新位置 if (currentNodePositions[i] == null) { currentNodePositions[i] = startPos; previousNodePositions[i] = startPos; } startPos.y -= distance; } // 设置线渲染器 lineRenderer.startWidth = ropeWidth; lineRenderer.endWidth = ropeWidth; lineRenderer.positionCount = totalNodes; } void Update() { // 检查长度是否变化 if (!Mathf.Approximately(totalLength, lastTotalLength)) { AdjustRopeLength(); lastTotalLength = totalLength; } // 处理鼠标输入 if (Input.GetMouseButtonDown(0)) { if (!isStartLocked) { isStartLocked = true; startLock = GetMouseWorldPosition(); } else if (!isEndLocked) { isEndLocked = true; endLock = GetMouseWorldPosition(); } } else if (!isStartLocked) { startLock = GetMouseWorldPosition(); } else if (isStartLocked && !isEndLocked) { endLock = GetMouseWorldPosition(); } DrawRope(); } void AdjustRopeLength() { // 保存旧位置 Vector3[] oldPositions = (Vector3[])currentNodePositions.Clone(); Vector3[] oldPrevPositions = (Vector3[])previousNodePositions.Clone(); // 重新初始化绳索 InitializeRope(); // 尽可能保留旧位置数据 int copyLength = Mathf.Min(oldPositions.Length, currentNodePositions.Length); System.Array.Copy(oldPositions, currentNodePositions, copyLength); System.Array.Copy(oldPrevPositions, previousNodePositions, copyLength); // 如果长度增加,初始化新增节点的位置 if (currentNodePositions.Length > oldPositions.Length) { Vector3 lastPos = oldPositions[oldPositions.Length - 1]; for (int i = oldPositions.Length; i < currentNodePositions.Length; i++) { float distance = (i == currentNodePositions.Length - 1 && (totalLength % nodeDistance) > 0) ? (totalLength % nodeDistance) : nodeDistance; lastPos.y -= distance; currentNodePositions[i] = lastPos; previousNodePositions[i] = lastPos; } } } void FixedUpdate() { Simulate(); for (int i = 0; i < iterations; i++) { ApplyConstraint(); // 减少碰撞检测频率 if (i % (iterateCollisionsEvery + 1) == 0) { AdjustCollisions(); } } } Vector3 GetMouseWorldPosition() { return cam.ScreenToWorldPoint(Input.mousePosition + new Vector3(0, 0, mouseOffset)); } void Simulate() { float fixedDt = Time.fixedDeltaTime; for (int i = 0; i < totalNodes; i++) { // 计算并应用速度 Vector3 velocity = (currentNodePositions[i] - previousNodePositions[i]) * velocityDampen; previousNodePositions[i] = currentNodePositions[i]; currentNodePositions[i] += velocity + gravity * fixedDt; } } void ApplyConstraint() { // 锁定端点 currentNodePositions[0] = startLock; if (isStartLocked && isEndLocked) { currentNodePositions[totalNodes - 1] = endLock; } // 预计算所有常用值 float halfStiffness = 0.5f * stiffness; int nodeCountMinusOne = totalNodes - 1; for (int i = 0; i < nodeCountMinusOne; i++) { Vector3 node1 = currentNodePositions[i]; Vector3 node2 = currentNodePositions[i + 1]; Vector3 diff = node1 - node2; // 计算期望的距离 - 如果是最后一个段且有剩余长度,使用剩余长度 float desiredDistance = (i == nodeCountMinusOne - 1 && (totalLength % nodeDistance) > 0) ? (totalLength % nodeDistance) : nodeDistance; float sqrDesiredDistance = desiredDistance * desiredDistance; float sqrDistance = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z; // 只有当距离差异超过一定阈值时才调整 if (Mathf.Abs(sqrDistance - sqrDesiredDistance) > 0.001f) { float distance = Mathf.Sqrt(sqrDistance); float difference = desiredDistance - distance; Vector3 direction = diff / distance; Vector3 adjustment = direction * (difference * halfStiffness); currentNodePositions[i] += adjustment; currentNodePositions[i + 1] -= adjustment; } } } void AdjustCollisions() { for (int i = 1; i < totalNodes; i += 2) { int hits = Physics.OverlapSphereNonAlloc( currentNodePositions[i], nodeColliderRadius, colliderHitBuffer, ~(1 << 8)); for (int n = 0; n < hits; n++) { if (Physics.ComputePenetration( nodeCollider, currentNodePositions[i], Quaternion.identity, colliderHitBuffer[n], colliderHitBuffer[n].transform.position, colliderHitBuffer[n].transform.rotation, out Vector3 direction, out float distance)) { currentNodePositions[i] += direction * distance; } } } } void DrawRope() { lineRenderer.positionCount = totalNodes; lineRenderer.SetPositions(currentNodePositions); } void OnDestroy() { if (nodeTester != null) { Destroy(nodeTester); } } }