From c9218275d782eecd966e020ca888c8876b8f8333 Mon Sep 17 00:00:00 2001 From: bottlefish <781230111@qq.com> Date: Fri, 16 May 2025 21:01:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=98=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FixSystemNew/BodyModule/GearFollower.cs | 264 +++++++++++++----- 1 file changed, 200 insertions(+), 64 deletions(-) diff --git a/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs b/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs index 8836d620a..7b7581d90 100644 --- a/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs +++ b/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs @@ -8,19 +8,32 @@ public class GearFollower : MonoBehaviour public SpriteRenderer targetSpriteRenderer; // 目标图片 public float snapDistance = 0.3f; // 距离边缘多少时吸附 public float detachDistance = 1.0f; // 超过多少距离脱离牵引 - public float tractionSpeed = 2f; // 牵引时沿边缘移动速度 + public float tractionSpeed = 5f; // 牵引时沿边缘移动速度 public float rotationSpeed = 360f; // 齿轮旋转速度(度/秒) public float smoothTime = 0.1f; // 平滑过渡时间 - public float edgeSmoothTime = 0.05f; // 边缘移动平滑时间 + 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; @@ -29,6 +42,15 @@ public class GearFollower : MonoBehaviour 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() { @@ -38,12 +60,33 @@ public class GearFollower : MonoBehaviour { 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) { @@ -54,11 +97,13 @@ public class GearFollower : MonoBehaviour UpdateTractionState(mouseWorldPos); break; } - // 更新齿轮旋转 UpdateGearRotation(); // 更新视觉反馈 UpdateVisualFeedback(); + // 更新路径显示 + UpdatePathDisplay(); + } // 更新齿轮旋转 @@ -68,6 +113,23 @@ public class GearFollower : MonoBehaviour { // 在牵引状态下按住左键时旋转 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; } } @@ -83,7 +145,7 @@ public class GearFollower : MonoBehaviour // 从Sprite的PhysicsShape获取边缘点,构成闭环路径 private void LoadEdgePointsFromSprite() { - edgePoints = new List(); + List originalPoints = new List(); if (targetSpriteRenderer == null) { Debug.LogError("targetSpriteRenderer 为空"); @@ -108,21 +170,59 @@ public class GearFollower : MonoBehaviour center /= shapePoints.Count; // 转换为世界坐标并应用放大 - edgePoints = shapePoints + originalPoints = 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]) + // 确保首尾连通 + if (originalPoints[0] != originalPoints[originalPoints.Count - 1]) { - edgePoints.Add(edgePoints[0]); + 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)); + } } } @@ -139,11 +239,16 @@ public class GearFollower : MonoBehaviour { currentEdgeIndex = nearestIndex; currentState = State.Traction; - // 切换到牵引状态时,调整渲染顺序 + currentPathProgress = 0f; + + // 立即吸附到最近的边缘点 + transform.position = edgePoints[currentEdgeIndex]; + if (gearSpriteRenderer != null) { gearSpriteRenderer.sortingOrder = 1; } + pathRenderer.enabled = true; } } @@ -152,85 +257,100 @@ public class GearFollower : MonoBehaviour { Vector2 gearPos = (Vector2)transform.position; - // 计算鼠标和齿轮距离,超过detachDistance则脱离牵引 - if (Vector2.Distance(mousePos, gearPos) > detachDistance) + // 计算鼠标到齿轮的距离 + 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)) { - // 未按键,齿轮固定在边缘点 - transform.position = Vector2.SmoothDamp(transform.position, edgePoints[currentEdgeIndex], ref currentVelocity, smoothTime); - } - else - { - // 按住左键,齿轮沿边缘点链移动 - MoveAlongEdge(mousePos); + // 未按键,齿轮固定在当前位置 + return; } + + // 沿路径移动 + MoveAlongPath(mousePos); } - // 沿边缘移动的逻辑 - private void MoveAlongEdge(Vector2 mousePos) + // 沿路径移动的逻辑 + private void MoveAlongPath(Vector2 mousePos) { - // 找到当前边缘点前后点索引,注意闭环处理 - int prevIndex = (currentEdgeIndex - 1 + edgePoints.Count) % edgePoints.Count; - int nextIndex = (currentEdgeIndex + 1) % edgePoints.Count; + // 计算鼠标到当前齿轮位置的距离 + float distToGear = Vector2.Distance(mousePos, transform.position); - Vector2 prevPoint = edgePoints[prevIndex]; - Vector2 currPoint = edgePoints[currentEdgeIndex]; - Vector2 nextPoint = edgePoints[nextIndex]; + // 获取当前段落的起点和终点 + Vector2 startPoint = edgePoints[currentEdgeIndex]; + Vector2 endPoint = edgePoints[(currentEdgeIndex + 1) % edgePoints.Count]; - // 计算向前和向后移动方向 - Vector2 dirToPrev = (prevPoint - currPoint).normalized; - Vector2 dirToNext = (nextPoint - currPoint).normalized; + // 计算鼠标到当前段落两端点的距离 + float distToStart = Vector2.Distance(mousePos, startPoint); + float distToEnd = Vector2.Distance(mousePos, endPoint); - // 计算鼠标到当前点的向量 - Vector2 mouseToCurrent = mousePos - currPoint; + // 计算移动方向 + float moveDirection = 0f; - // 计算鼠标在前后方向上的投影 - float projToPrev = Vector2.Dot(mouseToCurrent, dirToPrev); - float projToNext = Vector2.Dot(mouseToCurrent, dirToNext); + // 获取鼠标相对于当前段落的向量 + Vector2 mouseToStart = mousePos - startPoint; + Vector2 segmentVector = endPoint - startPoint; + + // 计算叉积来判断方向 + float crossProduct = mouseToStart.x * segmentVector.y - mouseToStart.y * segmentVector.x; + + // 根据叉积的符号决定移动方向 + moveDirection = crossProduct > 0 ? -1f : 1f; - // 根据投影决定移动方向 - Vector2 targetDir; - if (Mathf.Abs(projToPrev) > Mathf.Abs(projToNext)) + // 计算移动速度 + float moveSpeed = tractionSpeed * (cutStates[currentEdgeIndex] ? speedMultiplier : 1f); + + // 使用固定速度更新移动 + float maxProgressChange = moveSpeed * Time.deltaTime; + float targetProgress = currentPathProgress + moveDirection * maxProgressChange; + + // 处理段落切换 + if (targetProgress >= 1f) { - targetDir = dirToPrev; - currentEdgeIndex = prevIndex; - targetEdgePosition = prevPoint; + currentEdgeIndex = (currentEdgeIndex + 1) % edgePoints.Count; + targetProgress = 0f; + currentCutTime = 0f; // 重置切割时间 } - else + else if (targetProgress <= 0f) { - targetDir = dirToNext; - currentEdgeIndex = nextIndex; - targetEdgePosition = nextPoint; + // 修改逆时针移动的处理逻辑 + currentEdgeIndex = (currentEdgeIndex - 1 + edgePoints.Count) % edgePoints.Count; + targetProgress = 1f; + currentCutTime = 0f; // 重置切割时间 + + // 确保不会出现负值 + if (currentEdgeIndex < 0) + { + currentEdgeIndex = edgePoints.Count - 1; + } } - // 计算目标位置(在边缘上) - Vector2 currentPos = (Vector2)transform.position; - Vector2 targetPos = Vector2.Lerp(currPoint, targetEdgePosition, currentEdgeProgress); - + // 更新进度 + currentPathProgress = targetProgress; + + // 计算目标位置 + startPoint = edgePoints[currentEdgeIndex]; + endPoint = edgePoints[(currentEdgeIndex + 1) % edgePoints.Count]; + Vector2 targetPos = Vector2.Lerp(startPoint, endPoint, currentPathProgress); + // 使用平滑移动 - transform.position = Vector2.SmoothDamp(currentPos, targetPos, ref currentVelocity, edgeSmoothTime); - - // 更新边缘移动进度 - float moveDistance = tractionSpeed * Time.deltaTime; - float totalDistance = Vector2.Distance(currPoint, targetEdgePosition); - currentEdgeProgress += moveDistance / totalDistance; - - // 如果到达目标点,重置进度 - if (currentEdgeProgress >= 1f) - { - currentEdgeProgress = 0f; - } + transform.position = Vector2.SmoothDamp(transform.position, targetPos, ref currentVelocity, edgeSmoothTime); } // 找到距离pos最近的边缘点索引和距离 @@ -250,4 +370,20 @@ public class GearFollower : MonoBehaviour } 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; + } + } + } + + }