using System.Collections.Generic; using System.Linq; using UnityEngine; public class GearFollower : MonoBehaviour { [Header("参数")] public SpriteRenderer targetSpriteRenderer; // 目标图片 public float snapDistance = 0.3f; // 距离边缘多少时吸附 public float detachDistance = 1.0f; // 超过多少距离脱离牵引 public float tractionSpeed = 5f; // 牵引时沿边缘移动速度 public float rotationSpeed = 360f; // 齿轮旋转速度(度/秒) public float smoothTime = 0.1f; // 平滑过渡时间 public float edgeSmoothTime = 0.02f; // 边缘移动平滑时间 public float edgeScaleFactor = 1.1f; // 边缘点采样放大倍数 public int interpolationPoints = 0; // 每两个采样点之间插入的点数 public float cutTimeThreshold = 0.5f; // 切割时间阈值 public Color normalColor = Color.white; // 正常状态颜色 public Color tractionColor = Color.yellow; // 牵引状态颜色 public Color pathColor = new Color(1f, 1f, 1f, 0.3f); // 路径颜色 public Color cutPathColor = new Color(0f, 1f, 0f, 0.5f); // 已切割路径颜色 public float pathWidth = 0.05f; // 路径宽度 [Header("反馈效果")] public float speedMultiplier = 1f; // 切割时速度倍率 private List edgePoints; // 边缘路径点 private int currentEdgeIndex = 0; // 当前吸附边缘点索引 private SpriteRenderer gearSpriteRenderer; // 齿轮的SpriteRenderer private Vector2 currentVelocity; // 用于平滑移动 private float currentRotationVelocity; // 用于平滑旋转 private LineRenderer pathRenderer; // 路径渲染器 private float currentPathProgress; // 当前路径进度 private List cutStates; // 记录每个点是否被切割 private List cutTimes; // 记录每个点的切割时间 private float currentCutTime; // 当前点的切割时间 private enum State { Follow, Traction } private State currentState = State.Follow; private Camera mainCamera; private Vector2 targetEdgePosition; // 目标边缘位置 private float currentEdgeProgress; // 当前边缘移动进度 private Vector2 imageCenter; // 图片中心点 private float imageRadius; // 图片半径 private Vector2 lastMousePosition; // 上一帧的鼠标位置 private float currentSpeed; // 当前速度 private float targetSpeed; // 目标速度 private float screenShakeTimer; // 屏幕震动计时器 private ParticleSystem cutParticleSystem; // 切割粒子系统 private AudioSource cutAudioSource; // 切割音效 void Start() { mainCamera = Camera.main; gearSpriteRenderer = GetComponent(); if (gearSpriteRenderer == null) { Debug.LogError("齿轮对象缺少SpriteRenderer组件"); } // 计算图片中心点和半径 if (targetSpriteRenderer != null) { imageCenter = targetSpriteRenderer.bounds.center; // 使用bounds的size计算半径(取较大的值) imageRadius = Mathf.Max(targetSpriteRenderer.bounds.size.x, targetSpriteRenderer.bounds.size.y) * 0.5f; } // 添加并设置LineRenderer pathRenderer = gameObject.AddComponent(); pathRenderer.startWidth = pathWidth; pathRenderer.endWidth = pathWidth; pathRenderer.startColor = pathColor; pathRenderer.endColor = pathColor; pathRenderer.material = new Material(Shader.Find("Sprites/Default")); pathRenderer.sortingOrder = 0; pathRenderer.enabled = false; LoadEdgePointsFromSprite(); } void Update() { Vector2 mouseWorldPos = mainCamera.ScreenToWorldPoint(Input.mousePosition); Vector2 mouseDelta = mouseWorldPos - lastMousePosition; lastMousePosition = mouseWorldPos; switch (currentState) { case State.Follow: UpdateFollowState(mouseWorldPos); break; case State.Traction: UpdateTractionState(mouseWorldPos); break; } // 更新齿轮旋转 UpdateGearRotation(); // 更新视觉反馈 UpdateVisualFeedback(); // 更新路径显示 UpdatePathDisplay(); } // 更新齿轮旋转 private void UpdateGearRotation() { if (currentState == State.Traction && Input.GetMouseButton(0)) { // 在牵引状态下按住左键时旋转 transform.Rotate(0, 0, rotationSpeed * Time.deltaTime); // 更新当前点的切割时间 currentCutTime += Time.deltaTime; if (currentCutTime >= cutTimeThreshold) { if (!cutStates[currentEdgeIndex]) { // 触发切割效果 //TriggerCutEffects(); } cutStates[currentEdgeIndex] = true; cutTimes[currentEdgeIndex] = currentCutTime; } } else { currentCutTime = 0f; } } // 更新视觉反馈 private void UpdateVisualFeedback() { if (gearSpriteRenderer != null) { gearSpriteRenderer.color = currentState == State.Traction ? tractionColor : normalColor; } } // 从Sprite的PhysicsShape获取边缘点,构成闭环路径 private void LoadEdgePointsFromSprite() { List originalPoints = 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; // 转换为世界坐标并应用放大 originalPoints = shapePoints .Select(p => { Vector2 dirFromCenter = p - center; Vector2 scaledPoint = center + dirFromCenter * edgeScaleFactor; return (Vector2)targetSpriteRenderer.transform.TransformPoint(scaledPoint); }) .ToList(); // 确保首尾连通 if (originalPoints[0] != originalPoints[originalPoints.Count - 1]) { originalPoints.Add(originalPoints[0]); } // 生成插值点 edgePoints = new List(); for (int i = 0; i < originalPoints.Count - 1; i++) { Vector2 start = originalPoints[i]; Vector2 end = originalPoints[i + 1]; // 添加起始点 edgePoints.Add(start); // 在两点之间插入插值点 for (int j = 1; j <= interpolationPoints; j++) { float t = j / (float)(interpolationPoints + 1); Vector2 interpolatedPoint = Vector2.Lerp(start, end, t); edgePoints.Add(interpolatedPoint); } } // 添加最后一个点 edgePoints.Add(originalPoints[originalPoints.Count - 1]); // 初始化切割状态数组 cutStates = new List(new bool[edgePoints.Count]); cutTimes = new List(new float[edgePoints.Count]); // 更新路径渲染器 UpdatePathRenderer(); } private void UpdatePathRenderer() { if (pathRenderer != null) { pathRenderer.positionCount = edgePoints.Count; for (int i = 0; i < edgePoints.Count; i++) { pathRenderer.SetPosition(i, new Vector3(edgePoints[i].x, edgePoints[i].y, 0)); } } } // 跟随模式逻辑 private void UpdateFollowState(Vector2 mousePos) { // 使用平滑移动 transform.position = Vector2.SmoothDamp(transform.position, mousePos, ref currentVelocity, smoothTime); // 检测距离最近边缘点是否在snapDistance内,进入牵引 int nearestIndex = FindNearestEdgePointIndex(mousePos, out float dist); if (dist < snapDistance) { currentEdgeIndex = nearestIndex; currentState = State.Traction; currentPathProgress = 0f; // 立即吸附到最近的边缘点 transform.position = edgePoints[currentEdgeIndex]; if (gearSpriteRenderer != null) { gearSpriteRenderer.sortingOrder = 1; } pathRenderer.enabled = true; } } // 牵引模式逻辑 private void UpdateTractionState(Vector2 mousePos) { Vector2 gearPos = (Vector2)transform.position; // 计算鼠标到齿轮的距离 float distToGear = Vector2.Distance(mousePos, gearPos); // 计算鼠标到图片中心的距离 float distToCenter = Vector2.Distance(mousePos, imageCenter); // 如果鼠标在图片内部(距离中心小于半径),或者距离齿轮太近,则不脱离吸附 if (distToGear > detachDistance && distToCenter > imageRadius) { currentState = State.Follow; if (gearSpriteRenderer != null) { gearSpriteRenderer.sortingOrder = 5; } pathRenderer.enabled = false; return; } // 判断是否按住左键 if (!Input.GetMouseButton(0)) { // 未按键,齿轮固定在当前位置 return; } // 沿路径移动 MoveAlongPath(mousePos); } // 沿路径移动的逻辑 private void MoveAlongPath(Vector2 mousePos) { // 计算鼠标到当前齿轮位置的距离 float distToGear = Vector2.Distance(mousePos, transform.position); // 获取当前段落的起点和终点 Vector2 startPoint = edgePoints[currentEdgeIndex]; Vector2 endPoint = edgePoints[(currentEdgeIndex + 1) % edgePoints.Count]; // 计算鼠标到当前段落两端点的距离 float distToStart = Vector2.Distance(mousePos, startPoint); float distToEnd = Vector2.Distance(mousePos, endPoint); // 计算移动方向 float moveDirection = 0f; // 获取鼠标相对于当前段落的向量 Vector2 mouseToStart = mousePos - startPoint; Vector2 segmentVector = endPoint - startPoint; // 计算叉积来判断方向 float crossProduct = mouseToStart.x * segmentVector.y - mouseToStart.y * segmentVector.x; // 根据叉积的符号决定移动方向 moveDirection = crossProduct > 0 ? -1f : 1f; // 计算移动速度 float moveSpeed = tractionSpeed * (cutStates[currentEdgeIndex] ? speedMultiplier : 1f); // 使用固定速度更新移动 float maxProgressChange = moveSpeed * Time.deltaTime; float targetProgress = currentPathProgress + moveDirection * maxProgressChange; // 处理段落切换 if (targetProgress >= 1f) { currentEdgeIndex = (currentEdgeIndex + 1) % edgePoints.Count; targetProgress = 0f; currentCutTime = 0f; // 重置切割时间 } else if (targetProgress <= 0f) { // 修改逆时针移动的处理逻辑 currentEdgeIndex = (currentEdgeIndex - 1 + edgePoints.Count) % edgePoints.Count; targetProgress = 1f; currentCutTime = 0f; // 重置切割时间 // 确保不会出现负值 if (currentEdgeIndex < 0) { currentEdgeIndex = edgePoints.Count - 1; } } // 更新进度 currentPathProgress = targetProgress; // 计算目标位置 startPoint = edgePoints[currentEdgeIndex]; endPoint = edgePoints[(currentEdgeIndex + 1) % edgePoints.Count]; Vector2 targetPos = Vector2.Lerp(startPoint, endPoint, currentPathProgress); // 使用平滑移动 transform.position = Vector2.SmoothDamp(transform.position, targetPos, ref currentVelocity, edgeSmoothTime); } // 找到距离pos最近的边缘点索引和距离 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 void UpdatePathDisplay() { if (pathRenderer != null && pathRenderer.enabled) { for (int i = 0; i < edgePoints.Count; i++) { Color segmentColor = cutStates[i] ? cutPathColor : pathColor; pathRenderer.startColor = segmentColor; pathRenderer.endColor = segmentColor; } } } }