237 lines
9.7 KiB
C#
237 lines
9.7 KiB
C#
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;
|
||
|
||
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 = 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]);
|
||
}
|
||
}
|
||
}
|
||
}
|