基本交互和视觉效果确定
This commit is contained in:
@@ -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