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 obstacles = new List(); 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 ripples = new List(); 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; } }