using System.Collections.Generic; using UnityEngine; namespace IndianOceanAssets.Engine2_5D { /// /// 悬崖崖壁生成器 —— 根据子物体(边界点)生成垂直崖壁 mesh + 边缘发光条带。 /// /// 使用方式: /// 1. 场景中创建空物体,挂上此脚本(放在地面高度,Y=0) /// 2. 在此物体下创建若干子空物体作为边界角点 /// (至少 3 个,顺时针或逆时针排列,例如 4 个摆成矩形) /// 3. Inspector 里指定 wallMaterial(建议 URP Unlit 暗色材质) /// 4. 右键组件 > Build Cliff Walls 生成 /// 5. 修改边界点后再次 Build 会自动清除旧的重建 /// /// 生成结果(在 "Generated_CliffWalls" 子物体下): /// - CliffWall:垂直崖壁面,从边界点往下延伸 wallHeight /// - EdgeGlow:顶部细条,使用 AbyssEdgeGlow shader 勾勒断边轮廓 /// public class CliffWallBuilder : MonoBehaviour { [Header("崖壁参数")] [Tooltip("崖壁往下延伸的高度(世界单位)")] [SerializeField] private float wallHeight = 5f; [Header("边缘发光线")] [Tooltip("是否生成顶部发光条带")] [SerializeField] private bool generateEdgeGlow = true; [Tooltip("发光条带的高度(世界单位)")] [SerializeField] private float edgeGlowHeight = 0.2f; [Tooltip("发光颜色")] [SerializeField] private Color edgeGlowColor = new Color(0.2f, 0.6f, 0.9f, 1f); [Tooltip("发光亮度")] [SerializeField, Range(0.1f, 5f)] private float edgeGlowIntensity = 1.5f; [Tooltip("渐变:越大上边越亮下边越暗")] [SerializeField, Range(0.1f, 3f)] private float edgeGlowGradient = 1f; [Header("材质")] [Tooltip("崖壁材质(建议 URP Unlit,深色,Render Face = Both)")] [SerializeField] private Material wallMaterial; [Tooltip("边缘发光材质(使用 AbyssEdgeGlow shader)。留空则自动创建。")] [SerializeField] private Material edgeGlowMaterial; [Tooltip("边缘发光着色器(留空则尝试 Shader.Find 兜底;建议拖入 Assets/Light/shaders/AbyssEdgeGlow.shader 以避免构建裁剪)")] [SerializeField] private Shader edgeGlowShader; private const string GeneratedName = "Generated_CliffWalls"; [ContextMenu("Build Cliff Walls")] public void Build() { // 收集子物体作为边界点(跳过已生成的容器) var points = new List(); for (int i = 0; i < transform.childCount; i++) { Transform child = transform.GetChild(i); if (child.name == GeneratedName) continue; points.Add(child.position); } if (points.Count < 2) { Debug.LogWarning("[CliffWallBuilder] 需要至少 2 个子物体作为边界点(不含生成物)。" + "请在此物体下添加空物体作为角点。"); return; } // 清除旧生成物 Transform old = transform.Find(GeneratedName); if (old != null) { if (Application.isPlaying) Destroy(old.gameObject); else DestroyImmediate(old.gameObject); } // 创建容器 GameObject container = new GameObject(GeneratedName); container.transform.SetParent(transform, false); // ---- 崖壁 mesh ---- GameObject wallObj = new GameObject("CliffWall"); wallObj.transform.SetParent(container.transform, false); MeshFilter wallMF = wallObj.AddComponent(); MeshRenderer wallMR = wallObj.AddComponent(); Mesh wallMesh = GenerateWallMesh(points); wallMesh.name = "CliffWall_Mesh"; wallMF.sharedMesh = wallMesh; wallMR.sharedMaterial = wallMaterial; // ---- 边缘发光 mesh ---- if (generateEdgeGlow) { GameObject glowObj = new GameObject("EdgeGlow"); glowObj.transform.SetParent(container.transform, false); MeshFilter glowMF = glowObj.AddComponent(); MeshRenderer glowMR = glowObj.AddComponent(); Mesh glowMesh = GenerateEdgeGlowMesh(points); glowMesh.name = "EdgeGlow_Mesh"; glowMF.sharedMesh = glowMesh; if (edgeGlowMaterial != null) { glowMR.sharedMaterial = edgeGlowMaterial; } else { // 尝试自动创建 AbyssEdgeGlow 材质 Shader edgeShader = edgeGlowShader != null ? edgeGlowShader : Shader.Find("IndianOcean/AbyssEdgeGlow"); if (edgeShader != null) { Material mat = new Material(edgeShader); mat.SetColor("_Color", edgeGlowColor); mat.SetFloat("_Intensity", edgeGlowIntensity); mat.SetFloat("_Gradient", edgeGlowGradient); glowMR.sharedMaterial = mat; } else { Debug.LogWarning("[CliffWallBuilder] 找不到 IndianOcean/AbyssEdgeGlow shader。" + "请手动创建材质并指定到 edgeGlowMaterial。"); } } } Debug.Log($"[CliffWallBuilder] 已生成 {points.Count} 条崖壁边,高度 {wallHeight}"); } /// /// 生成崖壁 mesh:每条边一个垂直四边形(双面) /// private Mesh GenerateWallMesh(List points) { var verts = new List(); var tris = new List(); var uvs = new List(); int n = points.Count; for (int i = 0; i < n; i++) { Vector3 p0 = transform.InverseTransformPoint(points[i]); Vector3 p1 = transform.InverseTransformPoint(points[(i + 1) % n]); Vector3 bl = p0 + new Vector3(0, -wallHeight, 0); Vector3 br = p1 + new Vector3(0, -wallHeight, 0); Vector3 tl = p0; Vector3 tr = p1; int b = verts.Count; verts.Add(bl); verts.Add(br); verts.Add(tl); verts.Add(tr); uvs.Add(new Vector2(0, 0)); uvs.Add(new Vector2(1, 0)); uvs.Add(new Vector2(0, 1)); uvs.Add(new Vector2(1, 1)); // 正面 tris.Add(b + 0); tris.Add(b + 2); tris.Add(b + 1); tris.Add(b + 1); tris.Add(b + 2); tris.Add(b + 3); // 背面(反绕,双面可见) tris.Add(b + 0); tris.Add(b + 1); tris.Add(b + 2); tris.Add(b + 1); tris.Add(b + 3); tris.Add(b + 2); } Mesh mesh = new Mesh { name = "CliffWall" }; mesh.SetVertices(verts); mesh.SetUVs(0, uvs); mesh.SetTriangles(tris, 0); mesh.RecalculateNormals(); mesh.RecalculateBounds(); return mesh; } /// /// 生成边缘发光 mesh:沿边界顶部的细条 /// UV.y: 1=顶部(亮), 0=底部(暗) /// private Mesh GenerateEdgeGlowMesh(List points) { var verts = new List(); var tris = new List(); var uvs = new List(); int n = points.Count; for (int i = 0; i < n; i++) { Vector3 p0 = transform.InverseTransformPoint(points[i]); Vector3 p1 = transform.InverseTransformPoint(points[(i + 1) % n]); Vector3 tl = p0; // 顶(地面高度) Vector3 tr = p1; Vector3 bl = p0 + new Vector3(0, -edgeGlowHeight, 0); // 底(往下) Vector3 br = p1 + new Vector3(0, -edgeGlowHeight, 0); int b = verts.Count; verts.Add(bl); verts.Add(br); verts.Add(tl); verts.Add(tr); uvs.Add(new Vector2(0, 0)); // 底部暗 uvs.Add(new Vector2(1, 0)); uvs.Add(new Vector2(0, 1)); // 顶部亮 uvs.Add(new Vector2(1, 1)); tris.Add(b + 0); tris.Add(b + 2); tris.Add(b + 1); tris.Add(b + 1); tris.Add(b + 2); tris.Add(b + 3); tris.Add(b + 0); tris.Add(b + 1); tris.Add(b + 2); tris.Add(b + 1); tris.Add(b + 3); tris.Add(b + 2); } Mesh mesh = new Mesh { name = "EdgeGlow" }; mesh.SetVertices(verts); mesh.SetUVs(0, uvs); mesh.SetTriangles(tris, 0); mesh.RecalculateNormals(); mesh.RecalculateBounds(); return mesh; } private void OnDrawGizmosSelected() { // 画出边界点和连线,方便编辑 Gizmos.color = new Color(0.2f, 0.6f, 0.9f, 0.8f); var points = new List(); for (int i = 0; i < transform.childCount; i++) { Transform child = transform.GetChild(i); if (child.name == GeneratedName) continue; points.Add(child.position); Gizmos.DrawWireSphere(child.position, 0.3f); } if (points.Count < 2) return; Gizmos.color = new Color(0.2f, 0.6f, 0.9f, 0.4f); for (int i = 0; i < points.Count; i++) { Gizmos.DrawLine(points[i], points[(i + 1) % points.Count]); } } } }