Files
2026-07-07 03:34:56 +08:00

240 lines
9.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using UnityEngine;
namespace IndianOceanAssets.Engine2_5D
{
/// <summary>
/// 悬崖崖壁生成器 —— 根据子物体(边界点)生成垂直崖壁 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 勾勒断边轮廓
/// </summary>
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<Vector3>();
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<MeshFilter>();
MeshRenderer wallMR = wallObj.AddComponent<MeshRenderer>();
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<MeshFilter>();
MeshRenderer glowMR = glowObj.AddComponent<MeshRenderer>();
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}");
}
/// <summary>
/// 生成崖壁 mesh:每条边一个垂直四边形(双面)
/// </summary>
private Mesh GenerateWallMesh(List<Vector3> points)
{
var verts = new List<Vector3>();
var tris = new List<int>();
var uvs = new List<Vector2>();
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;
}
/// <summary>
/// 生成边缘发光 mesh:沿边界顶部的细条
/// UV.y: 1=顶部(亮), 0=底部(暗)
/// </summary>
private Mesh GenerateEdgeGlowMesh(List<Vector3> points)
{
var verts = new List<Vector3>();
var tris = new List<int>();
var uvs = new List<Vector2>();
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<Vector3>();
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]);
}
}
}
}