246 lines
7.5 KiB
C#
246 lines
7.5 KiB
C#
using UnityEngine;
|
|
|
|
[RequireComponent(typeof(LineRenderer))]
|
|
public class Rope : MonoBehaviour
|
|
{
|
|
[Header("Attachment Points")] [SerializeField]
|
|
public Transform startAttachment; // 绳子起点绑定的Transform
|
|
|
|
[SerializeField] public Transform endAttachment; // 绳子终点绑定的Transform
|
|
|
|
[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;
|
|
|
|
// 私有变量
|
|
Vector3 gravity;
|
|
|
|
// 数组和缓存
|
|
Vector3[] currentNodePositions;
|
|
Vector3[] previousNodePositions;
|
|
Collider[] colliderHitBuffer;
|
|
LineRenderer lineRenderer;
|
|
GameObject nodeTester;
|
|
SphereCollider nodeCollider;
|
|
int totalNodes;
|
|
float lastTotalLength;
|
|
|
|
void Awake()
|
|
{
|
|
// 获取组件引用
|
|
lineRenderer = GetComponent<LineRenderer>();
|
|
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;
|
|
|
|
if (remainingLength > 0 && totalLength > nodeDistance)
|
|
{
|
|
totalNodes++;
|
|
}
|
|
|
|
// 初始化或调整数组大小
|
|
System.Array.Resize(ref currentNodePositions, totalNodes);
|
|
System.Array.Resize(ref previousNodePositions, totalNodes);
|
|
colliderHitBuffer = new Collider[colliderBufferSize];
|
|
|
|
// 初始化节点位置
|
|
Vector3 startPos = startAttachment != null ? startAttachment.position : transform.position;
|
|
for (int i = 0; i < totalNodes; i++)
|
|
{
|
|
float distance = (i == totalNodes - 1 && remainingLength > 0) ? remainingLength : nodeDistance;
|
|
|
|
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;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
// 绑定到起点Transform
|
|
if (startAttachment != null)
|
|
{
|
|
currentNodePositions[0] = startAttachment.position;
|
|
}
|
|
|
|
// 绑定到终点Transform
|
|
if (endAttachment != null)
|
|
{
|
|
currentNodePositions[totalNodes - 1] = endAttachment.position;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 公开方法用于动态设置绑定点
|
|
public void SetAttachments(Transform start, Transform end)
|
|
{
|
|
startAttachment = start;
|
|
endAttachment = end;
|
|
}
|
|
} |