This commit is contained in:
2025-05-17 01:07:08 +08:00
parent c69da76dbd
commit 9a88f6bee9
@@ -2,69 +2,26 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
[RequireComponent(typeof(LineRenderer))]
public class GearFollowerWithVisualCut : MonoBehaviour
public class GearFollower : 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 SpriteRenderer targetSpriteRenderer; // 目标图片
public float snapDistance = 0.3f; // 距离边缘多少时吸附
public float detachDistance = 1.0f; // 超过多少距离脱离牵引
public float tractionSpeed = 2f; // 牵引时沿边缘移动速度
public Color normalEdgeColor = Color.white;
public Color cutEdgeColor = Color.yellow;
private List<Vector2> edgePoints; // 边缘路径点
private int currentEdgeIndex = 0; // 当前吸附边缘点索引
private List<Vector2> edgePoints;
private int currentEdgeIndex = 0;
private SpriteRenderer gearSpriteRenderer;
private Camera mainCamera;
private enum State { Follow, Attract, Traction }
private enum State { Follow, Traction }
private State currentState = State.Follow;
private bool[] cutSegments;
private LineRenderer edgeLineRenderer; // 绘制全部边缘
private LineRenderer cutLineRenderer; // 绘制切割线段
private Camera mainCamera;
void Start()
{
mainCamera = Camera.main;
gearSpriteRenderer = GetComponent<SpriteRenderer>();
LoadEdgePointsFromSprite();
cutSegments = new bool[edgePoints.Count - 1];
// 设置主边缘线条渲染器
edgeLineRenderer = GetComponent<LineRenderer>();
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<LineRenderer>();
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()
@@ -76,36 +33,13 @@ public class GearFollowerWithVisualCut : MonoBehaviour
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;
}
}
// 从Sprite的PhysicsShape获取边缘点,构成闭环路径
private void LoadEdgePointsFromSprite()
{
edgePoints = new List<Vector2>();
@@ -124,138 +58,96 @@ public class GearFollowerWithVisualCut : MonoBehaviour
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);
})
.Select(p => (Vector2)targetSpriteRenderer.transform.TransformPoint(p))
.ToList();
// 假设形状是闭合的,确保首尾连通
if (edgePoints[0] != edgePoints[edgePoints.Count - 1])
{
edgePoints.Add(edgePoints[0]);
}
}
// 跟随模式逻辑
private void UpdateFollowState(Vector2 mousePos)
{
transform.position = mousePos;
// 检测距离最近边缘点是否在snapDistance内,进入牵引
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;
// 吸附齿轮到边缘点位置
transform.position = edgePoints[currentEdgeIndex];
}
}
// 牵引模式逻辑
private void UpdateTractionState(Vector2 mousePos)
{
if (Vector2.Distance(mousePos, transform.position) > detachDistance)
Vector2 gearPos = (Vector2)transform.position;
// 计算鼠标和齿轮距离,超过detachDistance则脱离牵引
if (Vector2.Distance(mousePos, gearPos) > detachDistance)
{
currentState = State.Follow;
if (gearSpriteRenderer != null)
gearSpriteRenderer.sortingOrder = 5;
return;
}
if (Input.GetMouseButton(0))
// 判断是否按住左键
if (!Input.GetMouseButton(0))
{
MoveAlongEdge(mousePos);
// 未按键,齿轮固定在边缘点
transform.position = edgePoints[currentEdgeIndex];
}
else
{
// 按住左键,齿轮沿边缘点链移动
// 找到当前边缘点前后点索引,注意闭环处理
int prevIndex = (currentEdgeIndex - 1 + edgePoints.Count) % edgePoints.Count;
int nextIndex = (currentEdgeIndex + 1) % edgePoints.Count;
Vector2 prevPoint = edgePoints[prevIndex];
Vector2 currPoint = edgePoints[currentEdgeIndex];
Vector2 nextPoint = edgePoints[nextIndex];
// 计算向前和向后移动方向
Vector2 dirToPrev = (prevPoint - currPoint).normalized;
Vector2 dirToNext = (nextPoint - currPoint).normalized;
// 预测如果沿前后方向移动一个小步长后到鼠标的距离
float stepDistance = tractionSpeed * Time.deltaTime;
Vector2 posIfMovePrev = currPoint + dirToPrev * stepDistance;
Vector2 posIfMoveNext = currPoint + dirToNext * stepDistance;
float distPrev = Vector2.Distance(posIfMovePrev, mousePos);
float distNext = Vector2.Distance(posIfMoveNext, mousePos);
// 选择距离鼠标更近的方向前进
if (distPrev < distNext)
{
// 向前移动
currentEdgeIndex = prevIndex;
transform.position = prevPoint;
}
else
{
// 向后移动
currentEdgeIndex = nextIndex;
transform.position = nextPoint;
}
}
}
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--;
}
}
// 找到距离pos最近的边缘点索引和距离
private int FindNearestEdgePointIndex(Vector2 pos, out float minDist)
{
int nearestIndex = 0;
@@ -272,51 +164,4 @@ public class GearFollowerWithVisualCut : MonoBehaviour
}
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;
}
}
}