231 lines
7.1 KiB
C#
231 lines
7.1 KiB
C#
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; // 现在由代码计算
|
||
|
||
void Awake()
|
||
{
|
||
// 计算节点数量
|
||
totalNodes = Mathf.FloorToInt(totalLength / nodeDistance) + 1;
|
||
float remainingLength = totalLength % nodeDistance;
|
||
|
||
// 如果剩余长度大于0,增加一个节点
|
||
if (remainingLength > 0 && totalLength > nodeDistance)
|
||
{
|
||
totalNodes++;
|
||
}
|
||
|
||
// 初始化数组
|
||
currentNodePositions = new Vector3[totalNodes];
|
||
previousNodePositions = new Vector3[totalNodes];
|
||
colliderHitBuffer = new Collider[colliderBufferSize];
|
||
|
||
// 获取组件引用
|
||
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;
|
||
|
||
// 初始化节点位置
|
||
Vector3 startPos = 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;
|
||
}
|
||
|
||
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;
|
||
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; // 比 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);
|
||
}
|
||
}
|
||
} |