diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index bef4f7ba7..88c0ad0fb 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -714,6 +714,7 @@ GameObject: - component: {fileID: 963194228} - component: {fileID: 963194227} - component: {fileID: 963194226} + - component: {fileID: 963194229} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera @@ -795,6 +796,50 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &963194229 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 --- !u!1 &1418847128 GameObject: m_ObjectHideFlags: 0 @@ -932,11 +977,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: daf71302d510df147b3f07dee056e8fd, type: 3} m_Name: m_EditorClassIdentifier: - mouseOffset: 10 - nodeDistance: 0.1 + startAttachment: {fileID: 1670765576} + endAttachment: {fileID: 1778778649} + nodeDistance: 0.05 nodeColliderRadius: 0.3 gravityStrength: 1.962 - totalLength: 6 + totalLength: 30 velocityDampen: 0.95 stiffness: 0.99 iterateCollisionsEvery: 1 @@ -1177,6 +1223,129 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: -90, y: 0, z: 0} +--- !u!1 &1670765571 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1670765576} + - component: {fileID: 1670765575} + - component: {fileID: 1670765574} + - component: {fileID: 1670765573} + - component: {fileID: 1670765572} + m_Layer: 0 + m_Name: Start + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1670765572 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1670765571} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f32dd1b0ec37b4443afde8d3426a4257, type: 3} + m_Name: + m_EditorClassIdentifier: + radius: 1 + angularSpeed: 100 +--- !u!135 &1670765573 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1670765571} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &1670765574 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1670765571} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 5365763285455e743bcff11042adedb5, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1670765575 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1670765571} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1670765576 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1670765571} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -1.38, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1770539745 GameObject: m_ObjectHideFlags: 0 @@ -1287,6 +1456,129 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} +--- !u!1 &1778778644 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1778778649} + - component: {fileID: 1778778648} + - component: {fileID: 1778778647} + - component: {fileID: 1778778646} + - component: {fileID: 1778778645} + m_Layer: 0 + m_Name: End + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1778778645 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1778778644} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f32dd1b0ec37b4443afde8d3426a4257, type: 3} + m_Name: + m_EditorClassIdentifier: + radius: 1 + angularSpeed: 100 +--- !u!135 &1778778646 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1778778644} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &1778778647 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1778778644} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 5365763285455e743bcff11042adedb5, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1778778648 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1778778644} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1778778649 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1778778644} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 2.14, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1790524738 GameObject: m_ObjectHideFlags: 0 @@ -1700,6 +1992,8 @@ SceneRoots: - {fileID: 1418847131} - {fileID: 272993070} - {fileID: 7476150} + - {fileID: 1670765576} + - {fileID: 1778778649} - {fileID: 1930140672} - {fileID: 601989252} - {fileID: 1790524742} diff --git a/Assets/Scripts/Rope.cs b/Assets/Scripts/Rope.cs index a48e749a1..0720324af 100644 --- a/Assets/Scripts/Rope.cs +++ b/Assets/Scripts/Rope.cs @@ -3,8 +3,10 @@ using UnityEngine; [RequireComponent(typeof(LineRenderer))] public class Rope : MonoBehaviour { - [Header("Demo Parameters")] [SerializeField, Min(0)] - float mouseOffset = 10f; + [Header("Attachment Points")] [SerializeField] + public Transform startAttachment; // 绳子起点绑定的Transform + + [SerializeField] public Transform endAttachment; // 绳子终点绑定的Transform [Header("Verlet Parameters")] [SerializeField] float nodeDistance = 0.35f; @@ -22,12 +24,7 @@ public class Rope : MonoBehaviour float ropeWidth = 0.1f; // 私有变量 - Camera cam; Vector3 gravity; - Vector3 startLock; - Vector3 endLock; - bool isStartLocked = false; - bool isEndLocked = false; // 数组和缓存 Vector3[] currentNodePositions; @@ -37,13 +34,12 @@ public class Rope : MonoBehaviour GameObject nodeTester; SphereCollider nodeCollider; int totalNodes; - float lastTotalLength; // 用于检测长度变化 + float lastTotalLength; void Awake() { // 获取组件引用 lineRenderer = GetComponent(); - cam = Camera.main; gravity = new Vector3(0, -gravityStrength, 0); // 初始化节点测试器 @@ -62,8 +58,7 @@ public class Rope : MonoBehaviour // 计算节点数量 totalNodes = Mathf.FloorToInt(totalLength / nodeDistance) + 1; float remainingLength = totalLength % nodeDistance; - - // 如果剩余长度大于0,增加一个节点 + if (remainingLength > 0 && totalLength > nodeDistance) { totalNodes++; @@ -75,18 +70,13 @@ public class Rope : MonoBehaviour colliderHitBuffer = new Collider[colliderBufferSize]; // 初始化节点位置 - Vector3 startPos = transform.position; + Vector3 startPos = startAttachment != null ? startAttachment.position : 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; - } + + currentNodePositions[i] = startPos; + previousNodePositions[i] = startPos; startPos.y -= distance; } @@ -105,55 +95,29 @@ public class Rope : MonoBehaviour 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; - + float distance = (i == currentNodePositions.Length - 1 && (totalLength % nodeDistance) > 0) + ? (totalLength % nodeDistance) + : nodeDistance; + lastPos.y -= distance; currentNodePositions[i] = lastPos; previousNodePositions[i] = lastPos; @@ -169,7 +133,6 @@ public class Rope : MonoBehaviour { ApplyConstraint(); - // 减少碰撞检测频率 if (i % (iterateCollisionsEvery + 1) == 0) { AdjustCollisions(); @@ -177,17 +140,11 @@ public class Rope : MonoBehaviour } } - 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; @@ -196,14 +153,18 @@ public class Rope : MonoBehaviour void ApplyConstraint() { - // 锁定端点 - currentNodePositions[0] = startLock; - if (isStartLocked && isEndLocked) + // 绑定到起点Transform + if (startAttachment != null) { - currentNodePositions[totalNodes - 1] = endLock; + currentNodePositions[0] = startAttachment.position; + } + + // 绑定到终点Transform + if (endAttachment != null) + { + currentNodePositions[totalNodes - 1] = endAttachment.position; } - // 预计算所有常用值 float halfStiffness = 0.5f * stiffness; int nodeCountMinusOne = totalNodes - 1; @@ -213,14 +174,13 @@ public class Rope : MonoBehaviour Vector3 node2 = currentNodePositions[i + 1]; Vector3 diff = node1 - node2; - // 计算期望的距离 - 如果是最后一个段且有剩余长度,使用剩余长度 - float desiredDistance = (i == nodeCountMinusOne - 1 && (totalLength % nodeDistance) > 0) ? - (totalLength % nodeDistance) : nodeDistance; - + 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); @@ -276,4 +236,11 @@ public class Rope : MonoBehaviour Destroy(nodeTester); } } + + // 公开方法用于动态设置绑定点 + public void SetAttachments(Transform start, Transform end) + { + startAttachment = start; + endAttachment = end; + } } \ No newline at end of file