323 lines
9.5 KiB
C#
323 lines
9.5 KiB
C#
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<Vector2> 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<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()
|
||
{
|
||
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<Vector2>();
|
||
if (targetSpriteRenderer == null)
|
||
{
|
||
Debug.LogError("targetSpriteRenderer 为空");
|
||
return;
|
||
}
|
||
|
||
List<Vector2> shapePoints = new List<Vector2>();
|
||
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;
|
||
}
|
||
}
|
||
}
|