基本交互和视觉效果确定
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class CutLineManager : MonoBehaviour
|
||||
{
|
||||
[Header("切割线参数")]
|
||||
public float cutLineWidth = 0.1f;
|
||||
public float noiseAmount = 0.05f; // 毛刺偏移幅度
|
||||
public float segmentDuration = 4f; // 线段持续时间
|
||||
public float cutLineDelay = 3f; // 延迟几秒才显示轨迹点
|
||||
|
||||
public LineRenderer mainCutLineRenderer;
|
||||
private List<TimedPoint> timedCutPoints = new List<TimedPoint>();
|
||||
private bool isCutting = false;
|
||||
public bool IsCutting => isCutting; // Public property to access isCutting state
|
||||
|
||||
private class TimedPoint
|
||||
{
|
||||
public Vector3 position;
|
||||
public float timeAdded;
|
||||
public TimedPoint(Vector3 pos, float time)
|
||||
{
|
||||
position = pos;
|
||||
timeAdded = time;
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitializeMainCutLine();
|
||||
}
|
||||
|
||||
private void InitializeMainCutLine()
|
||||
{
|
||||
GameObject cutLineObj = new GameObject("MainCutLine");
|
||||
cutLineObj.transform.parent = transform;
|
||||
mainCutLineRenderer = cutLineObj.AddComponent<LineRenderer>();
|
||||
mainCutLineRenderer.positionCount = 0;
|
||||
mainCutLineRenderer.widthCurve = AnimationCurve.Constant(0, 1, cutLineWidth);
|
||||
mainCutLineRenderer.material = new Material(Shader.Find("Sprites/Default"));
|
||||
mainCutLineRenderer.numCapVertices = 0;
|
||||
mainCutLineRenderer.sortingOrder = 1;
|
||||
}
|
||||
|
||||
public void StartCutting()
|
||||
{
|
||||
isCutting = true;
|
||||
timedCutPoints.Clear();
|
||||
if (mainCutLineRenderer != null)
|
||||
{
|
||||
mainCutLineRenderer.positionCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCutPoint(Vector3 point)
|
||||
{
|
||||
if (!isCutting) return;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EndCutting()
|
||||
{
|
||||
isCutting = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateCutLineRenderer();
|
||||
}
|
||||
|
||||
private void UpdateCutLineRenderer()
|
||||
{
|
||||
if (mainCutLineRenderer == 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);
|
||||
}
|
||||
}
|
||||
|
||||
mainCutLineRenderer.positionCount = visiblePoints.Count;
|
||||
if (visiblePoints.Count > 0)
|
||||
mainCutLineRenderer.SetPositions(visiblePoints.ToArray());
|
||||
}
|
||||
|
||||
private void CreateSegment(Vector3 from, Vector3 to)
|
||||
{
|
||||
GameObject segObj = new GameObject("HeatLineSegment");
|
||||
segObj.transform.parent = 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fe8e1ba5a1fbed40b777c74953b13bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,198 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class CuttingSystem : MonoBehaviour
|
||||
{
|
||||
[Header("切割系统参数")]
|
||||
public float snapDistance = 0.3f;
|
||||
public float detachDistance = 1.0f;
|
||||
public float directionSwitchCooldown = 0.2f;
|
||||
private CutLineManager cutLineManager;
|
||||
public float tractionSpeed = 2f;
|
||||
|
||||
[Header("测试")]
|
||||
public SpriteRenderer TargetspriteRenderer;
|
||||
private GearController gearController;
|
||||
|
||||
private Camera mainCamera;
|
||||
private int currentEdgeIndex = 0;
|
||||
private int tractionDirection = 1;
|
||||
private float directionSwitchTimer = 0f;
|
||||
|
||||
private enum State { Follow, Traction }
|
||||
private State currentState = State.Follow;
|
||||
|
||||
private List<Vector2> edgePoints;
|
||||
private List<Vector2> originalEdgePoints;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
gearController = GetComponentInChildren<GearController>();
|
||||
cutLineManager = GetComponentInChildren<CutLineManager>();
|
||||
|
||||
edgePoints = new List<Vector2>();
|
||||
originalEdgePoints = new List<Vector2>();
|
||||
|
||||
if (TargetspriteRenderer != null)
|
||||
{
|
||||
InitializeCutting(TargetspriteRenderer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("TargetspriteRenderer is null!");
|
||||
}
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
UpdateCutting();
|
||||
}
|
||||
|
||||
public void InitializeCutting(SpriteRenderer targetSprite, float edgePointsScale = 1.2f)
|
||||
{
|
||||
if (targetSprite == null) return;
|
||||
LoadEdgePointsFromSprite(targetSprite, edgePointsScale);
|
||||
Debug.Log($"InitializeCutting: edgePoints count = {edgePoints.Count}");
|
||||
}
|
||||
|
||||
public void UpdateCutting()
|
||||
{
|
||||
Vector2 mouseWorldPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
|
||||
|
||||
if (Input.GetMouseButton(0))
|
||||
{
|
||||
gearController.SetRotation(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
gearController.SetRotation(false);
|
||||
}
|
||||
|
||||
switch (currentState)
|
||||
{
|
||||
case State.Follow:
|
||||
UpdateFollowState(mouseWorldPos);
|
||||
break;
|
||||
case State.Traction:
|
||||
UpdateTractionState(mouseWorldPos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadEdgePointsFromSprite(SpriteRenderer targetSprite, float edgePointsScale)
|
||||
{
|
||||
var shapePoints = CuttingUtils.GetSpritePhysicsShapePoints(targetSprite);
|
||||
if (shapePoints == null) return;
|
||||
|
||||
var (scaledPoints, originalPoints) = CuttingUtils.ProcessEdgePoints(shapePoints, targetSprite, edgePointsScale);
|
||||
if (scaledPoints == null) return;
|
||||
|
||||
edgePoints = scaledPoints;
|
||||
originalEdgePoints = originalPoints;
|
||||
Debug.Log($"LoadEdgePointsFromSprite: edgePoints count = {edgePoints.Count}");
|
||||
}
|
||||
|
||||
private void UpdateFollowState(Vector2 mousePos)
|
||||
{
|
||||
gearController.transform.position = mousePos;
|
||||
|
||||
int nearestIndex = CuttingUtils.FindNearestEdgePointIndex(mousePos, edgePoints, out float dist);
|
||||
Debug.Log($"Distance to nearest point: {dist}, Snap distance: {snapDistance}");
|
||||
|
||||
if (dist < snapDistance)
|
||||
{
|
||||
currentEdgeIndex = nearestIndex;
|
||||
|
||||
Vector2 prev = edgePoints[CuttingUtils.WrapIndex(currentEdgeIndex - 1, edgePoints.Count)];
|
||||
Vector2 next = edgePoints[CuttingUtils.WrapIndex(currentEdgeIndex + 1, edgePoints.Count)];
|
||||
|
||||
float distToPrev = Vector2.Distance(mousePos, prev);
|
||||
float distToNext = Vector2.Distance(mousePos, next);
|
||||
|
||||
tractionDirection = (distToNext < distToPrev) ? 1 : -1;
|
||||
|
||||
gearController.transform.position = edgePoints[currentEdgeIndex];
|
||||
currentState = State.Traction;
|
||||
Debug.Log("Switched to Traction state");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTractionState(Vector2 mousePos)
|
||||
{
|
||||
Vector2 gearPos = transform.position;
|
||||
|
||||
if (Vector2.Distance(mousePos, gearPos) > detachDistance)
|
||||
{
|
||||
currentState = State.Follow;
|
||||
cutLineManager.EndCutting();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Input.GetMouseButton(0))
|
||||
{
|
||||
cutLineManager.EndCutting();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cutLineManager.IsCutting)
|
||||
{
|
||||
cutLineManager.StartCutting();
|
||||
}
|
||||
|
||||
int nextIndex = CuttingUtils.WrapIndex(currentEdgeIndex + tractionDirection, edgePoints.Count);
|
||||
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 = CuttingUtils.WrapIndex(currentEdgeIndex + tractionDirection, edgePoints.Count);
|
||||
from = edgePoints[currentEdgeIndex];
|
||||
to = edgePoints[nextIndex];
|
||||
forward = (to - from).normalized;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
directionSwitchTimer = 0f;
|
||||
}
|
||||
|
||||
Vector2 targetPoint = CuttingUtils.GetClosestPointOnSegment(from, to, mousePos);
|
||||
Vector2 moveDir = (targetPoint - gearPos);
|
||||
float distance = moveDir.magnitude;
|
||||
|
||||
float maxStep = tractionSpeed * Time.deltaTime;
|
||||
|
||||
if (distance <= maxStep)
|
||||
{
|
||||
gearController.transform.position = targetPoint;
|
||||
|
||||
if (Vector2.Distance(targetPoint, to) < 0.01f)
|
||||
{
|
||||
currentEdgeIndex = nextIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gearController.transform.position += (Vector3)(moveDir.normalized * maxStep);
|
||||
}
|
||||
|
||||
Vector2 originalFrom = originalEdgePoints[currentEdgeIndex];
|
||||
Vector2 originalTo = originalEdgePoints[nextIndex];
|
||||
Vector2 originalTargetPoint = CuttingUtils.GetClosestPointOnSegment(originalFrom, originalTo, transform.position);
|
||||
|
||||
Vector3 noisyPoint = originalTargetPoint;
|
||||
noisyPoint.x += Random.Range(-cutLineManager.noiseAmount, cutLineManager.noiseAmount);
|
||||
noisyPoint.y += Random.Range(-cutLineManager.noiseAmount, cutLineManager.noiseAmount);
|
||||
cutLineManager.AddCutPoint(noisyPoint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee21f841c30549c43837ad222c4892b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public static class CuttingUtils
|
||||
{
|
||||
public static int FindNearestEdgePointIndex(Vector2 pos, List<Vector2> edgePoints, 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;
|
||||
}
|
||||
|
||||
public static 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;
|
||||
}
|
||||
|
||||
public static int WrapIndex(int index, int count)
|
||||
{
|
||||
if (index < 0) return count - 2;
|
||||
if (index >= count - 1) return 0;
|
||||
return index;
|
||||
}
|
||||
|
||||
public static List<Vector2> GetSpritePhysicsShapePoints(SpriteRenderer spriteRenderer)
|
||||
{
|
||||
List<Vector2> shapePoints = new List<Vector2>();
|
||||
spriteRenderer.sprite.GetPhysicsShape(0, shapePoints);
|
||||
|
||||
if (shapePoints.Count == 0)
|
||||
{
|
||||
Debug.LogError("目标Sprite没有物理形状点");
|
||||
return null;
|
||||
}
|
||||
|
||||
return shapePoints;
|
||||
}
|
||||
|
||||
public static (List<Vector2> scaledPoints, List<Vector2> originalPoints) ProcessEdgePoints(
|
||||
List<Vector2> shapePoints,
|
||||
SpriteRenderer spriteRenderer,
|
||||
float edgePointsScale = 1.2f,
|
||||
float desiredSegmentLength = 0.1f)
|
||||
{
|
||||
if (shapePoints == null || shapePoints.Count == 0) return (null, null);
|
||||
|
||||
List<Vector2> scaledPoints = new List<Vector2>();
|
||||
List<Vector2> originalPoints = new List<Vector2>();
|
||||
|
||||
// 计算中心点
|
||||
Vector2 center = Vector2.zero;
|
||||
foreach (Vector2 point in shapePoints)
|
||||
{
|
||||
center += point;
|
||||
}
|
||||
center /= shapePoints.Count;
|
||||
|
||||
// 处理每个线段
|
||||
for (int i = 0; i < shapePoints.Count; i++)
|
||||
{
|
||||
Vector2 p1 = spriteRenderer.transform.TransformPoint(shapePoints[i]);
|
||||
Vector2 p2 = spriteRenderer.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);
|
||||
scaledPoints.Add(interpolated);
|
||||
originalPoints.Add(originalInterpolated);
|
||||
}
|
||||
}
|
||||
|
||||
// 确保首尾相连
|
||||
if ((scaledPoints[0] - scaledPoints[scaledPoints.Count - 1]).sqrMagnitude > 0.0001f)
|
||||
{
|
||||
scaledPoints.Add(scaledPoints[0]);
|
||||
originalPoints.Add(originalPoints[0]);
|
||||
}
|
||||
|
||||
return (scaledPoints, originalPoints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fd7ba5ee9e9b7a4ca602330a830ac95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class GearController : MonoBehaviour
|
||||
{
|
||||
[Header("齿轮参数")]
|
||||
public float rotationSpeed = 360f;
|
||||
public float snapDistance = 0.3f;
|
||||
public float detachDistance = 10.0f;
|
||||
|
||||
private bool isRotating = false;
|
||||
|
||||
public void SetRotation(bool shouldRotate)
|
||||
{
|
||||
isRotating = shouldRotate;
|
||||
}
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (isRotating)
|
||||
{
|
||||
transform.Rotate(Vector3.forward, -rotationSpeed * Time.deltaTime);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b570555589f44fb4889f5dd5712ced34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using Shapes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
public class GearFollower : MonoBehaviour
|
||||
{
|
||||
@@ -13,12 +15,22 @@ public class GearFollower : MonoBehaviour
|
||||
|
||||
[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;
|
||||
@@ -26,15 +38,10 @@ public class GearFollower : MonoBehaviour
|
||||
|
||||
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;
|
||||
@@ -45,25 +52,30 @@ public class GearFollower : MonoBehaviour
|
||||
timeAdded = time;
|
||||
}
|
||||
}
|
||||
|
||||
private List<TimedPoint> timedCutPoints = new List<TimedPoint>();
|
||||
public float cutLineDelay = 3f; // 延迟几秒才显示轨迹点
|
||||
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.numCapVertices = 0;
|
||||
cutLineRenderer.sortingOrder = 1;
|
||||
}
|
||||
if (cutLight != null)
|
||||
{
|
||||
cutLight.intensity = 0f;
|
||||
cutLight.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
Vector2 mouseWorldPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
|
||||
@@ -72,7 +84,6 @@ public class GearFollower : MonoBehaviour
|
||||
{
|
||||
transform.Rotate(Vector3.forward, -360f * Time.deltaTime);
|
||||
}
|
||||
|
||||
switch (currentState)
|
||||
{
|
||||
case State.Follow:
|
||||
@@ -83,18 +94,60 @@ public class GearFollower : MonoBehaviour
|
||||
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>(); // 初始化原始边缘点列表
|
||||
originalEdgePoints = new List<Vector2>();
|
||||
if (targetSpriteRenderer == null)
|
||||
{
|
||||
Debug.LogError("targetSpriteRenderer 为空");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Vector2> shapePoints = new List<Vector2>();
|
||||
targetSpriteRenderer.sprite.GetPhysicsShape(0, shapePoints);
|
||||
|
||||
@@ -103,32 +156,26 @@ public class GearFollower : MonoBehaviour
|
||||
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;
|
||||
@@ -144,12 +191,11 @@ public class GearFollower : MonoBehaviour
|
||||
edgePoints.Add(edgePoints[0]);
|
||||
originalEdgePoints.Add(originalEdgePoints[0]);
|
||||
}
|
||||
|
||||
Debug.Log($"边缘点数量:{edgePoints.Count}");
|
||||
}
|
||||
private void UpdateFollowState(Vector2 mousePos)
|
||||
{
|
||||
transform.position = mousePos;
|
||||
isInTractionMode = false;
|
||||
|
||||
int nearestIndex = FindNearestEdgePointIndex(mousePos, out float dist);
|
||||
if (dist < snapDistance)
|
||||
@@ -164,27 +210,28 @@ public class GearFollower : MonoBehaviour
|
||||
|
||||
tractionDirection = (distToNext < distToPrev) ? 1 : -1;
|
||||
|
||||
transform.position = edgePoints[currentEdgeIndex];
|
||||
targetPosition = edgePoints[currentEdgeIndex];
|
||||
transform.position = targetPosition;
|
||||
currentState = State.Traction;
|
||||
isInTractionMode = true;
|
||||
}
|
||||
}
|
||||
private void UpdateTractionState(Vector2 mousePos)
|
||||
{
|
||||
Vector2 gearPos = transform.position;
|
||||
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();
|
||||
@@ -225,7 +272,7 @@ public class GearFollower : MonoBehaviour
|
||||
|
||||
if (distance <= maxStep)
|
||||
{
|
||||
transform.position = targetPoint;
|
||||
targetPosition = targetPoint;
|
||||
|
||||
if (Vector2.Distance(targetPoint, to) < 0.01f)
|
||||
{
|
||||
@@ -234,19 +281,36 @@ public class GearFollower : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position += (Vector3)(moveDir.normalized * maxStep);
|
||||
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);
|
||||
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()
|
||||
@@ -257,15 +321,27 @@ public class GearFollower : MonoBehaviour
|
||||
{
|
||||
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;
|
||||
@@ -289,7 +365,6 @@ public class GearFollower : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
// 这里的点还没到延迟时间,不显示
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +376,11 @@ public class GearFollower : MonoBehaviour
|
||||
private void EndCutting()
|
||||
{
|
||||
isCutting = false;
|
||||
// 这里可以添加切割结束后的淡出效果等
|
||||
if (sparkVFX != null)
|
||||
{
|
||||
sparkVFX.Stop();
|
||||
}
|
||||
|
||||
}
|
||||
private int FindNearestEdgePointIndex(Vector2 pos, out float minDist)
|
||||
{
|
||||
@@ -319,7 +398,6 @@ public class GearFollower : MonoBehaviour
|
||||
}
|
||||
return nearestIndex;
|
||||
}
|
||||
|
||||
private Vector2 GetClosestPointOnSegment(Vector2 a, Vector2 b, Vector2 p)
|
||||
{
|
||||
Vector2 ab = b - a;
|
||||
|
||||
Reference in New Issue
Block a user