using System.Collections.Generic; using System.Linq; using UnityEngine; [RequireComponent(typeof(LineRenderer))] public class GearFollowerWithVisualCut : MonoBehaviour { [Header("参数")] public SpriteRenderer targetSpriteRenderer; public float snapDistance = 0.3f; public float detachDistance = 1.0f; public float tractionSpeed = 2f; public float rotationSpeed = 360f; public float edgeScaleFactor = 1.1f; public Color normalEdgeColor = Color.white; public Color cutEdgeColor = Color.yellow; private List edgePoints; private int currentEdgeIndex = 0; private SpriteRenderer gearSpriteRenderer; private Camera mainCamera; private enum State { Follow, Attract, Traction } private State currentState = State.Follow; private bool[] cutSegments; private LineRenderer edgeLineRenderer; // 绘制全部边缘 private LineRenderer cutLineRenderer; // 绘制切割线段 void Start() { mainCamera = Camera.main; gearSpriteRenderer = GetComponent(); LoadEdgePointsFromSprite(); cutSegments = new bool[edgePoints.Count - 1]; // 设置主边缘线条渲染器 edgeLineRenderer = GetComponent(); edgeLineRenderer.positionCount = edgePoints.Count; edgeLineRenderer.loop = false; edgeLineRenderer.startWidth = 0.05f; edgeLineRenderer.endWidth = 0.05f; edgeLineRenderer.material = new Material(Shader.Find("Sprites/Default")); edgeLineRenderer.startColor = normalEdgeColor; edgeLineRenderer.endColor = normalEdgeColor; edgeLineRenderer.sortingOrder = 3; for (int i = 0; i < edgePoints.Count; i++) { edgeLineRenderer.SetPosition(i, edgePoints[i]); } // 创建用于切割线段的LineRenderer GameObject cutLineObj = new GameObject("CutLineRenderer"); cutLineObj.transform.parent = this.transform; cutLineRenderer = cutLineObj.AddComponent(); cutLineRenderer.positionCount = 0; cutLineRenderer.loop = false; cutLineRenderer.startWidth = 0.07f; cutLineRenderer.endWidth = 0.07f; cutLineRenderer.material = new Material(Shader.Find("Sprites/Default")); cutLineRenderer.startColor = cutEdgeColor; cutLineRenderer.endColor = cutEdgeColor; cutLineRenderer.sortingOrder = 4; } void Update() { Vector2 mouseWorldPos = mainCamera.ScreenToWorldPoint(Input.mousePosition); switch (currentState) { case State.Follow: UpdateFollowState(mouseWorldPos); break; case State.Attract: UpdateAttractState(mouseWorldPos); break; case State.Traction: UpdateTractionState(mouseWorldPos); break; } UpdateGearRotation(); UpdateVisualFeedback(); UpdateCutLineRenderer(); CheckAllSegmentsCut(); } private void UpdateGearRotation() { if (currentState == State.Traction && Input.GetMouseButton(0)) { transform.Rotate(0, 0, rotationSpeed * Time.deltaTime); } } private void UpdateVisualFeedback() { if (gearSpriteRenderer != null) { gearSpriteRenderer.color = (currentState == State.Traction || currentState == State.Attract) ? cutEdgeColor : normalEdgeColor; } } private void LoadEdgePointsFromSprite() { edgePoints = 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; } Vector2 center = Vector2.zero; foreach (Vector2 point in shapePoints) { center += point; } center /= shapePoints.Count; edgePoints = shapePoints .Select(p => { Vector2 dirFromCenter = p - center; Vector2 scaledPoint = center + dirFromCenter * edgeScaleFactor; return (Vector2)targetSpriteRenderer.transform.TransformPoint(scaledPoint); }) .ToList(); if (edgePoints[0] != edgePoints[edgePoints.Count - 1]) { edgePoints.Add(edgePoints[0]); } } private void UpdateFollowState(Vector2 mousePos) { transform.position = mousePos; int nearestIndex = FindNearestEdgePointIndex(mousePos, out float dist); if (dist < snapDistance) { currentEdgeIndex = nearestIndex; currentState = State.Attract; if (gearSpriteRenderer != null) gearSpriteRenderer.sortingOrder = 1; } } private void UpdateAttractState(Vector2 mousePos) { Vector2 closestPoint = Vector2.zero; float closestDist = float.MaxValue; int closestSegmentStartIndex = 0; for (int i = 0; i < edgePoints.Count - 1; i++) { Vector2 a = edgePoints[i]; Vector2 b = edgePoints[i + 1]; Vector2 projected = ProjectPointOnSegment(mousePos, a, b); float dist = Vector2.Distance(mousePos, projected); if (dist < closestDist) { closestDist = dist; closestPoint = projected; closestSegmentStartIndex = i; } } transform.position = closestPoint; currentEdgeIndex = closestSegmentStartIndex; if (Vector2.Distance(mousePos, closestPoint) > detachDistance) { currentState = State.Follow; if (gearSpriteRenderer != null) gearSpriteRenderer.sortingOrder = 5; return; } if (Input.GetMouseButton(0)) { currentState = State.Traction; } } private void UpdateTractionState(Vector2 mousePos) { if (Vector2.Distance(mousePos, transform.position) > detachDistance) { currentState = State.Follow; if (gearSpriteRenderer != null) gearSpriteRenderer.sortingOrder = 5; return; } if (Input.GetMouseButton(0)) { MoveAlongEdge(mousePos); } } private void MoveAlongEdge(Vector2 mousePos) { // 当前段的两个点 Vector2 a = edgePoints[currentEdgeIndex]; Vector2 b = edgePoints[currentEdgeIndex + 1]; // 投影鼠标位置到当前段 Vector2 projected = ProjectPointOnSegment(mousePos, a, b); // 鼠标离齿轮足够远才移动(模拟牵引力) float tractionThreshold = 0.1f; float distToMouse = Vector2.Distance(transform.position, mousePos); if (distToMouse < tractionThreshold) return; // 移动齿轮朝向投影点 transform.position = Vector2.MoveTowards(transform.position, projected, tractionSpeed * Time.deltaTime); // 标记切割 if (!cutSegments[currentEdgeIndex]) { cutSegments[currentEdgeIndex] = true; Debug.Log($"切割线段 {currentEdgeIndex} ({a}->{b})"); } // 判断是否前进或后退段 float switchThreshold = 0.05f; // 往前走(靠近 b 点且还有下一段) if (Vector2.Distance(projected, b) < switchThreshold && currentEdgeIndex < edgePoints.Count - 2) { currentEdgeIndex++; } // 往后退(靠近 a 点且还有上一段) else if (Vector2.Distance(projected, a) < switchThreshold && currentEdgeIndex > 0) { currentEdgeIndex--; } } 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 ProjectPointOnSegment(Vector2 point, Vector2 a, Vector2 b) { Vector2 ab = b - a; float abLengthSquared = ab.sqrMagnitude; if (abLengthSquared == 0f) return a; float t = Vector2.Dot(point - a, ab) / abLengthSquared; t = Mathf.Clamp01(t); return a + t * ab; } private void UpdateCutLineRenderer() { // 统计所有已切割线段点数 = 2*切割线段数(start和end) int count = cutSegments.Count(cut => cut); if (count == 0) { cutLineRenderer.positionCount = 0; return; } // 每个切割线段2个点 cutLineRenderer.positionCount = count * 2; int index = 0; for (int i = 0; i < cutSegments.Length; i++) { if (cutSegments[i]) { cutLineRenderer.SetPosition(index, edgePoints[i]); cutLineRenderer.SetPosition(index + 1, edgePoints[i + 1]); index += 2; } } } private void CheckAllSegmentsCut() { if (cutSegments == null) return; if (System.Array.TrueForAll(cutSegments, cut => cut)) { Debug.Log("所有边缘线段都已经切割完成!"); enabled = false; } } }