184 lines
5.1 KiB
C#
184 lines
5.1 KiB
C#
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 = 2f;
|
|
|
|
private List<Vector2> edgePoints;
|
|
private int currentEdgeIndex = 0;
|
|
private int tractionDirection = 1;
|
|
|
|
private enum State { Follow, Traction }
|
|
private State currentState = State.Follow;
|
|
|
|
private Camera mainCamera;
|
|
|
|
void Start()
|
|
{
|
|
mainCamera = Camera.main;
|
|
LoadEdgePointsFromSprite();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
Vector2 mouseWorldPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
|
|
|
|
if (Input.GetMouseButton(0))
|
|
{
|
|
transform.Rotate(Vector3.forward, -360f * Time.deltaTime);
|
|
}
|
|
|
|
switch (currentState)
|
|
{
|
|
case State.Follow:
|
|
UpdateFollowState(mouseWorldPos);
|
|
break;
|
|
case State.Traction:
|
|
UpdateTractionState(mouseWorldPos);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
edgePoints = shapePoints
|
|
.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;
|
|
|
|
int nearestIndex = FindNearestEdgePointIndex(mousePos, out float dist);
|
|
if (dist < snapDistance)
|
|
{
|
|
currentEdgeIndex = nearestIndex;
|
|
|
|
// 判断应该向前还是向后吸附
|
|
Vector2 prev = edgePoints[WrapIndex(currentEdgeIndex - 1)];
|
|
Vector2 next = edgePoints[WrapIndex(currentEdgeIndex + 1)];
|
|
|
|
float distToPrev = Vector2.Distance(mousePos, prev);
|
|
float distToNext = Vector2.Distance(mousePos, next);
|
|
|
|
tractionDirection = (distToNext < distToPrev) ? 1 : -1;
|
|
|
|
transform.position = edgePoints[currentEdgeIndex];
|
|
currentState = State.Traction;
|
|
}
|
|
}
|
|
|
|
private void UpdateTractionState(Vector2 mousePos)
|
|
{
|
|
Vector2 gearPos = transform.position;
|
|
|
|
if (Vector2.Distance(mousePos, gearPos) > detachDistance)
|
|
{
|
|
currentState = State.Follow;
|
|
return;
|
|
}
|
|
|
|
if (!Input.GetMouseButton(0))
|
|
{
|
|
// 不按左键不移动
|
|
return;
|
|
}
|
|
|
|
int nextIndex = WrapIndex(currentEdgeIndex + tractionDirection);
|
|
Vector2 from = edgePoints[currentEdgeIndex];
|
|
Vector2 to = edgePoints[nextIndex];
|
|
|
|
// 判断鼠标是否在当前前进方向上,否则允许换方向
|
|
float forwardDot = Vector2.Dot((to - from).normalized, (mousePos - from).normalized);
|
|
if (forwardDot < 0) // 鼠标在反方向
|
|
{
|
|
// 允许方向切换
|
|
tractionDirection *= -1;
|
|
nextIndex = WrapIndex(currentEdgeIndex + tractionDirection);
|
|
from = edgePoints[currentEdgeIndex];
|
|
to = edgePoints[nextIndex];
|
|
}
|
|
|
|
Vector2 targetPoint = GetClosestPointOnSegment(from, to, mousePos);
|
|
Vector2 moveDir = (targetPoint - gearPos);
|
|
float distance = moveDir.magnitude;
|
|
|
|
float maxStep = tractionSpeed * Time.deltaTime;
|
|
|
|
if (distance <= maxStep)
|
|
{
|
|
transform.position = targetPoint;
|
|
|
|
// 如果到达段末,前进一段
|
|
if (Vector2.Distance(targetPoint, to) < 0.01f)
|
|
{
|
|
currentEdgeIndex = nextIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
transform.position += (Vector3)(moveDir.normalized * maxStep);
|
|
}
|
|
}
|
|
|
|
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 GetClosestPointOnSegment(Vector2 a, Vector2 b, Vector2 p)
|
|
{
|
|
Vector2 ab = b - a;
|
|
float t = Vector2.Dot(p - a, ab) / ab.sqrMagnitude;
|
|
t = Mathf.Clamp01(t);
|
|
return a + ab * t;
|
|
}
|
|
|
|
private int WrapIndex(int index)
|
|
{
|
|
if (index < 0) return edgePoints.Count - 2;
|
|
if (index >= edgePoints.Count - 1) return 0;
|
|
return index;
|
|
}
|
|
}
|