using UnityEngine; using Shapes; using System.Collections.Generic; using UnityEngine.UI; [ExecuteAlways] public class LakeLineDrawer : ImmediateModeShapeDrawer { [Header("场设置")] public float fieldSize = 80f; public Vector2 fieldOffset = Vector2.zero; public int pointCount = 80; public float pointSize = 0.1f; public float lineWidth = 0.03f; [Header("颜色设置")] public Color lakeDarkColor = new Color(0f, 0.5f, 1f); // 湖面低处颜色 public Color lakeLightColor = new Color(0.5f, 1f, 1f, 0.3f); // 湖面高处颜色 public Color edgeDarkColor = new Color(0f, 0.5f, 1f); // 边缘低处颜色 public Color edgeLightColor = new Color(0.5f, 1f, 1f, 0.3f); // 边缘高处颜色 public float edgeLineLength = 1f; // 边缘线的长度 [Header("UI控制")] public Button upButton; public Button downButton; public Button leftButton; public Button rightButton; public float moveStep = 1f; // 每次移动的步长 [Header("目标点设置")] public List targetPoints = new List(); public float targetSize = 0.5f; public float rotationSpeed = 30f; // 旋转速度(度/秒) public float floatAmplitude = 0.2f; // 起伏幅度 public float floatFrequency = 2f; // 起伏频率 public float targetLineWidth = 0.02f; // 目标点线框的宽度 public float targetHeightOffset = 0.5f; // 目标点距离湖面的高度偏移 public Color targetFillColor = Color.red; // 目标点实体颜色 public Color targetWireColor = Color.white; // 目标点线框颜色 public float wireScale = 1.05f; // 线框相对于实体的大小比例 [Header("可视区域")] public float visibleAreaSize = 20f; // 波动参数,用于生成波动效果 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 = 0.8f; // 适中的持续时间 public float rippleSpreadSpeed = 4f; // 适中的传播速度 public float rippleSharpness = 4f; // 适中的锐度 public float rippleMaxRadius = 3f; // 新增:涟漪最大半径 [Header("障碍物设置")] public List obstacles = new List(); public float obstacleHeight = 2f; // 障碍物高度 public float obstacleWidth = 0.5f; // 障碍物宽度 public Color obstacleColor = Color.white; // 障碍物颜色 public float collisionEffectDuration = 0.5f; // 碰撞效果持续时间 public float collisionEffectSize = 1f; // 碰撞效果大小 public Color collisionEffectColor = Color.white; // 碰撞效果颜色 public int collisionEffectRingCount = 3; // 涟漪环的数量 public int collisionEffectDashCount = 12; // 每个环的虚线数量 private Vector3[] points; // 存储每个点是否在可视区域内的标记 private bool[] validPoints; private float time; private List ripples = new List(); [System.Serializable] public class TargetPoint { public Vector2 gridPosition; // 网格位置(0到pointCount-1) public TargetType type; public Color color = Color.white; } public enum TargetType { Sphere, Cube, Torus } private struct Ripple { public Vector2 position; public float startTime; } private struct RippleResult { public float heightOffset; public float brightnessBoost; } [System.Serializable] public class Obstacle { public Vector2 gridPosition; // 网格位置(0到pointCount-1) [HideInInspector]public float visibility = 0f; // 可见度(0-1) [HideInInspector]public float lastRippleTime = -1f; // 最后一次被涟漪影响的时间 [HideInInspector]public float riseProgress = 0f; // 升起进度(0-1) [HideInInspector]public float colorFadeProgress = 0f; // 颜色淡入进度(0-1) } private struct CollisionEffect { public Vector2 position; public Vector2 direction; // 撞击方向 public float startTime; } private List collisionEffects = new List(); private void Start() { // 添加按钮点击事件监听 if (upButton != null) upButton.onClick.AddListener(MoveUp); if (downButton != null) downButton.onClick.AddListener(MoveDown); if (leftButton != null) leftButton.onClick.AddListener(MoveLeft); if (rightButton != null) rightButton.onClick.AddListener(MoveRight); } private void OnDestroy() { // 移除按钮点击事件监听 if (upButton != null) upButton.onClick.RemoveListener(MoveUp); if (downButton != null) downButton.onClick.RemoveListener(MoveDown); if (leftButton != null) leftButton.onClick.RemoveListener(MoveLeft); if (rightButton != null) rightButton.onClick.RemoveListener(MoveRight); } private void MoveUp() { float halfVisible = visibleAreaSize * 0.5f; float maxOffset = fieldSize * 0.5f - halfVisible * 0.6f; // 允许超出20% if (fieldOffset.y < maxOffset) { fieldOffset.y += moveStep; fieldOffset.y = Mathf.Clamp(fieldOffset.y, -maxOffset, maxOffset); } } private void MoveDown() { float halfVisible = visibleAreaSize * 0.5f; float maxOffset = fieldSize * 0.5f - halfVisible * 0.8f; // 允许超出20% if (fieldOffset.y > -maxOffset) { fieldOffset.y -= moveStep; fieldOffset.y = Mathf.Clamp(fieldOffset.y, -maxOffset, maxOffset); } } private void MoveLeft() { float halfVisible = visibleAreaSize * 0.5f; float maxOffset = fieldSize * 0.5f - halfVisible * 0.8f; // 允许超出20% if (fieldOffset.x > -maxOffset) { fieldOffset.x -= moveStep; fieldOffset.x = Mathf.Clamp(fieldOffset.x, -maxOffset, maxOffset); } } private void MoveRight() { float halfVisible = visibleAreaSize * 0.5f; float maxOffset = fieldSize * 0.5f - halfVisible * 0.8f; // 允许超出20% if (fieldOffset.x < maxOffset) { fieldOffset.x += moveStep; fieldOffset.x = Mathf.Clamp(fieldOffset.x, -maxOffset, maxOffset); } } private void HandleUIInput() { // 由于现在使用按钮事件,这个方法可以留空或删除 } private void Update() { // 如果是播放状态,则更新时间;否则设为0 time = Application.isPlaying ? Time.time * timeScale : 0f; HandleMouseInput(); HandleUIInput(); CleanExpiredRipples(); CleanExpiredCollisionEffects(); UpdateObstacleVisibility(); } private void HandleMouseInput() { if (Application.isPlaying && Input.GetMouseButtonDown(0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit)) { Vector3 hitPos = hit.point; ripples.Add(new Ripple { position = new Vector2(hitPos.x, hitPos.z), startTime = Time.time }); } } } private void CleanExpiredRipples() { ripples.RemoveAll(r => Time.time - r.startTime > rippleDuration); } private void CleanExpiredCollisionEffects() { collisionEffects.RemoveAll(e => Time.time - e.startTime > collisionEffectDuration); } // 计算涟漪对某个点的影响,返回该点的偏移量和亮度提升 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; float spread = elapsed * rippleSpreadSpeed; // 涟漪扩展的距离 float dist = Vector2.Distance(new Vector2(x, z), ripple.position); // 当前点与涟漪中心的距离 // 如果距离超过最大半径,跳过这个涟漪的影响 if (dist > rippleMaxRadius) continue; // 计算涟漪的时间影响,t表示涟漪的衰减程度 float t = Mathf.Clamp01(elapsed / rippleDuration); float ease = Mathf.Sin(t * Mathf.PI); // 使用正弦函数使衰减效果平滑 // 计算涟漪对当前点的高度影响 float spike = Mathf.Exp(-Mathf.Pow(dist - spread, 2f) * rippleSharpness); rippleOffset += spike * rippleStrength * ease; // 计算涟漪对当前点的亮度提升 float bright = Mathf.Exp(-Mathf.Pow((dist - spread) * 0.5f, 2f)); brightness += bright * ease; // 检查涟漪是否与障碍物碰撞 CheckObstacleCollision(ripple.position, spread, currentTime); } return new RippleResult { heightOffset = rippleOffset, brightnessBoost = brightness }; } private void CheckObstacleCollision(Vector2 ripplePos, float spread, float currentTime) { float step = fieldSize / (pointCount - 1); float halfField = fieldSize * 0.5f; foreach (var obstacle in obstacles) { // 将网格位置转换为世界坐标 float x = -halfField + obstacle.gridPosition.x * step + fieldOffset.x; float z = -halfField + obstacle.gridPosition.y * step + fieldOffset.y; Vector2 worldPos = new Vector2(x, z); float dist = Vector2.Distance(ripplePos, worldPos); if (dist <= rippleMaxRadius && Mathf.Abs(dist - spread) < obstacleWidth * 0.5f) { obstacle.lastRippleTime = currentTime; if (obstacle.visibility <= 0f) { obstacle.visibility = 0.2f; obstacle.riseProgress = 0f; obstacle.colorFadeProgress = 0f; } else if (obstacle.visibility < 1f) { obstacle.riseProgress = Mathf.Max(obstacle.riseProgress, obstacle.visibility); } // 计算撞击方向(从涟漪中心指向障碍物) Vector2 hitDirection = (worldPos - ripplePos).normalized; collisionEffects.Add(new CollisionEffect { position = worldPos, direction = hitDirection, startTime = currentTime }); } } } // 计算某个点的基础高度,基于噪声函数模拟波浪 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; } // 绘制湖面 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 halfField = fieldSize * 0.5f; float halfVisible = visibleAreaSize * 0.5f; if (points == null || points.Length != pointCount * pointCount) points = new Vector3[pointCount * pointCount]; if (validPoints == null || validPoints.Length != pointCount * pointCount) validPoints = new bool[pointCount * pointCount]; CalculatePointPositions(step, halfField, halfVisible); DrawPointsAndLines(step, halfField, halfVisible); DrawEdgeLines(halfField, halfVisible); DrawTargetPoints(); DrawObstacles(); // 绘制障碍物 DrawCollisionEffects(); // 绘制碰撞效果 } } // 计算每个点的位置和涟漪效果 private void CalculatePointPositions(float step, float halfField, float halfVisible) { // 遍历每个点,计算其位置和涟漪影响 for (int i = 0; i < pointCount; i++) { for (int j = 0; j < pointCount; j++) { // 计算当前点的位置(x,z) float x = -halfField + j * step + fieldOffset.x; float z = -halfField + i * step + fieldOffset.y; // 判断该点是否在可视区域内 bool inside = Mathf.Abs(x) <= halfVisible && Mathf.Abs(z) <= halfVisible; int index = i * pointCount + j; // 计算当前点的索引 validPoints[index] = inside; // 设置该点是否有效 if (!inside) continue; // 获取基础波浪高度,并计算涟漪影响 float baseHeight = GetHeight(x, z, time); RippleResult ripple = GetRippleEffect(x, z, Time.time); float finalY = baseHeight + ripple.heightOffset; // 创建点的坐标 Vector3 pos = new Vector3(x, finalY, z); points[index] = pos; // 存储计算得到的点 // 计算亮度:基于高度,越高越亮 float heightT = Mathf.InverseLerp(-waveHeight, waveHeight, finalY); float brightness = Mathf.Clamp01(1f + ripple.brightnessBoost); // 计算亮度,防止超出范围 Color baseColor = Color.Lerp(lakeDarkColor, lakeLightColor, heightT); baseColor *= brightness; // 绘制当前点 Draw.Sphere(pos, pointSize * (1f - heightT * 0.5f), baseColor); } } } // 绘制点与点之间的连线 private void DrawPointsAndLines(float step, float halfField, float halfVisible) { for (int i = 0; i < pointCount; i++) { for (int j = 0; j < pointCount; j++) { int index = i * pointCount + j; if (!validPoints[index]) continue; Vector3 current = points[index]; if (j < pointCount - 1 && validPoints[index + 1]) { Vector3 next = points[index + 1]; DrawLineBetweenPoints(current, next, halfField, halfVisible); } // 绘制与下边点的连线 if (i < pointCount - 1 && validPoints[index + pointCount]) { Vector3 next = points[index + pointCount]; DrawLineBetweenPoints(current, next, halfField, halfVisible); } } } } // 绘制两点之间的连线,加入深度和颜色的渐变 private void DrawLineBetweenPoints(Vector3 current, Vector3 next, float halfField, float halfVisible) { // 计算连线的平均高度 float avgY = (current.y + next.y) * 0.5f; float heightT = Mathf.InverseLerp(-waveHeight, waveHeight, avgY); // 计算连线的颜色,依赖于高度 Color lineColor = Color.Lerp(lakeDarkColor, lakeLightColor, heightT); lineColor.a *= 0.8f; // 设置透明度 // 绘制连线 Draw.Line(current, next, lineWidth * (1f - heightT * 0.5f), lineColor); } private void DrawEdgeLines(float halfField, float halfVisible) { // 绘制湖面边缘的向下线条(无扰动) float step = fieldSize / (pointCount - 1); float halfEdgeLineLength = edgeLineLength * 0.5f; // 设置边缘线长度 // 1. 绘制 X 轴的边缘线(左边和右边) for (float z = -halfField; z <= halfField; z += step) { float worldZ = z + fieldOffset.y; float x = halfField + fieldOffset.x; // 右边 DrawEdgeLine(x, worldZ, halfVisible); x = -halfField + fieldOffset.x; // 左边 DrawEdgeLine(x, worldZ, halfVisible); } // 2. 绘制 Z 轴的边缘线(上边和下边) for (float x = -halfField; x <= halfField; x += step) { float worldX = x + fieldOffset.x; float z = halfField + fieldOffset.y; // 上边 DrawEdgeLine(worldX, z, halfVisible); z = -halfField + fieldOffset.y; // 下边 DrawEdgeLine(worldX, z, halfVisible); } } private void DrawEdgeLine(float x, float z, float halfVisible) { // 检查点是否在可视区域内 if (Mathf.Abs(x) > halfVisible || Mathf.Abs(z) > halfVisible) { return; } // 计算湖面高度作为起始点的Y坐标 float startY = GetHeight(x - fieldOffset.x, z - fieldOffset.y, time); // 获取湖面高度 // 设置边缘线的起始点 Vector3 start = new Vector3(x, startY, z); // 设置边缘线的终止点,向下延伸一定的长度 Vector3 end = new Vector3(x, startY - edgeLineLength, z); // 向下延伸的线条 // 设置颜色,基于高度 float heightT = Mathf.InverseLerp(-waveHeight, waveHeight, startY); Color lineColor = Color.Lerp(edgeDarkColor, edgeLightColor, heightT); lineColor.a *= 0.8f; // 设置透明度 // 绘制边缘线 Draw.Line(start, end, lineWidth * 0.5f, lineColor); } // 绘制目标点 private void DrawTargetPoints() { float step = fieldSize / (pointCount - 1); float halfField = fieldSize * 0.5f; float halfVisible = visibleAreaSize * 0.5f; foreach (var target in targetPoints) { // 将网格位置转换为世界坐标 float x = -halfField + target.gridPosition.x * step + fieldOffset.x; float z = -halfField + target.gridPosition.y * step + fieldOffset.y; // 检查是否在可视区域内 if (Mathf.Abs(x) > halfVisible || Mathf.Abs(z) > halfVisible) { continue; } // 获取该点的高度 float height = GetHeight(x, z, time); // 添加额外的起伏效果和高度偏移 float floatOffset = Mathf.Sin(time * floatFrequency) * floatAmplitude; Vector3 position = new Vector3(x, height + floatOffset + targetHeightOffset, z); // 计算旋转 float rotation = time * rotationSpeed; Quaternion rot = Quaternion.Euler(0, rotation, 0); // 绘制外层立方体线框 DrawWireCube(position, rot, targetSize * wireScale, targetWireColor); // 根据类型绘制内部形状 switch (target.type) { case TargetType.Sphere: Draw.Sphere(position, targetSize * 0.8f, targetFillColor); break; case TargetType.Cube: Draw.Cube(position, rot, targetSize * 0.8f, targetFillColor); break; case TargetType.Torus: Draw.Torus(position, rot, targetSize * 0.8f, targetSize * 0.3f, targetFillColor); break; } } } private void DrawWireCube(Vector3 position, Quaternion rotation, float size, Color color) { Vector3[] vertices = new Vector3[] { new Vector3(-size, -size, -size), new Vector3(size, -size, -size), new Vector3(size, size, -size), new Vector3(-size, size, -size), new Vector3(-size, -size, size), new Vector3(size, -size, size), new Vector3(size, size, size), new Vector3(-size, size, size) }; // 应用旋转 for (int i = 0; i < vertices.Length; i++) { vertices[i] = rotation * vertices[i] + position; } // 绘制边 int[] edges = new int[] { 0,1, 1,2, 2,3, 3,0, // 底面 4,5, 5,6, 6,7, 7,4, // 顶面 0,4, 1,5, 2,6, 3,7 // 连接线 }; for (int i = 0; i < edges.Length; i += 2) { Draw.Line(vertices[edges[i]], vertices[edges[i+1]], targetLineWidth, color); } } private void DrawObstacles() { float step = fieldSize / (pointCount - 1); float halfField = fieldSize * 0.5f; float halfVisible = visibleAreaSize * 0.5f; foreach (var obstacle in obstacles) { // 在编辑器中始终显示 if (Application.isPlaying && obstacle.visibility <= 0f) continue; // 将网格位置转换为世界坐标 float x = -halfField + obstacle.gridPosition.x * step + fieldOffset.x; float z = -halfField + obstacle.gridPosition.y * step + fieldOffset.y; // 检查是否在可视区域内 if (Mathf.Abs(x) > halfVisible || Mathf.Abs(z) > halfVisible) continue; // 获取该点的高度 float height = GetHeight(x, z, time); // 计算颜色和升起效果 Color currentColor = obstacleColor; if (Application.isPlaying) { currentColor.a *= obstacle.visibility * obstacle.colorFadeProgress; } float riseHeight = obstacleHeight * (Application.isPlaying ? obstacle.riseProgress : 1f); // 绘制障碍物 Vector3 basePos = new Vector3(x, height, z); float halfWidth = obstacleWidth * 0.5f; // 绘制底部 Draw.Line(basePos + new Vector3(-halfWidth, 0, -halfWidth), basePos + new Vector3(halfWidth, 0, -halfWidth), lineWidth, currentColor); Draw.Line(basePos + new Vector3(halfWidth, 0, -halfWidth), basePos + new Vector3(halfWidth, 0, halfWidth), lineWidth, currentColor); Draw.Line(basePos + new Vector3(halfWidth, 0, halfWidth), basePos + new Vector3(-halfWidth, 0, halfWidth), lineWidth, currentColor); Draw.Line(basePos + new Vector3(-halfWidth, 0, halfWidth), basePos + new Vector3(-halfWidth, 0, -halfWidth), lineWidth, currentColor); // 绘制顶部(考虑升起效果) Vector3 topPos = basePos + Vector3.up * riseHeight; Draw.Line(topPos + new Vector3(-halfWidth, 0, -halfWidth), topPos + new Vector3(halfWidth, 0, -halfWidth), lineWidth, currentColor); Draw.Line(topPos + new Vector3(halfWidth, 0, -halfWidth), topPos + new Vector3(halfWidth, 0, halfWidth), lineWidth, currentColor); Draw.Line(topPos + new Vector3(halfWidth, 0, halfWidth), topPos + new Vector3(-halfWidth, 0, halfWidth), lineWidth, currentColor); Draw.Line(topPos + new Vector3(-halfWidth, 0, halfWidth), topPos + new Vector3(-halfWidth, 0, -halfWidth), lineWidth, currentColor); // 绘制连接线 Draw.Line(basePos + new Vector3(-halfWidth, 0, -halfWidth), topPos + new Vector3(-halfWidth, 0, -halfWidth), lineWidth, currentColor); Draw.Line(basePos + new Vector3(halfWidth, 0, -halfWidth), topPos + new Vector3(halfWidth, 0, -halfWidth), lineWidth, currentColor); Draw.Line(basePos + new Vector3(halfWidth, 0, halfWidth), topPos + new Vector3(halfWidth, 0, halfWidth), lineWidth, currentColor); Draw.Line(basePos + new Vector3(-halfWidth, 0, halfWidth), topPos + new Vector3(-halfWidth, 0, halfWidth), lineWidth, currentColor); } } private void DrawCollisionEffects() { foreach (var effect in collisionEffects) { float elapsed = Time.time - effect.startTime; float t = elapsed / collisionEffectDuration; float scale = Mathf.Lerp(0f, collisionEffectSize, t); float alpha = Mathf.Lerp(1f, 0f, t); Color effectColor = collisionEffectColor; effectColor.a *= alpha; // 使用效果位置的世界坐标 Vector3 effectPos = new Vector3(effect.position.x, GetHeight(effect.position.x, effect.position.y, time), effect.position.y); // 设置虚线样式 Draw.UseDashes = true; Draw.DashStyle = DashStyle.RelativeDashes(DashType.Basic, 0.5f, 0.5f, DashSnapping.Tiling, 0f, 0f); // 绘制同心圆涟漪,但根据撞击方向调整形状 for (int i = 0; i < collisionEffectRingCount; i++) { float ringScale = scale * (i + 1) / collisionEffectRingCount; float ringAlpha = alpha * (1f - (float)i / collisionEffectRingCount); Color ringColor = effectColor; ringColor.a *= ringAlpha; // 计算椭圆的长轴和短轴 float majorAxis = ringScale; float minorAxis = ringScale * 0.7f; // 短轴略短,形成椭圆 // 计算旋转角度(使长轴与撞击方向对齐) float angle = Mathf.Atan2(effect.direction.y, effect.direction.x) * Mathf.Rad2Deg; Quaternion rotation = Quaternion.Euler(0, angle, 0); // 绘制椭圆环 int segments = 32; Vector3 prevPoint = Vector3.zero; for (int j = 0; j <= segments; j++) { float angle2 = (float)j / segments * Mathf.PI * 2; float x = Mathf.Cos(angle2) * majorAxis; float z = Mathf.Sin(angle2) * minorAxis; Vector3 point = rotation * new Vector3(x, 0, z) + effectPos; if (j > 0) { Draw.Line(prevPoint, point, lineWidth * (1f - t), ringColor); } prevPoint = point; } } // 恢复默认样式 Draw.UseDashes = false; } } private void UpdateObstacleVisibility() { float currentTime = Application.isPlaying ? Time.time : 0f; foreach (var obstacle in obstacles) { // 在编辑器中,如果没有被涟漪影响过,保持可见 if (!Application.isPlaying) { // obstacle.visibility = 1f; // obstacle.riseProgress = 1f; // obstacle.colorFadeProgress = 1f; continue; } // 如果超过6秒没有被涟漪影响,开始逐渐消失 if (currentTime - obstacle.lastRippleTime > 6f) { float fadeSpeed = Time.deltaTime; obstacle.visibility = Mathf.Max(0f, obstacle.visibility - fadeSpeed); // 同时降低升起高度 obstacle.riseProgress = Mathf.Max(0f, obstacle.riseProgress - fadeSpeed); } else if (obstacle.visibility > 0f) { // 如果正在显现,继续增加可见度 obstacle.visibility = Mathf.Min(1f, obstacle.visibility + Time.deltaTime * 0.5f); // 同时增加升起高度 obstacle.riseProgress = Mathf.Min(1f, obstacle.riseProgress + Time.deltaTime * 2f); } // 更新颜色淡入进度 if (obstacle.visibility > 0f) { obstacle.colorFadeProgress = Mathf.Min(1f, obstacle.colorFadeProgress + Time.deltaTime * 3f); } else { obstacle.colorFadeProgress = 0f; } } } }