Files
Fishing2/Assets/Scripts/Rope.cs
2025-05-10 20:57:53 +08:00

231 lines
7.1 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; // 现在由代码计算
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);
}
}
}