基本交互和视觉效果确定

This commit is contained in:
2025-05-18 02:23:06 +08:00
parent 3ef8e4441d
commit de34465cdf
20 changed files with 13210 additions and 56 deletions
@@ -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;