766 lines
26 KiB
C#
766 lines
26 KiB
C#
using System.Collections.Generic;
|
||
using Shapes;
|
||
using UnityEngine;
|
||
using UnityEngine.Rendering.Universal;
|
||
using UnityEngine.VFX;
|
||
using DG.Tweening;
|
||
using AibisDream.FixSystem;
|
||
using AibisDream; // 添加OpenSystem的命名空间
|
||
|
||
public class GearFollower : MonoBehaviour
|
||
{
|
||
[Header("参数")]
|
||
public SpriteRenderer targetSpriteRenderer;
|
||
public float snapDistance = 1.0f;
|
||
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()
|
||
{
|
||
}
|
||
public void Init(SpriteRenderer targetSpriteRenderer)
|
||
{
|
||
SpriteRenderer gearRenderer = GetComponent<SpriteRenderer>();
|
||
if (gearRenderer != null)
|
||
{
|
||
gearRenderer.color = new Color(1, 1, 1, 1);
|
||
}
|
||
this.targetSpriteRenderer = null;
|
||
this.targetSpriteRenderer = targetSpriteRenderer;
|
||
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()
|
||
{
|
||
|
||
}
|
||
public void GearUpdate()
|
||
{
|
||
// 获取鼠标在屏幕上的位置
|
||
Vector3 mouseScreenPos = Input.mousePosition;
|
||
// 将屏幕坐标转换为世界坐标
|
||
Vector3 mouseWorldPos = mainCamera.ScreenToWorldPoint(new Vector3(mouseScreenPos.x, mouseScreenPos.y, -mainCamera.transform.position.z));
|
||
mouseWorldPos.z = 0; // 确保z坐标为0
|
||
|
||
Debug.Log($"鼠标屏幕坐标: {mouseScreenPos}");
|
||
Debug.Log($"鼠标世界坐标: {mouseWorldPos}");
|
||
Debug.Log($"齿轮当前位置: {transform.position}");
|
||
if (targetSpriteRenderer != null)
|
||
{
|
||
Debug.Log($"目标精灵位置: {targetSpriteRenderer.transform.position}");
|
||
Debug.Log($"目标精灵边界: {targetSpriteRenderer.bounds}");
|
||
}
|
||
|
||
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;
|
||
}
|
||
Debug.Log($"原始形状点数量: {shapePoints.Count}");
|
||
float desiredSegmentLength = 0.1f;
|
||
|
||
edgePoints.Clear();
|
||
originalEdgePoints.Clear();
|
||
|
||
// 获取精灵的世界坐标
|
||
Vector3 spriteWorldPos = targetSpriteRenderer.transform.position;
|
||
Debug.Log($"精灵世界坐标: {spriteWorldPos}");
|
||
|
||
// 计算精灵的局部边界
|
||
Bounds localBounds = targetSpriteRenderer.bounds;
|
||
Vector3 localCenter = localBounds.center;
|
||
Vector3 localSize = localBounds.size;
|
||
|
||
// 计算缩放后的边界
|
||
float maxScale = Mathf.Max(localSize.x, localSize.y) * edgePointsScale;
|
||
float minScale = Mathf.Min(localSize.x, localSize.y) * edgePointsScale;
|
||
|
||
Debug.Log($"精灵局部边界: 中心={localCenter}, 大小={localSize}");
|
||
Debug.Log($"缩放后边界: 最大={maxScale}, 最小={minScale}");
|
||
|
||
for (int i = 0; i < shapePoints.Count; i++)
|
||
{
|
||
// 将局部坐标转换为世界坐标
|
||
Vector3 localPoint1 = shapePoints[i];
|
||
Vector3 localPoint2 = shapePoints[(i + 1) % shapePoints.Count];
|
||
|
||
// 转换为世界坐标
|
||
Vector3 worldPoint1 = targetSpriteRenderer.transform.TransformPoint(localPoint1);
|
||
Vector3 worldPoint2 = targetSpriteRenderer.transform.TransformPoint(localPoint2);
|
||
|
||
// 确保坐标在合理范围内
|
||
worldPoint1 = ClampPosition(worldPoint1, spriteWorldPos, maxScale);
|
||
worldPoint2 = ClampPosition(worldPoint2, spriteWorldPos, maxScale);
|
||
|
||
Debug.Log($"点 {i} 局部坐标: {localPoint1} -> 世界坐标: {worldPoint1}");
|
||
|
||
// 计算相对于精灵中心点的缩放
|
||
Vector3 scaledPoint1 = spriteWorldPos + (worldPoint1 - spriteWorldPos) * edgePointsScale;
|
||
Vector3 scaledPoint2 = spriteWorldPos + (worldPoint2 - spriteWorldPos) * edgePointsScale;
|
||
|
||
// 确保缩放后的点也在合理范围内
|
||
scaledPoint1 = ClampPosition(scaledPoint1, spriteWorldPos, maxScale);
|
||
scaledPoint2 = ClampPosition(scaledPoint2, spriteWorldPos, maxScale);
|
||
|
||
Debug.Log($"点 {i} 缩放后世界坐标: {scaledPoint1}");
|
||
|
||
float segmentLength = Vector3.Distance(scaledPoint1, scaledPoint2);
|
||
int steps = Mathf.Max(1, Mathf.CeilToInt(segmentLength / desiredSegmentLength));
|
||
|
||
for (int s = 0; s < steps; s++)
|
||
{
|
||
float t = (float)s / steps;
|
||
Vector3 interpolated = Vector3.Lerp(scaledPoint1, scaledPoint2, t);
|
||
Vector3 originalInterpolated = Vector3.Lerp(worldPoint1, worldPoint2, t);
|
||
|
||
// 确保插值点也在合理范围内
|
||
interpolated = ClampPosition(interpolated, spriteWorldPos, maxScale);
|
||
originalInterpolated = ClampPosition(originalInterpolated, spriteWorldPos, maxScale);
|
||
|
||
// 存储为Vector2,但保持世界坐标
|
||
edgePoints.Add(new Vector2(interpolated.x, interpolated.y));
|
||
originalEdgePoints.Add(new Vector2(originalInterpolated.x, originalInterpolated.y));
|
||
}
|
||
}
|
||
|
||
if ((edgePoints[0] - edgePoints[edgePoints.Count - 1]).sqrMagnitude > 0.0001f)
|
||
{
|
||
edgePoints.Add(edgePoints[0]);
|
||
originalEdgePoints.Add(originalEdgePoints[0]);
|
||
}
|
||
|
||
Debug.Log($"生成的边缘点数量: {edgePoints.Count}");
|
||
Debug.Log($"第一个边缘点世界坐标: {edgePoints[0]}");
|
||
Debug.Log($"最后一个边缘点世界坐标: {edgePoints[edgePoints.Count - 1]}");
|
||
}
|
||
|
||
// 添加一个辅助方法来限制坐标范围
|
||
private Vector3 ClampPosition(Vector3 position, Vector3 center, float maxDistance)
|
||
{
|
||
Vector3 offset = position - center;
|
||
float distance = offset.magnitude;
|
||
|
||
if (distance > maxDistance)
|
||
{
|
||
offset = offset.normalized * maxDistance;
|
||
position = center + offset;
|
||
}
|
||
|
||
return position;
|
||
}
|
||
|
||
private void UpdateFollowState(Vector2 mousePos)
|
||
{
|
||
transform.position = mousePos;
|
||
isInTractionMode = false;
|
||
|
||
int nearestIndex = FindNearestEdgePointIndex(mousePos, out float dist);
|
||
Debug.Log($"最近边缘点索引: {nearestIndex}, 距离: {dist}");
|
||
Debug.Log($"最近边缘点位置: {edgePoints[nearestIndex]}");
|
||
Debug.Log($"当前snapDistance: {snapDistance}");
|
||
|
||
if (dist < snapDistance)
|
||
{
|
||
currentEdgeIndex = nearestIndex;
|
||
|
||
Vector2 prev = edgePoints[WrapIndex(currentEdgeIndex - 1)];
|
||
Vector2 next = edgePoints[WrapIndex(currentEdgeIndex + 1)];
|
||
Debug.Log($"前一个点: {prev}, 后一个点: {next}");
|
||
|
||
float distToPrev = Vector2.Distance(mousePos, prev);
|
||
float distToNext = Vector2.Distance(mousePos, next);
|
||
Debug.Log($"到前一个点距离: {distToPrev}, 到后一个点距离: {distToNext}");
|
||
|
||
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]);
|
||
}
|
||
Debug.Log($"计算总边缘长度: {totalEdgeLength}");
|
||
}
|
||
|
||
private void UpdateCutProgress(Vector2 from, Vector2 to)
|
||
{
|
||
// 计算当前段的切割长度
|
||
float segmentLength = Vector2.Distance(from, to);
|
||
cutLength += segmentLength;
|
||
|
||
// 记录已切割的边缘点
|
||
cutEdgeIndices.Add(currentEdgeIndex);
|
||
|
||
// 计算切割进度
|
||
float progress = cutLength / totalEdgeLength;
|
||
Debug.Log($"当前切割长度: {cutLength}, 总长度: {totalEdgeLength}, 进度: {progress * 100}%");
|
||
|
||
// 检查是否满足切割完成条件
|
||
if (!isCutComplete && progress >= cutCompletionThreshold)
|
||
{
|
||
// 检查是否形成了连续的切割路径
|
||
bool hasContinuousCut = CheckContinuousCut();
|
||
Debug.Log($"连续切割检查: {hasContinuousCut}, 已切割点数: {cutEdgeIndices.Count}, 总点数: {edgePoints.Count}");
|
||
|
||
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;
|
||
int totalPoints = edgePoints.Count;
|
||
|
||
// 检查所有可能的连续段
|
||
for (int startIndex = 0; startIndex < sortedIndices.Count; startIndex++)
|
||
{
|
||
continuousCount = 1;
|
||
int currentIndex = sortedIndices[startIndex];
|
||
|
||
// 向前检查
|
||
for (int i = 1; i < sortedIndices.Count; i++)
|
||
{
|
||
int nextIndex = (currentIndex + i) % totalPoints;
|
||
if (sortedIndices.Contains(nextIndex))
|
||
{
|
||
continuousCount++;
|
||
}
|
||
else
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 向后检查
|
||
for (int i = 1; i < sortedIndices.Count; i++)
|
||
{
|
||
int prevIndex = (currentIndex - i + totalPoints) % totalPoints;
|
||
if (sortedIndices.Contains(prevIndex))
|
||
{
|
||
continuousCount++;
|
||
}
|
||
else
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
maxContinuousCount = Mathf.Max(maxContinuousCount, continuousCount);
|
||
}
|
||
|
||
// 计算连续切割点的比例
|
||
float continuousRatio = (float)maxContinuousCount / totalPoints;
|
||
Debug.Log($"最大连续切割点数: {maxContinuousCount}, 总点数: {totalPoints}, 比例: {continuousRatio * 100}%");
|
||
|
||
// 如果连续切割点的数量超过总点数的70%,认为形成了有效的切割路径
|
||
return continuousRatio >= 0.7f;
|
||
}
|
||
|
||
private void OnCutComplete()
|
||
{
|
||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||
if (cuttingManager != null)
|
||
{
|
||
cuttingManager.OnCutComplete();
|
||
}
|
||
}
|
||
public void FinishCutting()
|
||
{
|
||
// 清理特效
|
||
if (cutLight != null)
|
||
{
|
||
cutLight.enabled = false;
|
||
}
|
||
if (sparkVFX != null)
|
||
{
|
||
sparkVFX.Stop();
|
||
}
|
||
if (cutLineRenderer != null)
|
||
{
|
||
cutLineRenderer.positionCount = 0;
|
||
}
|
||
// 计算随机方向
|
||
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(this.gameObject);
|
||
var cuttingManager = FixSystemCenter.SystemDic.Get<CuttingManager>();
|
||
if (cuttingManager != null)
|
||
{
|
||
cuttingManager.UpdateModule();
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
// 清理DOTween序列
|
||
if (cutCompleteSequence != null)
|
||
{
|
||
cutCompleteSequence.Kill();
|
||
}
|
||
}
|
||
}
|