using System.Collections.Generic; using Shapes; using UnityEngine; public class GearFollower : MonoBehaviour { [Header("参数")] public SpriteRenderer targetSpriteRenderer; public float snapDistance = 0.3f; public float detachDistance = 1.0f; public float tractionSpeed = 2f; public float edgePointsScale = 1.2f; // 边缘点缩放倍率 [Header("切割轨迹")] public LineRenderer cutLineRenderer; public float cutLineWidth = 0.1f; public float noiseAmount = 0.05f; // 毛刺偏移幅度 public float segmentDuration = 4f; // 线段持续时间 private List edgePoints; private List originalEdgePoints; // 存储原始边缘点 private int currentEdgeIndex = 0; private int tractionDirection = 1; private enum State { Follow, Traction } private State currentState = State.Follow; private Camera mainCamera; private List cutLinePoints = new List(); private bool isCutting = false; private float directionSwitchCooldown = 0.2f; private float directionSwitchTimer = 0f; private class TimedPoint { public Vector3 position; public float timeAdded; public TimedPoint(Vector3 pos, float time) { position = pos; timeAdded = time; } } private List timedCutPoints = new List(); public float cutLineDelay = 3f; // 延迟几秒才显示轨迹点 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; // 线头不圆滑,显锐利 cutLineRenderer.sortingOrder = 1; } } void Update() { Vector2 mouseWorldPos = mainCamera.ScreenToWorldPoint(Input.mousePosition); if (Input.GetMouseButton(0)) { transform.Rotate(Vector3.forward, -360f * Time.deltaTime); } switch (currentState) { case State.Follow: UpdateFollowState(mouseWorldPos); break; case State.Traction: UpdateTractionState(mouseWorldPos); break; } UpdateCutLineRenderer(); } private void LoadEdgePointsFromSprite() { edgePoints = new List(); originalEdgePoints = new List(); // 初始化原始边缘点列表 if (targetSpriteRenderer == null) { Debug.LogError("targetSpriteRenderer 为空"); return; } List shapePoints = new List(); targetSpriteRenderer.sprite.GetPhysicsShape(0, shapePoints); if (shapePoints.Count == 0) { Debug.LogError("目标Sprite没有物理形状点"); return; } float desiredSegmentLength = 0.1f; edgePoints.Clear(); originalEdgePoints.Clear(); // 计算中心点 Vector2 center = Vector2.zero; foreach (Vector2 point in shapePoints) { center += point; } center /= shapePoints.Count; for (int i = 0; i < shapePoints.Count; i++) { Vector2 p1 = targetSpriteRenderer.transform.TransformPoint(shapePoints[i]); Vector2 p2 = targetSpriteRenderer.transform.TransformPoint(shapePoints[(i + 1) % shapePoints.Count]); // 计算缩放后的点 Vector2 scaledP1 = center + (p1 - center) * edgePointsScale; Vector2 scaledP2 = center + (p2 - center) * edgePointsScale; float segmentLength = Vector2.Distance(scaledP1, scaledP2); int steps = Mathf.Max(1, Mathf.CeilToInt(segmentLength / desiredSegmentLength)); for (int s = 0; s < steps; s++) { float t = (float)s / steps; Vector2 interpolated = Vector2.Lerp(scaledP1, scaledP2, t); Vector2 originalInterpolated = Vector2.Lerp(p1, p2, t); edgePoints.Add(interpolated); originalEdgePoints.Add(originalInterpolated); } } if ((edgePoints[0] - edgePoints[edgePoints.Count - 1]).sqrMagnitude > 0.0001f) { edgePoints.Add(edgePoints[0]); originalEdgePoints.Add(originalEdgePoints[0]); } Debug.Log($"边缘点数量:{edgePoints.Count}"); } private void UpdateFollowState(Vector2 mousePos) { transform.position = mousePos; int nearestIndex = FindNearestEdgePointIndex(mousePos, out float dist); if (dist < snapDistance) { currentEdgeIndex = nearestIndex; Vector2 prev = edgePoints[WrapIndex(currentEdgeIndex - 1)]; Vector2 next = edgePoints[WrapIndex(currentEdgeIndex + 1)]; float distToPrev = Vector2.Distance(mousePos, prev); float distToNext = Vector2.Distance(mousePos, next); tractionDirection = (distToNext < distToPrev) ? 1 : -1; transform.position = edgePoints[currentEdgeIndex]; currentState = State.Traction; } } private void UpdateTractionState(Vector2 mousePos) { Vector2 gearPos = transform.position; 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); if (Mathf.Abs(angle) > 120f) { directionSwitchTimer += Time.deltaTime; if (directionSwitchTimer > directionSwitchCooldown) { tractionDirection *= -1; directionSwitchTimer = 0f; nextIndex = WrapIndex(currentEdgeIndex + tractionDirection); from = edgePoints[currentEdgeIndex]; to = edgePoints[nextIndex]; forward = (to - from).normalized; } } else { directionSwitchTimer = 0f; } Vector2 targetPoint = GetClosestPointOnSegment(from, to, mousePos); Vector2 moveDir = (targetPoint - gearPos); float distance = moveDir.magnitude; float maxStep = tractionSpeed * Time.deltaTime; if (distance <= maxStep) { transform.position = targetPoint; if (Vector2.Distance(targetPoint, to) < 0.01f) { currentEdgeIndex = nextIndex; } } else { transform.position += (Vector3)(moveDir.normalized * maxStep); } // 使用原始边缘点来记录切割线 Vector2 originalFrom = originalEdgePoints[currentEdgeIndex]; Vector2 originalTo = originalEdgePoints[nextIndex]; Vector2 originalTargetPoint = GetClosestPointOnSegment(originalFrom, originalTo, transform.position); // 记录切割点(带噪声模拟毛糙) Vector3 noisyPoint = originalTargetPoint; noisyPoint.x += Random.Range(-noiseAmount, noiseAmount); noisyPoint.y += Random.Range(-noiseAmount, noiseAmount); AddCutPoint(noisyPoint); } private void StartCutting() { isCutting = true; //cutLinePoints.Clear(); if (cutLineRenderer != null) { cutLineRenderer.positionCount = 0; } } private void AddCutPoint(Vector3 point) { if (timedCutPoints.Count == 0 || Vector3.Distance(timedCutPoints[timedCutPoints.Count - 1].position, point) > 0.05f) { timedCutPoints.Add(new TimedPoint(point, Time.time)); // 立即创建片段 if (timedCutPoints.Count >= 2) { Vector3 prev = timedCutPoints[timedCutPoints.Count - 2].position; Vector3 curr = timedCutPoints[timedCutPoints.Count - 1].position; CreateSegment(prev, curr); } } } private void UpdateCutLineRenderer() { if (cutLineRenderer == null) return; float currentTime = Time.time; List visiblePoints = new List(); foreach (var timedPoint in timedCutPoints) { if (currentTime - timedPoint.timeAdded >= cutLineDelay) { visiblePoints.Add(timedPoint.position); } else { // 这里的点还没到延迟时间,不显示 } } cutLineRenderer.positionCount = visiblePoints.Count; if (visiblePoints.Count > 0) cutLineRenderer.SetPositions(visiblePoints.ToArray()); } private void EndCutting() { isCutting = false; // 这里可以添加切割结束后的淡出效果等 } private int FindNearestEdgePointIndex(Vector2 pos, out float minDist) { int nearestIndex = 0; minDist = float.MaxValue; for (int i = 0; i < edgePoints.Count; i++) { float dist = Vector2.Distance(pos, edgePoints[i]); if (dist < minDist) { minDist = dist; nearestIndex = i; } } return nearestIndex; } private Vector2 GetClosestPointOnSegment(Vector2 a, Vector2 b, Vector2 p) { Vector2 ab = b - a; float t = Vector2.Dot(p - a, ab) / ab.sqrMagnitude; t = Mathf.Clamp01(t); return a + ab * t; } private int WrapIndex(int index) { if (index < 0) return edgePoints.Count - 2; if (index >= edgePoints.Count - 1) return 0; return index; } private void CreateSegment(Vector3 from, Vector3 to) { GameObject segObj = new GameObject("HeatLineSegment"); segObj.transform.parent = this.transform; LineRenderer segLine = segObj.AddComponent(); segLine.positionCount = 2; segLine.SetPosition(0, from); segLine.SetPosition(1, to); segLine.sortingOrder = 4; segLine.material = new Material(Shader.Find("Sprites/Default")); segLine.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth); segLine.numCapVertices = 0; var segmentController = segObj.AddComponent(); segmentController.duration = segmentDuration; } }