diff --git a/Assets/Scenes/CutTest.unity b/Assets/Scenes/CutTest.unity index 8344a3f36..e0b67030e 100644 --- a/Assets/Scenes/CutTest.unity +++ b/Assets/Scenes/CutTest.unity @@ -207,6 +207,142 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!1 &173508913 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 173508914} + - component: {fileID: 173508915} + m_Layer: 0 + m_Name: Trail + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &173508914 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 173508913} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1568743344} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!96 &173508915 +TrailRenderer: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 173508913} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 0 + m_LightProbeUsage: 0 + m_ReflectionProbeUsage: 0 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, 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_Time: 1 + m_PreviewTimeScale: 1 + m_Parameters: + serializedVersion: 3 + widthMultiplier: 1 + widthCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0.027952194 + value: 0.30451202 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + colorGradient: + serializedVersion: 2 + key0: {r: 1, g: 1, b: 1, a: 1} + key1: {r: 1, g: 1, b: 1, a: 1} + key2: {r: 0, g: 0, b: 0, a: 0} + key3: {r: 0, g: 0, b: 0, a: 0} + key4: {r: 0, g: 0, b: 0, a: 0} + key5: {r: 0, g: 0, b: 0, a: 0} + key6: {r: 0, g: 0, b: 0, a: 0} + key7: {r: 0, g: 0, b: 0, a: 0} + ctime0: 0 + ctime1: 65535 + ctime2: 0 + ctime3: 0 + ctime4: 0 + ctime5: 0 + ctime6: 0 + ctime7: 0 + atime0: 0 + atime1: 65535 + atime2: 0 + atime3: 0 + atime4: 0 + atime5: 0 + atime6: 0 + atime7: 0 + m_Mode: 0 + m_ColorSpace: -1 + m_NumColorKeys: 2 + m_NumAlphaKeys: 2 + numCornerVertices: 0 + numCapVertices: 0 + alignment: 0 + textureMode: 0 + textureScale: {x: 1, y: 1} + shadowBias: 0.5 + generateLightingData: 0 + m_MinVertexDistance: 0.1 + m_MaskInteraction: 0 + m_Autodestruct: 0 + m_Emitting: 1 + m_ApplyActiveColorSpace: 1 --- !u!1 &519420028 GameObject: m_ObjectHideFlags: 0 @@ -1249,10 +1385,11 @@ Transform: m_GameObject: {fileID: 1568743342} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 3.05, y: -0.37, z: 0} + m_LocalPosition: {x: 3.66, y: 0.31, z: 0} m_LocalScale: {x: 1.5038, y: 1.5038, z: 1.5038} m_ConstrainProportionsScale: 0 - m_Children: [] + m_Children: + - {fileID: 173508914} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1568743345 @@ -1269,12 +1406,11 @@ MonoBehaviour: m_EditorClassIdentifier: targetSpriteRenderer: {fileID: 2110358530} snapDistance: 0.3 - detachDistance: 2 - tractionSpeed: 0.6 - rotationSpeed: 360 - edgeScaleFactor: 1.2 - normalEdgeColor: {r: 1, g: 1, b: 1, a: 1} - cutEdgeColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} + detachDistance: 10 + tractionSpeed: 1 + cutLineRenderer: {fileID: 1568743346} + cutLineWidth: 0.1 + noiseAmount: 0.05 --- !u!120 &1568743346 LineRenderer: serializedVersion: 2 @@ -1518,7 +1654,6 @@ GameObject: - component: {fileID: 2110358529} - component: {fileID: 2110358530} - component: {fileID: 2110358532} - - component: {fileID: 2110358531} m_Layer: 0 m_Name: Module Pic m_TagString: Cuttable @@ -1593,18 +1728,6 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 ---- !u!114 &2110358531 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2110358528} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ad912e1febe29ed499461c26b73ac2b8, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!60 &2110358532 PolygonCollider2D: m_ObjectHideFlags: 0 @@ -1688,6 +1811,37 @@ PolygonCollider2D: - {x: 0.5674419, y: -0.4883721} - {x: 0.572093, y: -0.43255815} m_UseDelaunayMesh: 0 +--- !u!1 &2135342729 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2135342730} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2135342730 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2135342729} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.5953367, y: -2.2389753, z: 0.43786627} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -1698,3 +1852,4 @@ SceneRoots: - {fileID: 1568743344} - {fileID: 1598025876} - {fileID: 1599995100} + - {fileID: 2135342730} diff --git a/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs b/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs index 494fa6518..818f6fc67 100644 --- a/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs +++ b/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs @@ -9,14 +9,10 @@ public class GearFollower : MonoBehaviour public float detachDistance = 1.0f; public float tractionSpeed = 2f; - [Header("切割效果参数")] - public float cutEdgeExpandDuration = 0.5f; // 卷边展开时间 - public float cutEdgeCoolDuration = 3f; // 冷却时间 - public float cutEdgeMaxOffset = 0.05f; // 卷边最大偏移量 - - public Gradient cutEdgeColorGradient; // 颜色渐变(从高温到冷却) - - public Material cutEdgeMaterial; // 用于绘制卷边的材质 + [Header("切割轨迹")] + public LineRenderer cutLineRenderer; + public float cutLineWidth = 0.1f; + public float noiseAmount = 0.05f; // 毛刺偏移幅度 private List edgePoints; private int currentEdgeIndex = 0; @@ -27,19 +23,24 @@ public class GearFollower : MonoBehaviour private Camera mainCamera; - // 记录切割的边段及其动画状态 - private class CutSegment - { - public int startIndex; - public float timeElapsed; // 0~卷边展开+冷却总时长 - public LineRenderer lineRenderer; - } - private List cutSegments = new List(); + private List cutLinePoints = new List(); + private bool isCutting = false; + + private float directionSwitchCooldown = 0.2f; + private float directionSwitchTimer = 0f; void Start() { mainCamera = Camera.main; LoadEdgePointsFromSprite(); + + if (cutLineRenderer != null) + { + cutLineRenderer.positionCount = 0; + cutLineRenderer.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth); + cutLineRenderer.material = new Material(Shader.Find("Sprites/Default")); + cutLineRenderer.numCapVertices = 0; // 线头不圆滑,显锐利 + } } void Update() @@ -60,8 +61,6 @@ public class GearFollower : MonoBehaviour UpdateTractionState(mouseWorldPos); break; } - - UpdateCutSegments(); } private void LoadEdgePointsFromSprite() @@ -82,7 +81,7 @@ public class GearFollower : MonoBehaviour return; } - float desiredSegmentLength = 0.1f; // 你可以根据需要调整这个值 + float desiredSegmentLength = 0.1f; edgePoints.Clear(); @@ -102,7 +101,6 @@ public class GearFollower : MonoBehaviour } } - // 确保闭环 if ((edgePoints[0] - edgePoints[edgePoints.Count - 1]).sqrMagnitude > 0.0001f) { edgePoints.Add(edgePoints[0]); @@ -120,7 +118,6 @@ public class GearFollower : MonoBehaviour { currentEdgeIndex = nearestIndex; - // 判断应该向前还是向后吸附 Vector2 prev = edgePoints[WrapIndex(currentEdgeIndex - 1)]; Vector2 next = edgePoints[WrapIndex(currentEdgeIndex + 1)]; @@ -134,9 +131,6 @@ public class GearFollower : MonoBehaviour } } - private float directionSwitchCooldown = 0.2f; - private float directionSwitchTimer = 0f; - private void UpdateTractionState(Vector2 mousePos) { Vector2 gearPos = transform.position; @@ -144,25 +138,29 @@ public class GearFollower : MonoBehaviour if (Vector2.Distance(mousePos, gearPos) > detachDistance) { currentState = State.Follow; + EndCutting(); return; } if (!Input.GetMouseButton(0)) { - // 不按左键不移动 + EndCutting(); return; } + if (!isCutting) + { + StartCutting(); + } + int nextIndex = WrapIndex(currentEdgeIndex + tractionDirection); Vector2 from = edgePoints[currentEdgeIndex]; Vector2 to = edgePoints[nextIndex]; - // 方向判断优化:使用角度判断是否反方向 Vector2 forward = (to - from).normalized; Vector2 toMouse = (mousePos - from).normalized; - float angle = Vector2.SignedAngle(forward, toMouse); // -180 ~ 180 + float angle = Vector2.SignedAngle(forward, toMouse); - // 当角度大于120度,说明鼠标方向明显是反的 if (Mathf.Abs(angle) > 120f) { directionSwitchTimer += Time.deltaTime; @@ -171,7 +169,6 @@ public class GearFollower : MonoBehaviour tractionDirection *= -1; directionSwitchTimer = 0f; - // 更新前进段 nextIndex = WrapIndex(currentEdgeIndex + tractionDirection); from = edgePoints[currentEdgeIndex]; to = edgePoints[nextIndex]; @@ -193,12 +190,8 @@ public class GearFollower : MonoBehaviour { transform.position = targetPoint; - // 如果到达段末,前进一段 if (Vector2.Distance(targetPoint, to) < 0.01f) { - // 记录切割痕迹 - AddCutSegment(currentEdgeIndex); - currentEdgeIndex = nextIndex; } } @@ -206,85 +199,43 @@ public class GearFollower : MonoBehaviour { transform.position += (Vector3)(moveDir.normalized * maxStep); } + + // 记录切割点(带噪声模拟毛糙) + Vector3 noisyPoint = transform.position; + noisyPoint.x += Random.Range(-noiseAmount, noiseAmount); + noisyPoint.y += Random.Range(-noiseAmount, noiseAmount); + AddCutPoint(noisyPoint); } - // 添加一个切割段,生成卷边视觉效果 - private void AddCutSegment(int startIndex) + private void StartCutting() { - // 防止重复添加同一段(可选) - foreach (var seg in cutSegments) + isCutting = true; + //cutLinePoints.Clear(); + if (cutLineRenderer != null) { - if (seg.startIndex == startIndex) - return; + cutLineRenderer.positionCount = 0; } - - GameObject go = new GameObject($"CutSegment_{startIndex}"); - go.transform.parent = transform.parent; // 放在GearFollower同级方便管理 - - LineRenderer lr = go.AddComponent(); - lr.positionCount = 4; // 画一个四点的矩形条表示卷边 - lr.material = cutEdgeMaterial != null ? cutEdgeMaterial : new Material(Shader.Find("Sprites/Default")); - lr.widthCurve = AnimationCurve.Constant(0, 1, 0.05f); - lr.numCapVertices = 4; - lr.useWorldSpace = true; - - CutSegment newSeg = new CutSegment - { - startIndex = startIndex, - timeElapsed = 0f, - lineRenderer = lr - }; - - cutSegments.Add(newSeg); } - // 每帧更新所有切割段动画 - private void UpdateCutSegments() + private void AddCutPoint(Vector3 point) { - for (int i = cutSegments.Count - 1; i >= 0; i--) + if (cutLinePoints.Count == 0 || Vector3.Distance(cutLinePoints[cutLinePoints.Count - 1], point) > 0.01f) { - var seg = cutSegments[i]; - seg.timeElapsed += Time.deltaTime; - - float expandT = Mathf.Clamp01(seg.timeElapsed / cutEdgeExpandDuration); - float coolT = Mathf.Clamp01((seg.timeElapsed - cutEdgeExpandDuration) / cutEdgeCoolDuration); - - // 获取该段两个端点 - int nextIndex = WrapIndex(seg.startIndex + 1); - Vector2 p1 = edgePoints[seg.startIndex]; - Vector2 p2 = edgePoints[nextIndex]; - - Vector2 segmentDir = (p2 - p1).normalized; - Vector2 normal = new Vector2(-segmentDir.y, segmentDir.x); - - float offset = cutEdgeMaxOffset * expandT; - - // 四个点形成的卷边矩形 - Vector3[] positions = new Vector3[4]; - // 主边 - positions[0] = p1; - positions[1] = p2; - // 卷边偏移边(沿法线方向偏移offset) - positions[2] = p2 + normal * offset; - positions[3] = p1 + normal * offset; - - seg.lineRenderer.positionCount = 4; - seg.lineRenderer.SetPositions(positions); - - // 颜色渐变 - Color color = cutEdgeColorGradient.Evaluate(1f - coolT); - seg.lineRenderer.startColor = color; - seg.lineRenderer.endColor = color; - - // 冷却结束删除该段 - if (seg.timeElapsed > cutEdgeExpandDuration + cutEdgeCoolDuration) + cutLinePoints.Add(point); + if (cutLineRenderer != null) { - Destroy(seg.lineRenderer.gameObject); - cutSegments.RemoveAt(i); + cutLineRenderer.positionCount = cutLinePoints.Count; + cutLineRenderer.SetPositions(cutLinePoints.ToArray()); } } } + private void EndCutting() + { + isCutting = false; + // 这里可以添加切割结束后的淡出效果等 + } + private int FindNearestEdgePointIndex(Vector2 pos, out float minDist) { int nearestIndex = 0;