diff --git a/Assets/AdvancedRope/ExampleScenes/RopeTestScene_0.unity b/Assets/AdvancedRope/ExampleScenes/RopeTestScene_0.unity index 7400d98..5f8da37 100644 --- a/Assets/AdvancedRope/ExampleScenes/RopeTestScene_0.unity +++ b/Assets/AdvancedRope/ExampleScenes/RopeTestScene_0.unity @@ -355,7 +355,7 @@ GameObject: - component: {fileID: 524034410} - component: {fileID: 524034409} - component: {fileID: 524034408} - m_Layer: 0 + m_Layer: 6 m_Name: Plane m_TagString: Untagged m_Icon: {fileID: 0} @@ -468,6 +468,7 @@ GameObject: - component: {fileID: 678145338} - component: {fileID: 678145337} - component: {fileID: 678145336} + - component: {fileID: 678145340} m_Layer: 0 m_Name: RopeGenerator m_TagString: Untagged @@ -491,7 +492,7 @@ MonoBehaviour: enabled: 1 ropeStartPoint: {x: -3, y: 4, z: 0} ropeEndPoint: {x: 3, y: 4, z: 0} - ropeResolution: 0.895 + ropeResolution: 0.5 ropeRadius: 0.1 gravity: {x: 0, y: -9.81, z: 0} airFriction: 0.1 @@ -505,7 +506,7 @@ MonoBehaviour: enableCollisions: 1 collisionLayer: serializedVersion: 2 - m_Bits: 4294967295 + m_Bits: 64 enableSelfCollision: 0 initialPinPoints: - nodeIndex: 0 @@ -514,8 +515,7 @@ MonoBehaviour: - nodeIndex: -1 targetTransform: {fileID: 415232843} localPos: {x: 0, y: 0, z: 0} - meshRadialSegments: 8 - meshTextureTiling: 1 + ropeRendererComponent: {fileID: 0} --- !u!23 &678145337 MeshRenderer: m_ObjectHideFlags: 0 @@ -588,6 +588,20 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &678145340 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 678145335} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: daa54ae922807734c889e8404bfd7b2e, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::AdvancedRope.RopeMeshRenderer + radialSegments: 8 + textureTiling: 1 --- !u!1 &956472882 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/AdvancedRope/Scripts/IRopeRenderer.cs b/Assets/AdvancedRope/Scripts/IRopeRenderer.cs new file mode 100644 index 0000000..27c9b52 --- /dev/null +++ b/Assets/AdvancedRope/Scripts/IRopeRenderer.cs @@ -0,0 +1,10 @@ +namespace AdvancedRope +{ + public interface IRopeRenderer + { + void Bind(VerletRopeGenerator ropeGenerator); + void Rebuild(); + void Render(); + void Clear(); + } +} diff --git a/Assets/AdvancedRope/Scripts/IRopeRenderer.cs.meta b/Assets/AdvancedRope/Scripts/IRopeRenderer.cs.meta new file mode 100644 index 0000000..b677eab --- /dev/null +++ b/Assets/AdvancedRope/Scripts/IRopeRenderer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5fb18fbc92c2ecf49a7fcbb8fce7df36 \ No newline at end of file diff --git a/Assets/AdvancedRope/Scripts/RopeMesh.cs b/Assets/AdvancedRope/Scripts/RopeMeshRenderer.cs similarity index 56% rename from Assets/AdvancedRope/Scripts/RopeMesh.cs rename to Assets/AdvancedRope/Scripts/RopeMeshRenderer.cs index 84b41f4..b5a3fe0 100644 --- a/Assets/AdvancedRope/Scripts/RopeMesh.cs +++ b/Assets/AdvancedRope/Scripts/RopeMeshRenderer.cs @@ -2,12 +2,13 @@ namespace AdvancedRope { - public class RopeMesh + [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] + public class RopeMeshRenderer : MonoBehaviour, IRopeRenderer { - private readonly int _radialSegments; - private readonly float _textureTiling; - - private VerletRopeGenerator _verletRopeGenerator; + [SerializeField] private int radialSegments = 8; + [SerializeField] private float textureTiling = 1f; + + private VerletRopeGenerator _ropeGenerator; private MeshFilter _meshFilter; private Mesh _mesh; private Vector3[] _vertices; @@ -15,47 +16,76 @@ namespace AdvancedRope private Vector2[] _uvs; private float _radius; - public RopeMesh(MeshFilter meshFilter, VerletRopeGenerator verletRopeGenerator, int radialSegments = 8, float textureTiling = 1f) + private void Awake() { - _verletRopeGenerator = verletRopeGenerator; - _meshFilter = meshFilter; - _radialSegments = radialSegments; - _textureTiling = textureTiling; + _meshFilter = GetComponent(); } - public void SetupRopeMesh() + public void Bind(VerletRopeGenerator ropeGenerator) { - _mesh = new Mesh + _ropeGenerator = ropeGenerator; + } + + public void Rebuild() + { + SetupRopeMesh(); + } + + public void Render() + { + UpdateMeshVertices(); + } + + public void Clear() + { + ClearMesh(); + } + + private void SetupRopeMesh() + { + if (_ropeGenerator == null) return; + if (!_meshFilter) _meshFilter = GetComponent(); + + var segmentCount = Mathf.Max(3, radialSegments); + + if (_mesh == null) { - name = "ProceduralRope" - }; - _mesh.MarkDynamic(); + _mesh = new Mesh + { + name = "ProceduralRope" + }; + _mesh.MarkDynamic(); + } + else + { + _mesh.Clear(); + } _meshFilter.sharedMesh = _mesh; - - _radius = _verletRopeGenerator.ropeRadius; - var nodes = _verletRopeGenerator.Nodes; - var vertexCount = nodes.Count * (_radialSegments + 1) + 2; - var triCount = (nodes.Count - 1) * _radialSegments * 6; - var capTrisCount = (_radialSegments * 3) * 2; - + + _radius = _ropeGenerator.ropeRadius; + var nodes = _ropeGenerator.Nodes; + var vertexCount = nodes.Count * (segmentCount + 1) + 2; + var triCount = (nodes.Count - 1) * segmentCount * 6; + var capTrisCount = (segmentCount * 3) * 2; + _vertices = new Vector3[vertexCount]; _uvs = new Vector2[vertexCount]; _triangles = new int[triCount + capTrisCount]; - + var triIndex = 0; - + for (var i = 0; i < nodes.Count - 1; i++) { - for (var j = 0; j < _radialSegments; j++) + for (var j = 0; j < segmentCount; j++) { - var current = i * (_radialSegments + 1) + j; - var next = current + _radialSegments + 1; + var current = i * (segmentCount + 1) + j; + var next = current + segmentCount + 1; _triangles[triIndex++] = current; _triangles[triIndex++] = current + 1; _triangles[triIndex++] = next; - + _triangles[triIndex++] = next; _triangles[triIndex++] = current + 1; _triangles[triIndex++] = next + 1; @@ -64,21 +94,21 @@ namespace AdvancedRope var startCenterIndex = vertexCount - 2; var endCenterIndex = vertexCount - 1; - + // Start Cap - for (var j = 0; j < _radialSegments; j++) + for (var j = 0; j < segmentCount; j++) { - var current = j; + var current = j; var next = j + 1; _triangles[triIndex++] = startCenterIndex; - _triangles[triIndex++] = next; + _triangles[triIndex++] = next; _triangles[triIndex++] = current; } - + // End Cap - var lastRingStart = (nodes.Count - 1) * (_radialSegments + 1); - for (var j = 0; j < _radialSegments; j++) + var lastRingStart = (nodes.Count - 1) * (segmentCount + 1); + for (var j = 0; j < segmentCount; j++) { var current = lastRingStart + j; var next = lastRingStart + j + 1; @@ -90,22 +120,36 @@ namespace AdvancedRope _mesh.vertices = _vertices; _mesh.triangles = _triangles; + _mesh.uv = _uvs; + _mesh.RecalculateBounds(); } - - public void UpdateMesh() + + private void ClearMesh() { - UpdateMeshVertices(); + if (_meshFilter) + _meshFilter.sharedMesh = null; + + if (_mesh != null) + { + if (Application.isPlaying) + Destroy(_mesh); + else + DestroyImmediate(_mesh); + } + + _mesh = null; } - public void ClearMesh() - { - _meshFilter.sharedMesh = null; - } - + private void UpdateMeshVertices() { - var verletTransform = _verletRopeGenerator.transform; - var nodes = _verletRopeGenerator.Nodes; - if(nodes.Count < 2) return; + if (_ropeGenerator == null || _mesh == null) return; + + var segmentCount = Mathf.Max(3, radialSegments); + var uvTiling = Mathf.Max(0.01f, textureTiling); + + var verletTransform = _ropeGenerator.transform; + var nodes = _ropeGenerator.Nodes; + if (nodes.Count < 2) return; var lastRotation = Quaternion.identity; var lastForward = Vector3.zero; @@ -117,9 +161,9 @@ namespace AdvancedRope forward = (nodes[i + 1].position - nodes[i].position).normalized; else forward = (nodes[i].position - nodes[i - 1].position).normalized; - + if (forward == Vector3.zero) forward = Vector3.up; - + Quaternion rotation; if (i == 0) @@ -127,16 +171,16 @@ namespace AdvancedRope else { var swing = Quaternion.FromToRotation(lastForward, forward); - + rotation = swing * lastRotation; } - + lastRotation = rotation; lastForward = forward; - - for (var j = 0; j <= _radialSegments; j++) + + for (var j = 0; j <= segmentCount; j++) { - var u = (float)j / _radialSegments; + var u = (float)j / segmentCount; var angle = u * Mathf.PI * 2; var x = Mathf.Cos(angle) * _radius; @@ -146,24 +190,24 @@ namespace AdvancedRope var worldPos = nodes[i].position + (rotation * localPos); var meshLocalPos = verletTransform.InverseTransformPoint(worldPos); - - var vertIndex = i * (_radialSegments + 1) + j; + + var vertIndex = i * (segmentCount + 1) + j; _vertices[vertIndex] = meshLocalPos; - var v = (float)i / (nodes.Count - 1) * _textureTiling; + var v = (float)i / (nodes.Count - 1) * uvTiling; _uvs[vertIndex] = new Vector2(u, v); } } - + var startCenterIndex = _vertices.Length - 2; var endCenterIndex = _vertices.Length - 1; _vertices[startCenterIndex] = verletTransform.InverseTransformPoint(nodes[0].position); _uvs[startCenterIndex] = new Vector2(0.5f, 0f); - + _vertices[endCenterIndex] = verletTransform.InverseTransformPoint(nodes[^1].position); - _uvs[endCenterIndex] = new Vector2(0.5f, 1f * _textureTiling); - + _uvs[endCenterIndex] = new Vector2(0.5f, uvTiling); + _mesh.vertices = _vertices; _mesh.uv = _uvs; diff --git a/Assets/AdvancedRope/Scripts/RopeMesh.cs.meta b/Assets/AdvancedRope/Scripts/RopeMeshRenderer.cs.meta similarity index 100% rename from Assets/AdvancedRope/Scripts/RopeMesh.cs.meta rename to Assets/AdvancedRope/Scripts/RopeMeshRenderer.cs.meta diff --git a/Assets/AdvancedRope/Scripts/VerletRopeGenerator.cs b/Assets/AdvancedRope/Scripts/VerletRopeGenerator.cs index 1fad064..3d4a1e4 100644 --- a/Assets/AdvancedRope/Scripts/VerletRopeGenerator.cs +++ b/Assets/AdvancedRope/Scripts/VerletRopeGenerator.cs @@ -7,7 +7,6 @@ namespace AdvancedRope /// /// 用于生成并模拟基于 Verlet 积分绳索的基类。 /// - [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class VerletRopeGenerator : MonoBehaviour { #region --- 数据结构 --- @@ -24,7 +23,7 @@ namespace AdvancedRope public RopeNode connectedNode; public Vector3 connectionOffset; - + public Transform pinnedTransform; public Vector3 pinLocalPos; @@ -46,7 +45,7 @@ namespace AdvancedRope pinnedTransform = t; pinLocalPos = offset; } - + /// /// 将该节点连接到另一条绳索节点,可选偏移量。 /// @@ -59,7 +58,7 @@ namespace AdvancedRope isPinned = false; pinnedTransform = null; } - + /// /// 取消该节点的固定状态。 /// @@ -82,8 +81,7 @@ namespace AdvancedRope [System.Serializable] public struct PinConfiguration { - [Tooltip("要固定的节点索引(0 = 起点,-1 = 末尾)")] - public int nodeIndex; + [Tooltip("要固定的节点索引(0 = 起点,-1 = 末尾)")] public int nodeIndex; public Transform targetTransform; public Vector3 localPos; } @@ -95,33 +93,29 @@ namespace AdvancedRope //[Header("通用设置")] public bool initializeOnStart = false; public bool enabled = true; - [Tooltip("绳索起点")] - public Vector3 ropeStartPoint; - [Tooltip("绳索终点(可选,若为空则绳索自由下垂)")] - public Vector3 ropeEndPoint; + [Tooltip("绳索起点")] public Vector3 ropeStartPoint; + [Tooltip("绳索终点(可选,若为空则绳索自由下垂)")] public Vector3 ropeEndPoint; //[Header("分辨率与尺寸")] - [Range(0.01f, 1f)] - [Tooltip("0.1 = 稀疏,1.0 = 按半径可达到的最大密度")] + [Range(0.01f, 1f)] [Tooltip("0.1 = 稀疏,1.0 = 按半径可达到的最大密度")] public float ropeResolution = 0.5f; + public float ropeRadius = 0.1f; - + //[Header("物理参数")] public Vector3 gravity = new Vector3(0, -9.81f, 0); [Range(0f, 1f)] public float airFriction = 0.1f; [Range(0f, 1f)] public float stiffness = 1.0f; - [Tooltip("约束求解迭代次数。值越高越硬,但开销更大")] - public int constraintIterations = 8; - [Tooltip("绳索与刚体碰撞时施加给刚体的力")] - public float collisionPushPower = 5f; + [Tooltip("约束求解迭代次数。值越高越硬,但开销更大")] public int constraintIterations = 8; + [Tooltip("绳索与刚体碰撞时施加给刚体的力")] public float collisionPushPower = 5f; [Range(0f, 1f)] public float contactFriction = 0.5f; - + //[Header("耦合设置")] public Rigidbody connectedBodyStart; public Rigidbody connectedBodyEnd; public float rigidbodyInteractionForce = 10f; - + //[Header("碰撞设置")] public bool enableCollisions = true; public LayerMask collisionLayer; @@ -129,65 +123,82 @@ namespace AdvancedRope //[Header("初始设置")] public List initialPinPoints; - - //[Header("网格设置")] - public int meshRadialSegments = 8; - public float meshTextureTiling = 1f; #endregion - #region --- 公开属性 --- + #region --- 公开属性 --- public List Nodes { get; private set; } = new List(); #endregion #region --- 私有变量 --- - + private float _segmentLength; private float _preferredSegmentLength; private Collider[] _collisionBuffer = new Collider[10]; private List _ignoreColliders = new List(); - - private RopeMesh _ropeMesh; - + + private IRopeRenderer _ropeRenderer; + #endregion #region --- 初始化 --- private void OnEnable() { - if(enabled) + if (enabled) VerletPhysics.RegisterRopeGenerator(this); } + private void OnDisable() { VerletPhysics.UnregisterRopeGenerator(this); } - + + private void Awake() + { + ResolveRenderer(); + } private void Start() { - if(initializeOnStart && enabled) + if (initializeOnStart && enabled) InitializeRope(); } + private void ResolveRenderer() + { + if (_ropeRenderer != null) return; + var behaviours = GetComponents(); + foreach (var behaviour in behaviours) + { + if (behaviour is not IRopeRenderer ropeRenderer) continue; + _ropeRenderer = ropeRenderer; + break; + } + + if (_ropeRenderer == null) + Debug.LogWarning("未找到 IRopeRenderer 实现,绳索将不会渲染。请在预制体上绑定渲染组件。", this); + } + /// /// 根据参数从零创建绳索。 /// public void InitializeRope() { - _ropeMesh?.ClearMesh(); - _ropeMesh = new RopeMesh(GetComponent(), this, meshRadialSegments, meshTextureTiling); + ResolveRenderer(); + _ropeRenderer?.Clear(); + _ropeRenderer?.Bind(this); Nodes.Clear(); _ignoreColliders.Clear(); - + var startPos = ropeStartPoint; var endPos = ropeEndPoint; - - if(Vector3.Distance(startPos, endPos) < 0.01f) + + if (Vector3.Distance(startPos, endPos) < 0.01f) endPos = startPos + Vector3.forward * 5f; - + var totalDistance = Vector3.Distance(startPos, endPos); var diameter = ropeRadius * 2f; @@ -195,39 +206,39 @@ namespace AdvancedRope var minNodes = 2; var nodeCount = Mathf.RoundToInt(Mathf.Lerp(minNodes, maxNodes, ropeResolution)); - + _segmentLength = totalDistance / (nodeCount - 1); _preferredSegmentLength = _segmentLength; - + var direction = (endPos - startPos).normalized; for (var i = 0; i < nodeCount; i++) { var pos = startPos + direction * (_segmentLength * i); Nodes.Add(new RopeNode(pos)); } - + // 应用初始固定点配置 ApplyInitialPins(); - - if(connectedBodyStart) + + if (connectedBodyStart) _ignoreColliders.AddRange(connectedBodyStart.GetComponentsInChildren()); - if(connectedBodyEnd) + if (connectedBodyEnd) _ignoreColliders.AddRange(connectedBodyEnd.GetComponentsInChildren()); - - _ropeMesh.SetupRopeMesh(); + + _ropeRenderer?.Rebuild(); } private void ApplyInitialPins() { - if(initialPinPoints == null) return; + if (initialPinPoints == null) return; foreach (var pin in initialPinPoints) { var index = pin.nodeIndex; if (index < 0) index = Nodes.Count + index; - - if(index >= 0 && index < Nodes.Count && pin.targetTransform) + + if (index >= 0 && index < Nodes.Count && pin.targetTransform) PinNode(index, pin.targetTransform, pin.localPos); } } @@ -238,15 +249,15 @@ namespace AdvancedRope private void LateUpdate() { - if(!enabled) return; - _ropeMesh.UpdateMesh(); + if (!enabled) return; + _ropeRenderer?.Render(); } private void FixedUpdate() { - if(!enabled) return; - if(Nodes.Count < 2) return; - + if (!enabled) return; + if (Nodes.Count < 2) return; + SimulateVerlet(); @@ -256,9 +267,9 @@ namespace AdvancedRope ApplyInterRopeConnection(); // 如需强制同步连接节点,可启用下一行 - + // 每次迭代都检查碰撞 - if(enableCollisions) + if (enableCollisions) ResolveCollisions(); } @@ -273,15 +284,15 @@ namespace AdvancedRope for (var i = 0; i < Nodes.Count; i++) { var myNode = Nodes[i]; - if(myNode.connectedNode == null) continue; - + if (myNode.connectedNode == null) continue; + var otherNode = myNode.connectedNode; - + var currentVector = myNode.position - otherNode.position; var targetVector = myNode.connectionOffset; - + var diff = currentVector - targetVector; - + var moveFactorA = 0.5f; var moveFactorB = 0.5f; @@ -295,14 +306,14 @@ namespace AdvancedRope moveFactorA = 1f; moveFactorB = 0f; } - - if(moveFactorA > 0f) + + if (moveFactorA > 0f) myNode.position -= diff * moveFactorA; - if(moveFactorB > 0f) + if (moveFactorB > 0f) otherNode.position += diff * moveFactorB; } } - + /// /// 在约束迭代过程中防止连接节点发生漂移。 /// @@ -313,7 +324,7 @@ namespace AdvancedRope node.position = node.connectedNode.position + node.connectionOffset; } - + /// /// 基础 Verlet 积分:x(t+dt) = 2x(t) - x(t-dt) + a*dt*dt /// @@ -331,12 +342,13 @@ namespace AdvancedRope node.position = node.pinnedTransform.TransformPoint(node.pinLocalPos); node.prevPosition = node.position; } + continue; } - + var velocity = (node.position - node.prevPosition) * (1f - airFriction); var newPos = node.position + velocity + gravityStep; - + node.prevPosition = node.position; node.position = newPos; } @@ -348,17 +360,17 @@ namespace AdvancedRope { var nodeA = Nodes[i]; var nodeB = Nodes[i + 1]; - + var diff = nodeA.position - nodeB.position; var dist = diff.magnitude; if (dist < 0.0001f) continue; - + var differance = (dist - _segmentLength) / dist; var translate = diff * (0.5f * differance * stiffness); - + if (!nodeA.isPinned) nodeA.position -= translate; - + if (!nodeB.isPinned) nodeB.position += translate; } @@ -373,7 +385,7 @@ namespace AdvancedRope /// private void ApplyCouplingForces() { - if(connectedBodyStart && Nodes.Count > 0) + if (connectedBodyStart && Nodes.Count > 0) { var firstNode = Nodes[0]; @@ -388,12 +400,12 @@ namespace AdvancedRope } } } - - if(connectedBodyEnd && Nodes.Count > 0) + + if (connectedBodyEnd && Nodes.Count > 0) { var lastNode = Nodes[^1]; var prevNode = Nodes[^2]; - + var pullDir = (prevNode.position - lastNode.position).normalized; var dist = Vector3.Distance(prevNode.position, lastNode.position); @@ -416,7 +428,7 @@ namespace AdvancedRope { for (var i = 0; i < Nodes.Count; i++) { - if(Nodes[i].isPinned) continue; + if (Nodes[i].isPinned) continue; HandleTerrainCollision(Nodes[i]); @@ -431,17 +443,18 @@ namespace AdvancedRope { var terrain = UnityEngine.Terrain.activeTerrain; if (!terrain) return; - + var terrainHeight = terrain.SampleHeight(node.position) + terrain.transform.position.y; if (node.position.y < terrainHeight + ropeRadius) { node.position = new Vector3(node.position.x, terrainHeight + ropeRadius, node.position.z); - + // 地面摩擦 var vel = node.position - node.prevPosition; node.prevPosition = node.position - (vel * 0.5f); } } + private void HandleStandardCollisions(RopeNode node) { var count = Physics.OverlapSphereNonAlloc(node.position, ropeRadius, _collisionBuffer, collisionLayer); @@ -449,7 +462,7 @@ namespace AdvancedRope for (var i = 0; i < count; i++) { var col = _collisionBuffer[i]; - if(col is TerrainCollider) continue; + if (col is TerrainCollider) continue; if (_ignoreColliders.Contains(col)) continue; if (col is MeshCollider { convex: false } meshCol) @@ -457,40 +470,40 @@ namespace AdvancedRope HandleNonConvexCollision(node, meshCol); continue; } - + var closestPoint = col.ClosestPoint(node.position); var distToSurface = Vector3.Distance(node.position, closestPoint); - + if (distToSurface < ropeRadius) { var pushDir = (node.position - closestPoint).normalized; - - if(pushDir == Vector3.zero) + + if (pushDir == Vector3.zero) pushDir = (node.position - node.prevPosition).normalized; - + var penetrationDepth = ropeRadius - distToSurface; var pushVector = pushDir * penetrationDepth; - - if(!node.isPinned) + + if (!node.isPinned) node.position += pushVector; - + // 对刚体施加反作用力 var targetRb = col.attachedRigidbody; if (targetRb && !targetRb.isKinematic) { var scale = collisionPushPower / constraintIterations; var reactionForce = -pushDir * (penetrationDepth * scale); - + targetRb.AddForceAtPosition(reactionForce, node.position, ForceMode.Impulse); - + // 摩擦效果 - + var nodeVel = (node.position - node.prevPosition) / Time.fixedDeltaTime; var rbVel = targetRb.GetPointVelocity(node.position); var relativeVel = rbVel - nodeVel; - + var tangentVel = Vector3.ProjectOnPlane(relativeVel, pushDir); - + var normalForceMag = reactionForce.magnitude; var frictionMag = normalForceMag * contactFriction; @@ -498,7 +511,7 @@ namespace AdvancedRope { var frictionDir = -tangentVel.normalized; var frictionForce = frictionDir * frictionMag; - + targetRb.AddForceAtPosition(frictionForce, node.position, ForceMode.Impulse); if (!node.isPinned) @@ -511,28 +524,30 @@ namespace AdvancedRope } } } + private void HandleNonConvexCollision(RopeNode node, MeshCollider meshCol) { var travelDir = node.position - node.prevPosition; var travelDist = travelDir.magnitude; - + if (travelDist < 0.0001f) return; if (Physics.SphereCast(node.prevPosition, ropeRadius, travelDir.normalized, out var hit, travelDist + ropeRadius, collisionLayer)) { - if(hit.collider == meshCol) + if (hit.collider == meshCol) node.position = hit.point + hit.normal * (ropeRadius * 1.1f); } } + private void HandleSelfCollision(int currentIndex) { var currentNode = Nodes[currentIndex]; for (var i = 0; i < Nodes.Count; i++) { - if(Mathf.Abs(currentIndex - i) <= 1) continue; - + if (Mathf.Abs(currentIndex - i) <= 1) continue; + var otherNode = Nodes[i]; var distSqr = (currentNode.position - otherNode.position).sqrMagnitude; var minSpacing = ropeRadius * 2f; @@ -542,10 +557,10 @@ namespace AdvancedRope var dist = Mathf.Sqrt(distSqr); var pushDir = (currentNode.position - otherNode.position).normalized; var pushAmount = (minSpacing - dist) * 0.5f; - - if(!currentNode.isPinned) + + if (!currentNode.isPinned) currentNode.position += pushDir * pushAmount; - if(!otherNode.isPinned) + if (!otherNode.isPinned) otherNode.position -= pushDir * pushAmount; } } @@ -557,10 +572,10 @@ namespace AdvancedRope public void Enable() { - if(enabled) return; - if(gameObject.activeInHierarchy) + if (enabled) return; + if (gameObject.activeInHierarchy) VerletPhysics.RegisterRopeGenerator(this); - + InitializeRope(); enabled = true; } @@ -568,7 +583,7 @@ namespace AdvancedRope public void Disable() { VerletPhysics.UnregisterRopeGenerator(this); - _ropeMesh?.ClearMesh(); + _ropeRenderer?.Clear(); enabled = false; } @@ -578,7 +593,7 @@ namespace AdvancedRope public void IncreaseLength() { if (TryInsertSegmentAtStart()) - _ropeMesh?.SetupRopeMesh(); + _ropeRenderer?.Rebuild(); } /// @@ -587,7 +602,7 @@ namespace AdvancedRope public void DecreaseLength() { if (TryRemoveFreeSegmentNode()) - _ropeMesh?.SetupRopeMesh(); + _ropeRenderer?.Rebuild(); } /// @@ -620,7 +635,8 @@ namespace AdvancedRope targetLength = Mathf.Max(0.001f, targetLength); - var preferredSegmentLength = Mathf.Max(0.001f, _preferredSegmentLength > 0f ? _preferredSegmentLength : _segmentLength); + var preferredSegmentLength = Mathf.Max(0.001f, + _preferredSegmentLength > 0f ? _preferredSegmentLength : _segmentLength); var desiredSegmentCount = Mathf.Max(1, Mathf.RoundToInt(targetLength / preferredSegmentLength)); var topologyChanged = false; @@ -642,7 +658,7 @@ namespace AdvancedRope _segmentLength = targetLength / Mathf.Max(1, Nodes.Count - 1); if (topologyChanged) - _ropeMesh?.SetupRopeMesh(); + _ropeRenderer?.Rebuild(); } /// @@ -675,7 +691,7 @@ namespace AdvancedRope return false; } - + /// /// 将指定索引节点固定到目标变换组件,可选本地偏移。 /// @@ -684,32 +700,32 @@ namespace AdvancedRope /// public void PinNode(int index, Transform target, Vector3 localOffset = default) { - if(index < 0) + if (index < 0) index = Nodes.Count + index; if (index >= 0 && index < Nodes.Count) { Nodes[index].PinTo(target, localOffset); if (index == 0) ropeStartPoint = target.position; - if(index == Nodes.Count -1) + if (index == Nodes.Count - 1) ropeEndPoint = target.position; } } - + /// /// 解除指定索引节点的固定。 /// /// 0 表示起点,-1 表示末尾节点 public void UnpinNode(int index) { - if(index < 0) + if (index < 0) index = Nodes.Count + index; if (index >= 0 && index < Nodes.Count) { Nodes[index].Unpin(); - if(index == 0) + if (index == 0) ropeStartPoint = transform.position; - if(index == Nodes.Count -1) + if (index == Nodes.Count - 1) ropeEndPoint = transform.position + Vector3.forward * 5f; } } @@ -722,30 +738,31 @@ namespace AdvancedRope /// public void ConnectStartRigidbody(Rigidbody connectedRb, Vector3 localPos, float time) { - StopCoroutine(RigibBodyConnectionRoute(connectedRb,localPos, time)); - if(connectedBodyStart) + StopCoroutine(RigibBodyConnectionRoute(connectedRb, localPos, time)); + if (connectedBodyStart) { _ignoreColliders.RemoveAll(c => - c && (c.gameObject == connectedBodyStart.gameObject) || + c && (c.gameObject == connectedBodyStart.gameObject) || c.transform.IsChildOf(connectedBodyStart.transform)); } UnpinNode(0); connectedBodyStart = null; - - if(Nodes.Count == 0) return; + + if (Nodes.Count == 0) return; Nodes[0].isPinned = true; - + StartCoroutine(RigibBodyConnectionRoute(connectedRb, localPos, time)); } private IEnumerator RigibBodyConnectionRoute(Rigidbody connectedRb, Vector3 localPos, float time) { - var dist = Vector3.Distance(Nodes[0].position, connectedRb.position + connectedRb.transform.TransformVector(localPos)); + var dist = Vector3.Distance(Nodes[0].position, + connectedRb.position + connectedRb.transform.TransformVector(localPos)); var deltaTime = Time.fixedDeltaTime; var deltaDist = dist; - if(time > 0.01f) + if (time > 0.01f) deltaDist = deltaTime * (dist / time); var wait = new WaitForFixedUpdate(); while (dist > 0.01f) @@ -757,6 +774,7 @@ namespace AdvancedRope connectedRb.position + connectedRb.transform.TransformVector(localPos)); yield return wait; } + connectedBodyStart = connectedRb; _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren()); PinNode(0, connectedBodyStart.transform, localPos); @@ -772,14 +790,15 @@ namespace AdvancedRope if (connectedBodyStart) { _ignoreColliders.RemoveAll(c => - c&& (c.gameObject == connectedBodyStart.gameObject) || + c && (c.gameObject == connectedBodyStart.gameObject) || c.transform.IsChildOf(connectedBodyStart.transform)); } + connectedBodyStart = connectedRb; _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren()); PinNode(0, connectedBodyStart.transform, localPos); } - + /// /// 将绳索末端连接到指定刚体。 /// @@ -790,103 +809,108 @@ namespace AdvancedRope if (connectedBodyEnd) { _ignoreColliders.RemoveAll(c => - c && (c.gameObject == connectedBodyEnd.gameObject) || + c && (c.gameObject == connectedBodyEnd.gameObject) || c.transform.IsChildOf(connectedBodyEnd.transform)); } + connectedBodyEnd = connectedRb; _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren()); PinNode(-1, connectedBodyEnd.transform, localPos); } - + /// /// 断开绳索起点与刚体的连接。 /// public void DisconnectStartRigidbody() { if (!connectedBodyStart) return; - + _ignoreColliders.RemoveAll(c => - c != null && (c.gameObject == connectedBodyStart.gameObject) || + c != null && (c.gameObject == connectedBodyStart.gameObject) || c.transform.IsChildOf(connectedBodyStart.transform)); - + connectedBodyStart = null; UnpinNode(0); } - + /// /// 断开绳索末端与刚体的连接。 /// public void DisconnectEndRigidbody() { if (!connectedBodyEnd) return; - + _ignoreColliders.RemoveAll(c => - c != null && (c.gameObject == connectedBodyEnd.gameObject) || + c != null && (c.gameObject == connectedBodyEnd.gameObject) || c.transform.IsChildOf(connectedBodyEnd.transform)); - + connectedBodyEnd = null; UnpinNode(-1); } - - + + /// /// 将当前绳索末端连接到目标绳索的某个节点。 /// /// /// /// - public void ConnectEndToOtherRope(VerletRopeGenerator targetRope, int targetNodeIndex, Vector3 connectionOffset = default) + public void ConnectEndToOtherRope(VerletRopeGenerator targetRope, int targetNodeIndex, + Vector3 connectionOffset = default) { ConnectToOtherRope(-1, targetRope, targetNodeIndex, connectionOffset); } - + /// /// 将当前绳索起点连接到目标绳索的某个节点。 /// /// /// /// - public void ConnectStartToOtherRope(VerletRopeGenerator targetRope, int targetNodeIndex, Vector3 connectionOffset = default) + public void ConnectStartToOtherRope(VerletRopeGenerator targetRope, int targetNodeIndex, + Vector3 connectionOffset = default) { ConnectToOtherRope(0, targetRope, targetNodeIndex, connectionOffset); } - + /// /// 将当前绳索的某个节点连接到目标绳索的某个节点。 /// /// 0 表示起点,-1 表示末尾节点 /// /// 0 表示起点,-1 表示末尾节点 - public void ConnectToOtherRope(int myNodeIndex, VerletRopeGenerator targetRope, int targetNodeIndex, Vector3 connectionOffset = default) + public void ConnectToOtherRope(int myNodeIndex, VerletRopeGenerator targetRope, int targetNodeIndex, + Vector3 connectionOffset = default) { - if(myNodeIndex < 0) + if (myNodeIndex < 0) myNodeIndex = Nodes.Count + myNodeIndex; - if(targetNodeIndex < 0) + if (targetNodeIndex < 0) targetNodeIndex = targetRope.Nodes.Count + targetNodeIndex; - if(myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return; - if(!targetRope || targetNodeIndex < 0 || targetNodeIndex >= targetRope.Nodes.Count) return; - + if (myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return; + if (!targetRope || targetNodeIndex < 0 || targetNodeIndex >= targetRope.Nodes.Count) return; + var myNode = Nodes[myNodeIndex]; var targetNode = targetRope.Nodes[targetNodeIndex]; - - if(targetNodeIndex == 0) + + if (targetNodeIndex == 0) targetRope.DisconnectStartRigidbody(); - if(targetNodeIndex == targetRope.Nodes.Count - 1) + if (targetNodeIndex == targetRope.Nodes.Count - 1) targetRope.DisconnectEndRigidbody(); - + myNode.ConnectToNode(targetNode, connectionOffset); targetNode.ConnectToNode(myNode, -connectionOffset); } + /// /// 断开该节点与其他绳索节点的连接。 /// /// 0 表示起点,-1 表示末尾节点 public void DisconnectFromOtherRope(int myNodeIndex) { - if(myNodeIndex < 0) + if (myNodeIndex < 0) myNodeIndex = Nodes.Count + myNodeIndex; - if(myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return; - + if (myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return; + var myNode = Nodes[myNodeIndex]; var targetNode = myNode.connectedNode; myNode.ConnectToNode(null); @@ -910,7 +934,7 @@ namespace AdvancedRope { var p1 = Nodes[i].position; var p2 = Nodes[i + 1].position; - + var distSqr = SqrDistanceRaySegment(ray, p1, p2, out var rayParam, out var segmentParam); if (distSqr <= (ropeRadius * ropeRadius) && rayParam < maxDistance && rayParam > 0) @@ -918,7 +942,7 @@ namespace AdvancedRope if (rayParam < closestDistSqr) { closestDistSqr = rayParam; - + hitInfo.DidHit = true; hitInfo.Rope = this; hitInfo.NodeIndex = segmentParam < 0.5f ? i : i + 1; @@ -926,27 +950,28 @@ namespace AdvancedRope hitInfo.Distance = rayParam; hitInfo.SegmentRatio = segmentParam; hitInfo.Point = ray.GetPoint(rayParam); - + hasHit = true; } } } - + return hasHit; } - private static float SqrDistanceRaySegment(Ray ray, Vector3 s1, Vector3 s2, out float rayParam, out float segmentParam) + private static float SqrDistanceRaySegment(Ray ray, Vector3 s1, Vector3 s2, out float rayParam, + out float segmentParam) { var u = ray.direction; var v = s2 - s1; var w = ray.origin - s1; - + var a = Vector3.Dot(u, u); var b = Vector3.Dot(u, v); var c = Vector3.Dot(v, v); var d = Vector3.Dot(u, w); var e = Vector3.Dot(v, w); - + var denom = a * c - b * b; float sc, tc; @@ -960,43 +985,44 @@ namespace AdvancedRope sc = (b * e - c * d) / denom; tc = (a * e - b * d) / denom; } + rayParam = sc; segmentParam = Mathf.Clamp01(tc); - + var closestPointOnSegment = s1 + v * segmentParam; var delta = closestPointOnSegment - ray.origin; rayParam = Vector3.Dot(delta, ray.direction); - - if(rayParam < 0f) + + if (rayParam < 0f) rayParam = 0f; var closestPointOnRay = ray.GetPoint(rayParam); - + return (closestPointOnSegment - closestPointOnRay).sqrMagnitude; } - + #endregion #region --- 调试 / 场景可视化 --- private void OnDrawGizmos() { - if(Nodes == null || Nodes.Count == 0) return; - + if (Nodes == null || Nodes.Count == 0) return; + Gizmos.color = Color.green; for (var i = 0; i < Nodes.Count - 1; i++) Gizmos.DrawLine(Nodes[i].position, Nodes[i + 1].position); - + Gizmos.color = Color.red; foreach (var node in Nodes) - if(node.isPinned) + if (node.isPinned) Gizmos.DrawWireSphere(node.position, ropeRadius * 1.2f); } private void OnDrawGizmosSelected() { - if(Application.isPlaying) return; + if (Application.isPlaying) return; var startPos = ropeStartPoint; var endPos = ropeEndPoint; @@ -1025,7 +1051,7 @@ namespace AdvancedRope foreach (var pin in initialPinPoints) { if (pin.targetTransform == null) continue; - + var idx = pin.nodeIndex; if (idx < 0) idx = nodeCount + idx; @@ -1067,7 +1093,7 @@ namespace AdvancedRope } } } - + #endregion } @@ -1081,4 +1107,4 @@ namespace AdvancedRope public float Distance; public float SegmentRatio; } -} +} \ No newline at end of file diff --git a/F2RopeLine2.sln.DotSettings.user b/F2RopeLine2.sln.DotSettings.user new file mode 100644 index 0000000..6c65f31 --- /dev/null +++ b/F2RopeLine2.sln.DotSettings.user @@ -0,0 +1,2 @@ + + ForceIncluded \ No newline at end of file diff --git a/UserSettings/EditorUserSettings.asset b/UserSettings/EditorUserSettings.asset index 5c4d5db..ed7ed51 100644 --- a/UserSettings/EditorUserSettings.asset +++ b/UserSettings/EditorUserSettings.asset @@ -24,20 +24,20 @@ EditorUserSettings: value: 055550005c055e0e58085a2741720f4415154a7f7b2c22652e794461b7e5376a flags: 0 RecentlyUsedSceneGuid-4: - value: 00040c5204025e5d0f59547242710744454e4d7f28717363757c1f63e7b0633c - flags: 0 - RecentlyUsedSceneGuid-5: value: 0050525253025d0b5a0f542748710f4446151a727d2b74652e714862b4b6636d flags: 0 - RecentlyUsedSceneGuid-6: + RecentlyUsedSceneGuid-5: value: 0206055103530a0908560e21497b0d4410161c2b2a712468757e4461b3e3306a flags: 0 - RecentlyUsedSceneGuid-7: + RecentlyUsedSceneGuid-6: value: 5100040304005e5a5b0d5875482659441715412b757b74342b781b6ab7e1653b flags: 0 - RecentlyUsedSceneGuid-8: + RecentlyUsedSceneGuid-7: value: 5005025453535f0f595d597b12225d44174f1a73757f22357f711c30b4e4666a flags: 0 + RecentlyUsedSceneGuid-8: + value: 00040c5204025e5d0f59547242710744454e4d7f28717363757c1f63e7b0633c + flags: 0 vcSharedLogLevel: value: 0d5e400f0650 flags: 0