263 lines
8.2 KiB
C#
263 lines
8.2 KiB
C#
using UnityEngine;
|
||
using Shapes;
|
||
using System.Collections.Generic;
|
||
|
||
[ExecuteAlways]
|
||
public class EmoMazeDrawer : ImmediateModeShapeDrawer
|
||
{
|
||
[Header("场设置")]
|
||
public float fieldSize = 20f;
|
||
public int pointCount = 30;
|
||
public float pointSize = 0.15f;
|
||
public float lineWidth = 0.03f;
|
||
|
||
[Header("波动参数")]
|
||
public float noiseScale = 0.3f;
|
||
public float waveHeight = 1.5f;
|
||
public float waveSpeed = 1f;
|
||
public float timeScale = 1f;
|
||
|
||
[Header("涟漪参数")]
|
||
public float rippleStrength = 2f;
|
||
public float rippleDuration = 1.5f;
|
||
public float rippleSpreadSpeed = 6f;
|
||
public float rippleSharpness = 2f;
|
||
public float rippleDelay = 0.25f;
|
||
|
||
[Header("迷宫设置")]
|
||
public Vector2Int playerPos = new Vector2Int(1, 1);
|
||
public float moveCooldown = 0.2f;
|
||
public float playerBounceHeight = 1.5f;
|
||
public List<Vector2Int> obstacles = new List<Vector2Int>();
|
||
|
||
private float lastMoveTime = -999f;
|
||
private Vector3[] points;
|
||
private float time;
|
||
|
||
// 涟漪记录结构体
|
||
private struct Ripple
|
||
{
|
||
public Vector2 position;
|
||
public float startTime;
|
||
public float delay;
|
||
}
|
||
|
||
private struct RippleResult
|
||
{
|
||
public float heightOffset;
|
||
public float brightnessBoost;
|
||
}
|
||
|
||
private List<Ripple> ripples = new List<Ripple>();
|
||
|
||
private void Update()
|
||
{
|
||
// 时间更新(Play模式下使用 Time.time)
|
||
time = Application.isPlaying ? Time.time * timeScale : Time.realtimeSinceStartup * timeScale;
|
||
|
||
if (Application.isPlaying)
|
||
{
|
||
HandleInput();
|
||
ripples.RemoveAll(r => Time.time - r.startTime > rippleDuration + rippleDelay);
|
||
}
|
||
}
|
||
|
||
private void HandleInput()
|
||
{
|
||
if (Time.time - lastMoveTime < moveCooldown) return;
|
||
|
||
Vector2Int dir = Vector2Int.zero;
|
||
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) dir = Vector2Int.up;
|
||
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) dir = Vector2Int.down;
|
||
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) dir = Vector2Int.left;
|
||
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) dir = Vector2Int.right;
|
||
|
||
if (dir != Vector2Int.zero)
|
||
{
|
||
Vector2Int newPos = playerPos + dir;
|
||
if (IsInBounds(newPos) && !IsBlocked(newPos))
|
||
{
|
||
playerPos = newPos;
|
||
lastMoveTime = Time.time;
|
||
}
|
||
else
|
||
{
|
||
Vector3 world = GridToWorld(newPos);
|
||
AddRipple(world.x, world.z); // 产生“碰撞弹开”的涟漪
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool IsInBounds(Vector2Int pos) =>
|
||
pos.x >= 0 && pos.y >= 0 && pos.x < pointCount && pos.y < pointCount;
|
||
|
||
private bool IsBlocked(Vector2Int pos)
|
||
{
|
||
foreach (var obs in obstacles)
|
||
{
|
||
if (Vector2Int.Distance(obs, pos) <= 1f) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private void AddRipple(float x, float z)
|
||
{
|
||
ripples.Add(new Ripple
|
||
{
|
||
position = new Vector2(x, z),
|
||
startTime = Time.time,
|
||
delay = rippleDelay
|
||
});
|
||
}
|
||
|
||
private RippleResult GetRippleEffect(float x, float z, float currentTime)
|
||
{
|
||
float rippleOffset = 0f;
|
||
float brightness = 0f;
|
||
|
||
foreach (var ripple in ripples)
|
||
{
|
||
float elapsed = currentTime - ripple.startTime;
|
||
|
||
if (elapsed < ripple.delay)
|
||
{
|
||
float dist = Vector2.Distance(new Vector2(x, z), ripple.position);
|
||
float spike = Mathf.Exp(-dist * rippleSharpness);
|
||
float ease = Mathf.Sin((elapsed / ripple.delay) * Mathf.PI * 0.5f);
|
||
rippleOffset += spike * rippleStrength * ease;
|
||
continue;
|
||
}
|
||
|
||
float spreadElapsed = elapsed - ripple.delay;
|
||
float spread = spreadElapsed * rippleSpreadSpeed;
|
||
float dist2 = Vector2.Distance(new Vector2(x, z), ripple.position);
|
||
float t = Mathf.Clamp01(spreadElapsed / rippleDuration);
|
||
float ease2 = Mathf.Sin(t * Mathf.PI);
|
||
|
||
float spike2 = Mathf.Exp(-Mathf.Pow(dist2 - spread, 2f) * rippleSharpness);
|
||
rippleOffset += spike2 * rippleStrength * ease2;
|
||
|
||
float bright = Mathf.Exp(-Mathf.Pow((dist2 - spread) * 0.5f, 2f));
|
||
brightness += bright * ease2;
|
||
}
|
||
|
||
return new RippleResult
|
||
{
|
||
heightOffset = rippleOffset,
|
||
brightnessBoost = brightness
|
||
};
|
||
}
|
||
|
||
private float GetHeight(float x, float z, float currentTime)
|
||
{
|
||
float noise = 0;
|
||
float amp = 1f;
|
||
float freq = 1f;
|
||
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
float nx = x * freq * noiseScale;
|
||
float nz = z * freq * noiseScale;
|
||
float nt = currentTime * waveSpeed * freq;
|
||
noise += Mathf.PerlinNoise(nx + nt, nz + nt) * amp;
|
||
amp *= 0.5f;
|
||
freq *= 2f;
|
||
}
|
||
|
||
return noise * waveHeight;
|
||
}
|
||
|
||
private Vector3 GridToWorld(Vector2Int pos)
|
||
{
|
||
float step = fieldSize / (pointCount - 1);
|
||
float half = fieldSize * 0.5f;
|
||
float x = -half + pos.x * step;
|
||
float z = -half + pos.y * step;
|
||
return new Vector3(x, 0, z);
|
||
}
|
||
|
||
public override void DrawShapes(Camera cam)
|
||
{
|
||
using (Draw.Command(cam))
|
||
{
|
||
Draw.ResetAllDrawStates();
|
||
Draw.LineGeometry = LineGeometry.Volumetric3D;
|
||
Draw.Thickness = lineWidth;
|
||
|
||
float step = fieldSize / (pointCount - 1);
|
||
float halfSize = fieldSize * 0.5f;
|
||
|
||
if (points == null || points.Length != pointCount * pointCount)
|
||
points = new Vector3[pointCount * pointCount];
|
||
|
||
for (int i = 0; i < pointCount; i++)
|
||
{
|
||
for (int j = 0; j < pointCount; j++)
|
||
{
|
||
float x = -halfSize + j * step;
|
||
float z = -halfSize + i * step;
|
||
|
||
float baseHeight = GetHeight(x, z, time);
|
||
|
||
if (IsNearObstacle(new Vector2Int(j, i)))
|
||
baseHeight += playerBounceHeight;
|
||
|
||
RippleResult ripple = GetRippleEffect(x, z, Time.time);
|
||
float finalY = baseHeight + ripple.heightOffset;
|
||
|
||
Vector3 pos = new Vector3(x, finalY, z);
|
||
points[i * pointCount + j] = pos;
|
||
|
||
float depth = (finalY + waveHeight) / (waveHeight * 2f);
|
||
float brightness = Mathf.Clamp01(1f + ripple.brightnessBoost);
|
||
float pointScale = pointSize * (1f - depth * 0.5f);
|
||
|
||
Color baseColor = Color.Lerp(new Color(0.5f, 1f, 1f), new Color(0f, 0.5f, 1f), depth);
|
||
baseColor *= brightness;
|
||
|
||
Draw.Sphere(pos, pointScale, baseColor);
|
||
}
|
||
}
|
||
|
||
// 绘制线连接点
|
||
for (int i = 0; i < pointCount; i++)
|
||
{
|
||
for (int j = 0; j < pointCount; j++)
|
||
{
|
||
int index = i * pointCount + j;
|
||
Vector3 current = points[index];
|
||
|
||
if (j < pointCount - 1)
|
||
DrawLine(current, points[index + 1]);
|
||
|
||
if (i < pointCount - 1)
|
||
DrawLine(current, points[index + pointCount]);
|
||
}
|
||
}
|
||
|
||
// 玩家显示
|
||
Vector3 playerWorld = GridToWorld(playerPos);
|
||
playerWorld.y = points[playerPos.y * pointCount + playerPos.x].y + 0.3f;
|
||
Draw.Sphere(playerWorld, pointSize * 1.5f, Color.yellow);
|
||
}
|
||
}
|
||
|
||
private void DrawLine(Vector3 a, Vector3 b)
|
||
{
|
||
float avgY = (a.y + b.y) * 0.5f;
|
||
float lineDepth = (avgY + waveHeight) / (waveHeight * 2f);
|
||
Color lineColor = Color.Lerp(Color.cyan, new Color(0, 0.5f, 1f, 0.3f), lineDepth);
|
||
Draw.Line(a, b, lineWidth * (1f - lineDepth * 0.5f), lineColor);
|
||
}
|
||
|
||
private bool IsNearObstacle(Vector2Int pos)
|
||
{
|
||
foreach (var o in obstacles)
|
||
{
|
||
if (Vector2Int.Distance(o, pos) <= 1f)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
}
|