Files
Fishing2/Assets/Scripts/Rope.cs
2025-05-10 21:10:06 +08:00

279 lines
8.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<LineRenderer>();
cam = Camera.main;
gravity = new Vector3(0, -gravityStrength, 0);
// 初始化节点测试器
nodeTester = new GameObject("Node Tester");
nodeTester.layer = 8;
nodeCollider = nodeTester.AddComponent<SphereCollider>();
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);
}
}
}