using UnityEngine; namespace NBF { [RequireComponent(typeof(LineRenderer))] public class FishingRope : 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, Range(1, 500)] int totalNodes = 100; [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.02f; // 私有变量 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; void Awake() { // 初始化数组 currentNodePositions = new Vector3[totalNodes]; previousNodePositions = new Vector3[totalNodes]; colliderHitBuffer = new Collider[colliderBufferSize]; // 获取组件引用 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; // 初始化节点位置 Vector3 startPos = transform.position; for (int i = 0; i < totalNodes; i++) { currentNodePositions[i] = startPos; previousNodePositions[i] = startPos; startPos.y -= nodeDistance; } // 设置线渲染器 lineRenderer.startWidth = ropeWidth; lineRenderer.endWidth = ropeWidth; } void Update() { // 处理鼠标输入 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 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; float sqrNodeDistance = nodeDistance * nodeDistance; 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 sqrDistance = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z; // 只有当距离差异超过一定阈值时才调整 if (Mathf.Abs(sqrDistance - sqrNodeDistance) > 0.001f) { float distance = Mathf.Sqrt(sqrDistance); float difference = nodeDistance - distance; Vector3 direction = diff / distance; // 比 normalized 更快 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() { // 直接使用currentNodePositions数组避免额外拷贝 lineRenderer.positionCount = totalNodes; lineRenderer.SetPositions(currentNodePositions); } void OnDestroy() { if (nodeTester != null) { Destroy(nodeTester); } } } }