Files
aibis-dream/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs
T

432 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using Shapes;
using UnityEngine;
using UnityEngine.Rendering.Universal;
using UnityEngine.VFX;
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; // 线段持续时间
[Header("震动与光照反馈")]
public Light2D cutLight; // 需要使用 Unity 的 2D 点光源(URP
public float lightPulseSpeed = 5f;
public float lightMaxIntensity = 1.5f;
public float shakeAmount = 0.05f;
[Header("VFX火花")]
public VisualEffect sparkVFX; // 关联Spark VFX Graph组件
public float maxEmissionRate = 50f; // 最大发射速率
public float emissionChangeSpeed = 100f; // 发射速率变化速度
private float currentEmissionRate = 0f;
private Vector3 originalLocalPosition;
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 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;
private Vector3 shakeOffset = Vector3.zero;
private Vector3 targetPosition = Vector3.zero;
private bool isInTractionMode = false;
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;
}
if (cutLight != null)
{
cutLight.intensity = 0f;
cutLight.enabled = false;
}
}
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();
UpdateVisualFeedback();
}
private void UpdateVisualFeedback()
{
if (isInTractionMode && isCutting)
{
// 原始小幅震动
Vector2 baseShake = Random.insideUnitCircle * shakeAmount;
shakeOffset = (Vector3)baseShake;
transform.position = targetPosition + shakeOffset;
// 根据震动幅度控制灯光强度
if (cutLight != null)
{
cutLight.enabled = true;
float intensity = shakeOffset.magnitude / shakeAmount; // 得到 [0,1] 比例
cutLight.intensity = intensity * lightMaxIntensity;
}
// if (sparkVFX != null)
// {
// sparkVFX.SendEvent("OnPlay"); // 这里的 "OnBurst" 是你在VFX Graph里定义的事件名,需要一致
// }
}
else
{
shakeOffset = Vector3.zero;
if (isInTractionMode)
{
transform.position = targetPosition;
}
if (cutLight != null)
{
cutLight.intensity = 0f;
cutLight.enabled = false;
}
// if (sparkVFX != null)
// {
// sparkVFX.SendEvent("OnStop"); // 这里的 "OnBurst" 是你在VFX Graph里定义的事件名,需要一致
// }
}
}
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]);
}
}
private void UpdateFollowState(Vector2 mousePos)
{
transform.position = mousePos;
isInTractionMode = false;
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;
targetPosition = edgePoints[currentEdgeIndex];
transform.position = targetPosition;
currentState = State.Traction;
isInTractionMode = true;
}
}
private void UpdateTractionState(Vector2 mousePos)
{
Vector2 gearPos = transform.position - shakeOffset;
if (Vector2.Distance(mousePos, gearPos) > detachDistance)
{
currentState = State.Follow;
isInTractionMode = false;
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)
{
targetPosition = targetPoint;
if (Vector2.Distance(targetPoint, to) < 0.01f)
{
currentEdgeIndex = nextIndex;
}
}
else
{
targetPosition = (Vector3)gearPos + (Vector3)(moveDir.normalized * maxStep);
}
transform.position = targetPosition + shakeOffset;
// 使用原始边缘点来记录切割线
Vector2 originalFrom = originalEdgePoints[currentEdgeIndex];
Vector2 originalTo = originalEdgePoints[nextIndex];
Vector2 originalTargetPoint = GetClosestPointOnSegment(originalFrom, originalTo, transform.position - shakeOffset);
// 记录切割点(带噪声模拟毛糙)
Vector3 noisyPoint = originalTargetPoint;
noisyPoint.x += Random.Range(-noiseAmount, noiseAmount);
noisyPoint.y += Random.Range(-noiseAmount, noiseAmount);
AddCutPoint(noisyPoint);
if (sparkVFX != null)
{
// 计算从切割点到目标精灵中心的向量
Vector2 center = targetSpriteRenderer.transform.position;
Vector2 toCenter = center - (Vector2)originalTargetPoint;
// 计算法线方向(垂直于切割方向)
Vector2 normal = Vector2.Perpendicular(moveDir.normalized);
// 根据切割方向决定法线方向
if (tractionDirection > 0)
{
normal = -normal;
}
Vector3 velocity = (Vector3)normal * 10f;
sparkVFX.SetVector3("Initial Velocity1", velocity);
}
}
private void StartCutting()
{
isCutting = true;
//cutLinePoints.Clear();
if (cutLineRenderer != null)
{
cutLineRenderer.positionCount = 0;
}
if (sparkVFX != null)
{
sparkVFX.Play();
}
}
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 (cutLight != null)
{
cutLight.transform.position = point;
}
// 把火花特效位置设置到当前切割点
if (sparkVFX != null)
{
sparkVFX.transform.position = point;
}
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;
if (sparkVFX != null)
{
sparkVFX.Stop();
}
}
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;
}
}