354 lines
11 KiB
C#
354 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using Shapes;
|
|
using UnityEngine;
|
|
|
|
public class GearFollower : MonoBehaviour
|
|
{
|
|
[Header("参数")]
|
|
public SpriteRenderer targetSpriteRenderer;
|
|
public float snapDistance = 0.3f;
|
|
public float detachDistance = 1.0f;
|
|
public float tractionSpeed = 2f;
|
|
public float edgePointsScale = 1.2f; // 边缘点缩放倍率
|
|
|
|
[Header("切割轨迹")]
|
|
public LineRenderer cutLineRenderer;
|
|
|
|
public float cutLineWidth = 0.1f;
|
|
public float noiseAmount = 0.05f; // 毛刺偏移幅度
|
|
|
|
public float segmentDuration = 4f; // 线段持续时间
|
|
|
|
private List<Vector2> edgePoints;
|
|
private List<Vector2> originalEdgePoints; // 存储原始边缘点
|
|
private int currentEdgeIndex = 0;
|
|
private int tractionDirection = 1;
|
|
|
|
private enum State { Follow, Traction }
|
|
private State currentState = State.Follow;
|
|
|
|
private Camera mainCamera;
|
|
|
|
private List<Vector3> cutLinePoints = new List<Vector3>();
|
|
private bool isCutting = false;
|
|
|
|
private float directionSwitchCooldown = 0.2f;
|
|
private float directionSwitchTimer = 0f;
|
|
|
|
private class TimedPoint
|
|
{
|
|
public Vector3 position;
|
|
public float timeAdded;
|
|
public TimedPoint(Vector3 pos, float time)
|
|
{
|
|
position = pos;
|
|
timeAdded = time;
|
|
}
|
|
}
|
|
|
|
private List<TimedPoint> timedCutPoints = new List<TimedPoint>();
|
|
public float cutLineDelay = 3f; // 延迟几秒才显示轨迹点
|
|
|
|
void Start()
|
|
{
|
|
mainCamera = Camera.main;
|
|
LoadEdgePointsFromSprite();
|
|
|
|
if (cutLineRenderer != null)
|
|
{
|
|
cutLineRenderer.positionCount = 0;
|
|
cutLineRenderer.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth);
|
|
cutLineRenderer.material = new Material(Shader.Find("Sprites/Default"));
|
|
cutLineRenderer.numCapVertices = 0; // 线头不圆滑,显锐利
|
|
cutLineRenderer.sortingOrder = 1;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
UpdateCutLineRenderer();
|
|
}
|
|
|
|
private void LoadEdgePointsFromSprite()
|
|
{
|
|
edgePoints = new List<Vector2>();
|
|
originalEdgePoints = 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;
|
|
}
|
|
|
|
float desiredSegmentLength = 0.1f;
|
|
|
|
edgePoints.Clear();
|
|
originalEdgePoints.Clear();
|
|
|
|
// 计算中心点
|
|
Vector2 center = Vector2.zero;
|
|
foreach (Vector2 point in shapePoints)
|
|
{
|
|
center += point;
|
|
}
|
|
center /= shapePoints.Count;
|
|
|
|
for (int i = 0; i < shapePoints.Count; i++)
|
|
{
|
|
Vector2 p1 = targetSpriteRenderer.transform.TransformPoint(shapePoints[i]);
|
|
Vector2 p2 = targetSpriteRenderer.transform.TransformPoint(shapePoints[(i + 1) % shapePoints.Count]);
|
|
|
|
// 计算缩放后的点
|
|
Vector2 scaledP1 = center + (p1 - center) * edgePointsScale;
|
|
Vector2 scaledP2 = center + (p2 - center) * edgePointsScale;
|
|
|
|
float segmentLength = Vector2.Distance(scaledP1, scaledP2);
|
|
int steps = Mathf.Max(1, Mathf.CeilToInt(segmentLength / desiredSegmentLength));
|
|
|
|
for (int s = 0; s < steps; s++)
|
|
{
|
|
float t = (float)s / steps;
|
|
Vector2 interpolated = Vector2.Lerp(scaledP1, scaledP2, t);
|
|
Vector2 originalInterpolated = Vector2.Lerp(p1, p2, t);
|
|
edgePoints.Add(interpolated);
|
|
originalEdgePoints.Add(originalInterpolated);
|
|
}
|
|
}
|
|
|
|
if ((edgePoints[0] - edgePoints[edgePoints.Count - 1]).sqrMagnitude > 0.0001f)
|
|
{
|
|
edgePoints.Add(edgePoints[0]);
|
|
originalEdgePoints.Add(originalEdgePoints[0]);
|
|
}
|
|
|
|
Debug.Log($"边缘点数量:{edgePoints.Count}");
|
|
}
|
|
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;
|
|
EndCutting();
|
|
return;
|
|
}
|
|
|
|
if (!Input.GetMouseButton(0))
|
|
{
|
|
EndCutting();
|
|
return;
|
|
}
|
|
|
|
if (!isCutting)
|
|
{
|
|
StartCutting();
|
|
}
|
|
|
|
int nextIndex = WrapIndex(currentEdgeIndex + tractionDirection);
|
|
Vector2 from = edgePoints[currentEdgeIndex];
|
|
Vector2 to = edgePoints[nextIndex];
|
|
|
|
Vector2 forward = (to - from).normalized;
|
|
Vector2 toMouse = (mousePos - from).normalized;
|
|
float angle = Vector2.SignedAngle(forward, toMouse);
|
|
|
|
if (Mathf.Abs(angle) > 120f)
|
|
{
|
|
directionSwitchTimer += Time.deltaTime;
|
|
if (directionSwitchTimer > directionSwitchCooldown)
|
|
{
|
|
tractionDirection *= -1;
|
|
directionSwitchTimer = 0f;
|
|
|
|
nextIndex = WrapIndex(currentEdgeIndex + tractionDirection);
|
|
from = edgePoints[currentEdgeIndex];
|
|
to = edgePoints[nextIndex];
|
|
forward = (to - from).normalized;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
directionSwitchTimer = 0f;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 使用原始边缘点来记录切割线
|
|
Vector2 originalFrom = originalEdgePoints[currentEdgeIndex];
|
|
Vector2 originalTo = originalEdgePoints[nextIndex];
|
|
Vector2 originalTargetPoint = GetClosestPointOnSegment(originalFrom, originalTo, transform.position);
|
|
|
|
// 记录切割点(带噪声模拟毛糙)
|
|
Vector3 noisyPoint = originalTargetPoint;
|
|
noisyPoint.x += Random.Range(-noiseAmount, noiseAmount);
|
|
noisyPoint.y += Random.Range(-noiseAmount, noiseAmount);
|
|
AddCutPoint(noisyPoint);
|
|
}
|
|
|
|
private void StartCutting()
|
|
{
|
|
isCutting = true;
|
|
//cutLinePoints.Clear();
|
|
if (cutLineRenderer != null)
|
|
{
|
|
cutLineRenderer.positionCount = 0;
|
|
}
|
|
}
|
|
|
|
private void AddCutPoint(Vector3 point)
|
|
{
|
|
if (timedCutPoints.Count == 0 || Vector3.Distance(timedCutPoints[timedCutPoints.Count - 1].position, point) > 0.05f)
|
|
{
|
|
timedCutPoints.Add(new TimedPoint(point, Time.time));
|
|
|
|
// 立即创建片段
|
|
if (timedCutPoints.Count >= 2)
|
|
{
|
|
Vector3 prev = timedCutPoints[timedCutPoints.Count - 2].position;
|
|
Vector3 curr = timedCutPoints[timedCutPoints.Count - 1].position;
|
|
CreateSegment(prev, curr);
|
|
}
|
|
}
|
|
}
|
|
private void UpdateCutLineRenderer()
|
|
{
|
|
if (cutLineRenderer == null) return;
|
|
|
|
float currentTime = Time.time;
|
|
List<Vector3> visiblePoints = new List<Vector3>();
|
|
|
|
foreach (var timedPoint in timedCutPoints)
|
|
{
|
|
if (currentTime - timedPoint.timeAdded >= cutLineDelay)
|
|
{
|
|
visiblePoints.Add(timedPoint.position);
|
|
}
|
|
else
|
|
{
|
|
// 这里的点还没到延迟时间,不显示
|
|
}
|
|
}
|
|
|
|
cutLineRenderer.positionCount = visiblePoints.Count;
|
|
if (visiblePoints.Count > 0)
|
|
cutLineRenderer.SetPositions(visiblePoints.ToArray());
|
|
}
|
|
|
|
private void EndCutting()
|
|
{
|
|
isCutting = false;
|
|
// 这里可以添加切割结束后的淡出效果等
|
|
}
|
|
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;
|
|
}
|
|
private void CreateSegment(Vector3 from, Vector3 to)
|
|
{
|
|
GameObject segObj = new GameObject("HeatLineSegment");
|
|
segObj.transform.parent = this.transform;
|
|
|
|
LineRenderer segLine = segObj.AddComponent<LineRenderer>();
|
|
segLine.positionCount = 2;
|
|
segLine.SetPosition(0, from);
|
|
segLine.SetPosition(1, to);
|
|
segLine.sortingOrder = 4;
|
|
segLine.material = new Material(Shader.Find("Sprites/Default"));
|
|
segLine.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth);
|
|
segLine.numCapVertices = 0;
|
|
var segmentController = segObj.AddComponent<HeatLineSegmentController>();
|
|
segmentController.duration = segmentDuration;
|
|
}
|
|
}
|