Files
aibis-dream/Assets/Scripts/FixSystemNew/BodyModule/GearFollower.cs
T
2025-05-19 21:27:58 +08:00

637 lines
21 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;
using DG.Tweening;
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; // 发射速率变化速度
[Header("切割完成效果")]
public float cutCompletionThreshold = 0.95f; // 切割完成阈值(切割路径占边缘的比例)
public float minCutLength = 0.5f; // 最小切割长度(相对于总长度的比例)
public float targetShakeAmount = 0.02f; // 目标抖动幅度
public float cutCompleteTiltAngle = 15f; // 切割完成后的倾斜角度
public float cutCompleteOffset = 0.2f; // 切割完成后的位移
public float cutCompleteScale = 1.2f; // 切割完成后的放大倍数
public float fadeOutDuration = 1f; // 淡出持续时间
public float cutCompleteDelay = 1f; // 切割完成后的延迟时间
public float scaleUpDuration = 1f; // 放大动画持续时间
public float fadeOutDelay = 1f; // 淡出前的延迟时间
public AudioClip cutCompleteSound; // 切割完成音效
private float totalEdgeLength = 0f; // 总边缘长度
private float cutLength = 0f; // 已切割长度
private bool isCutComplete = false;
private bool isFadingOut = false;
private float fadeOutTimer = 0f;
private Vector3 originalTargetPosition;
private Quaternion originalTargetRotation;
private Vector3 originalTargetScale;
private AudioSource audioSource;
private Sequence cutCompleteSequence; // DOTween序列
private HashSet<int> cutEdgeIndices = new HashSet<int>(); // 记录已切割的边缘点索引
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();
CalculateTotalEdgeLength();
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;
}
// 初始化目标相关变量
if (targetSpriteRenderer != null)
{
originalTargetPosition = targetSpriteRenderer.transform.position;
originalTargetRotation = targetSpriteRenderer.transform.rotation;
originalTargetScale = targetSpriteRenderer.transform.localScale;
}
// 添加音频源组件
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.playOnAwake = false;
audioSource.spatialBlend = 0f; // 2D音效
}
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 (targetSpriteRenderer != null && !isCutComplete)
{
Vector2 targetShake = Random.insideUnitCircle * targetShakeAmount;
targetSpriteRenderer.transform.position = originalTargetPosition + (Vector3)targetShake;
}
// 根据震动幅度控制灯光强度
if (cutLight != null)
{
cutLight.enabled = true;
float intensity = shakeOffset.magnitude / shakeAmount; // 得到 [0,1] 比例
cutLight.intensity = intensity * lightMaxIntensity;
}
}
else
{
shakeOffset = Vector3.zero;
if (isInTractionMode)
{
transform.position = targetPosition;
}
if (cutLight != null)
{
cutLight.intensity = 0f;
cutLight.enabled = false;
}
}
// 处理淡出效果
if (isFadingOut)
{
fadeOutTimer += Time.deltaTime;
float alpha = 1f - (fadeOutTimer / fadeOutDuration);
if (targetSpriteRenderer != null)
{
Color color = targetSpriteRenderer.color;
color.a = alpha;
targetSpriteRenderer.color = color;
}
// 齿轮也淡出
SpriteRenderer gearRenderer = GetComponent<SpriteRenderer>();
if (gearRenderer != null)
{
Color gearColor = gearRenderer.color;
gearColor.a = alpha;
gearRenderer.color = gearColor;
}
if (fadeOutTimer >= fadeOutDuration)
{
// 淡出完成后销毁物体
Destroy(gameObject);
if (targetSpriteRenderer != null)
{
Destroy(targetSpriteRenderer.gameObject);
}
}
}
}
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;
UpdateCutProgress(from, 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;
}
private void CalculateTotalEdgeLength()
{
totalEdgeLength = 0f;
for (int i = 0; i < edgePoints.Count - 1; i++)
{
totalEdgeLength += Vector2.Distance(edgePoints[i], edgePoints[i + 1]);
}
}
private void UpdateCutProgress(Vector2 from, Vector2 to)
{
cutLength += Vector2.Distance(from, to);
float progress = cutLength / totalEdgeLength;
// 记录已切割的边缘点
cutEdgeIndices.Add(currentEdgeIndex);
// 检查是否满足切割完成条件
if (!isCutComplete && progress >= cutCompletionThreshold && progress >= minCutLength)
{
// 检查是否形成了连续的切割路径
bool hasContinuousCut = CheckContinuousCut();
if (hasContinuousCut)
{
isCutComplete = true;
OnCutComplete();
}
}
}
private bool CheckContinuousCut()
{
if (cutEdgeIndices.Count < 2) return false;
// 将索引转换为有序列表
List<int> sortedIndices = new List<int>(cutEdgeIndices);
sortedIndices.Sort();
// 检查是否有连续的切割点
int continuousCount = 1;
int maxContinuousCount = 1;
for (int i = 1; i < sortedIndices.Count; i++)
{
if (sortedIndices[i] == sortedIndices[i - 1] + 1)
{
continuousCount++;
maxContinuousCount = Mathf.Max(maxContinuousCount, continuousCount);
}
else
{
continuousCount = 1;
}
}
// 如果连续切割点的数量超过总点数的50%,认为形成了有效的切割路径
return maxContinuousCount >= edgePoints.Count * 0.5f;
}
private void OnCutComplete()
{
// 停止切割状态
isCutting = false;
isInTractionMode = false;
enabled = false;
// 清理特效
if (cutLight != null)
{
cutLight.enabled = false;
}
if (sparkVFX != null)
{
sparkVFX.Stop();
}
if (cutLineRenderer != null)
{
cutLineRenderer.positionCount = 0;
}
timedCutPoints.Clear();
// 目标倾斜和位移
if (targetSpriteRenderer != null)
{
// 计算随机方向
float randomAngle = Random.Range(-cutCompleteTiltAngle, cutCompleteTiltAngle);
Vector2 randomOffset = Random.insideUnitCircle * cutCompleteOffset;
// 立即应用倾斜和位移
targetSpriteRenderer.transform.rotation = Quaternion.Euler(0, 0, randomAngle);
targetSpriteRenderer.transform.position = originalTargetPosition + (Vector3)randomOffset;
// 创建动画序列
cutCompleteSequence = DOTween.Sequence();
// 等待一段时间后开始放大
cutCompleteSequence.AppendInterval(cutCompleteDelay);
cutCompleteSequence.Append(targetSpriteRenderer.transform.DOScale(originalTargetScale * cutCompleteScale, scaleUpDuration)
.SetEase(Ease.OutQuad));
// 等待一段时间后开始淡出
cutCompleteSequence.AppendInterval(fadeOutDelay);
cutCompleteSequence.Append(targetSpriteRenderer.DOFade(0f, fadeOutDuration));
// 齿轮也同时淡出
SpriteRenderer gearRenderer = GetComponent<SpriteRenderer>();
if (gearRenderer != null)
{
cutCompleteSequence.Join(gearRenderer.DOFade(0f, fadeOutDuration));
}
// 动画完成后销毁物体
cutCompleteSequence.OnComplete(() => {
Destroy(gameObject);
if (targetSpriteRenderer != null)
{
Destroy(targetSpriteRenderer.gameObject);
}
});
}
}
private void OnDestroy()
{
// 清理DOTween序列
if (cutCompleteSequence != null)
{
cutCompleteSequence.Kill();
}
}
}