2026-03-25 14:46:41 +08:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using Unity.AI.Navigation;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.AI;
|
|
|
|
|
|
|
|
|
|
public class ProceduralMapGenerator : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
[Header("生成配置")]
|
|
|
|
|
public int seed = 12345;
|
|
|
|
|
public int maxPieces = 20;
|
|
|
|
|
public float gridSize = 2.0f;
|
|
|
|
|
|
|
|
|
|
[Header("预制体库")]
|
|
|
|
|
public List<GameObject> mapPiecePrefabs = new List<GameObject>();
|
|
|
|
|
public GameObject wallPiecePrefab; // 封口模块
|
|
|
|
|
|
|
|
|
|
[Header("后处理")]
|
|
|
|
|
public bool enableMeshCombining = true;
|
|
|
|
|
public bool enableNavMesh = true;
|
|
|
|
|
|
|
|
|
|
[Header("调试")]
|
|
|
|
|
public bool showDebugGizmos = true;
|
|
|
|
|
|
|
|
|
|
// 内部状态
|
|
|
|
|
private Dictionary<Vector3Int, GameObject> placedPieces = new Dictionary<Vector3Int, GameObject>();
|
|
|
|
|
private List<ConnectionPoint> openConnections = new List<ConnectionPoint>();
|
|
|
|
|
private System.Random random;
|
|
|
|
|
private Transform mapRoot;
|
|
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
{
|
|
|
|
|
GenerateMap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[ContextMenu("生成地图")]
|
|
|
|
|
public void GenerateMap()
|
|
|
|
|
{
|
|
|
|
|
ClearMap();
|
|
|
|
|
InitializeRandom();
|
|
|
|
|
|
|
|
|
|
// 创建地图根对象
|
|
|
|
|
mapRoot = new GameObject("GeneratedMap").transform;
|
|
|
|
|
mapRoot.SetParent(transform);
|
|
|
|
|
|
|
|
|
|
// 选择起始模块
|
|
|
|
|
PlaceStartPiece();
|
|
|
|
|
|
|
|
|
|
// 主生成循环
|
|
|
|
|
GenerateLoop();
|
|
|
|
|
|
|
|
|
|
// 后处理
|
|
|
|
|
if (enableMeshCombining)
|
|
|
|
|
CombineMeshes();
|
|
|
|
|
|
|
|
|
|
if (enableNavMesh)
|
|
|
|
|
BuildNavMesh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[ContextMenu("清除地图")]
|
|
|
|
|
public void ClearMap()
|
|
|
|
|
{
|
|
|
|
|
if (mapRoot != null)
|
|
|
|
|
DestroyImmediate(mapRoot.gameObject);
|
|
|
|
|
|
|
|
|
|
placedPieces.Clear();
|
|
|
|
|
openConnections.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void InitializeRandom()
|
|
|
|
|
{
|
|
|
|
|
random = new System.Random(seed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void PlaceStartPiece()
|
|
|
|
|
{
|
|
|
|
|
// 查找起始模块e
|
|
|
|
|
GameObject startPrefab = null;
|
|
|
|
|
foreach (var prefab in mapPiecePrefabs)
|
|
|
|
|
{
|
|
|
|
|
var piece = prefab.GetComponent<MapPiece>();
|
|
|
|
|
if (piece != null && piece.isStartPiece)
|
|
|
|
|
{
|
|
|
|
|
startPrefab = prefab;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (startPrefab == null && mapPiecePrefabs.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
startPrefab = mapPiecePrefabs[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (startPrefab == null)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("没有可用的起始模块预制体!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 实例化起始模块
|
|
|
|
|
var startPiece = Instantiate(startPrefab, Vector3.zero, Quaternion.identity, mapRoot);
|
|
|
|
|
var mapPiece = startPiece.GetComponent<MapPiece>();
|
|
|
|
|
|
|
|
|
|
// 记录位置
|
|
|
|
|
Vector3Int gridPos = WorldToGrid(Vector3.zero);
|
|
|
|
|
placedPieces[gridPos] = startPiece;
|
|
|
|
|
|
|
|
|
|
// 添加所有连接点到开放列表
|
|
|
|
|
openConnections.AddRange(mapPiece.GetAvailableConnectionPoints());
|
|
|
|
|
|
|
|
|
|
Debug.Log($"放置起始模块: {mapPiece.pieceName} 在位置 {gridPos}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void GenerateLoop()
|
|
|
|
|
{
|
|
|
|
|
int attempts = 0;
|
|
|
|
|
int maxAttempts = maxPieces * 3; // 最大尝试次数
|
|
|
|
|
|
|
|
|
|
while (openConnections.Count > 0 && placedPieces.Count < maxPieces && attempts < maxAttempts)
|
|
|
|
|
{
|
|
|
|
|
attempts++;
|
|
|
|
|
|
|
|
|
|
// 从开放列表中取出一个连接点
|
|
|
|
|
var connection = openConnections[0];
|
|
|
|
|
openConnections.RemoveAt(0);
|
|
|
|
|
|
|
|
|
|
// 尝试放置新模块
|
|
|
|
|
if (TryPlacePieceAtConnection(connection))
|
|
|
|
|
{
|
|
|
|
|
Debug.Log($"成功放置第 {placedPieces.Count} 个模块");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 放置失败,放置封口模块
|
|
|
|
|
PlaceWallPiece(connection);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug.Log($"生成完成!共放置 {placedPieces.Count} 个模块,尝试次数: {attempts}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryPlacePieceAtConnection(ConnectionPoint connection)
|
|
|
|
|
{
|
|
|
|
|
// 获取连接点的世界位置和方向
|
|
|
|
|
Vector3 worldPos = connection.localPosition; // 这里需要修正,应该从父对象获取
|
|
|
|
|
Vector3 worldDir = connection.localDirection;
|
|
|
|
|
|
|
|
|
|
// 计算新模块的位置
|
|
|
|
|
Vector3 newPiecePos = worldPos + worldDir * gridSize;
|
|
|
|
|
Vector3Int gridPos = WorldToGrid(newPiecePos);
|
|
|
|
|
|
|
|
|
|
// 检查位置是否已被占用
|
|
|
|
|
if (placedPieces.ContainsKey(gridPos))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// 查找合适的预制体
|
|
|
|
|
var candidates = FindCandidatePieces(connection);
|
|
|
|
|
if (candidates.Count == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// 随机选择一个候选
|
|
|
|
|
GameObject selectedPrefab = candidates[random.Next(candidates.Count)];
|
|
|
|
|
|
|
|
|
|
// 计算旋转(使新模块的连接点与当前连接点对齐)
|
|
|
|
|
Quaternion rotation = Quaternion.LookRotation(-worldDir, Vector3.up);
|
|
|
|
|
|
|
|
|
|
// 实例化新模块
|
|
|
|
|
var newPiece = Instantiate(selectedPrefab, newPiecePos, rotation, mapRoot);
|
|
|
|
|
var newMapPiece = newPiece.GetComponent<MapPiece>();
|
|
|
|
|
|
|
|
|
|
// 检查碰撞
|
|
|
|
|
if (CheckCollision(newPiece))
|
|
|
|
|
{
|
|
|
|
|
DestroyImmediate(newPiece);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 记录位置
|
|
|
|
|
placedPieces[gridPos] = newPiece;
|
|
|
|
|
|
|
|
|
|
// 添加新模块的连接点到开放列表(除了与当前连接点匹配的那个)
|
|
|
|
|
var newConnections = newMapPiece.GetAvailableConnectionPoints();
|
|
|
|
|
foreach (var newConn in newConnections)
|
|
|
|
|
{
|
|
|
|
|
// 简单的方向检查,避免添加反向连接
|
|
|
|
|
Vector3 newWorldDir = newPiece.transform.TransformDirection(newConn.localDirection);
|
|
|
|
|
if (Vector3.Dot(newWorldDir, worldDir) < -0.5f) // 大致相反方向
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
openConnections.Add(newConn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<GameObject> FindCandidatePieces(ConnectionPoint connection)
|
|
|
|
|
{
|
|
|
|
|
var candidates = new List<GameObject>();
|
|
|
|
|
|
|
|
|
|
foreach (var prefab in mapPiecePrefabs)
|
|
|
|
|
{
|
|
|
|
|
var mapPiece = prefab.GetComponent<MapPiece>();
|
|
|
|
|
if (mapPiece == null) continue;
|
|
|
|
|
|
|
|
|
|
// 简单的风格匹配(可扩展)
|
|
|
|
|
if (mapPiece.style != connection.style && connection.style != "default")
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
candidates.Add(prefab);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return candidates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void PlaceWallPiece(ConnectionPoint connection)
|
|
|
|
|
{
|
|
|
|
|
if (wallPiecePrefab == null) return;
|
|
|
|
|
|
|
|
|
|
Vector3 worldPos = connection.localPosition;
|
|
|
|
|
Vector3 worldDir = connection.localDirection;
|
|
|
|
|
Vector3 wallPos = worldPos + worldDir * gridSize * 0.5f;
|
|
|
|
|
Vector3Int gridPos = WorldToGrid(wallPos);
|
|
|
|
|
|
|
|
|
|
if (!placedPieces.ContainsKey(gridPos))
|
|
|
|
|
{
|
|
|
|
|
var wallPiece = Instantiate(wallPiecePrefab, wallPos, Quaternion.LookRotation(-worldDir), mapRoot);
|
|
|
|
|
placedPieces[gridPos] = wallPiece;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool CheckCollision(GameObject piece)
|
|
|
|
|
{
|
|
|
|
|
var collider = piece.GetComponent<Collider>();
|
|
|
|
|
if (collider == null) return false;
|
|
|
|
|
|
|
|
|
|
// 简单的重叠检测
|
|
|
|
|
var bounds = collider.bounds;
|
|
|
|
|
foreach (var placedPiece in placedPieces.Values)
|
|
|
|
|
{
|
|
|
|
|
var placedCollider = placedPiece.GetComponent<Collider>();
|
|
|
|
|
if (placedCollider != null && placedCollider.bounds.Intersects(bounds))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Vector3Int WorldToGrid(Vector3 worldPos)
|
|
|
|
|
{
|
|
|
|
|
return new Vector3Int(
|
|
|
|
|
Mathf.RoundToInt(worldPos.x / gridSize),
|
|
|
|
|
Mathf.RoundToInt(worldPos.y / gridSize),
|
|
|
|
|
Mathf.RoundToInt(worldPos.z / gridSize)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CombineMeshes()
|
|
|
|
|
{
|
|
|
|
|
// 简单的网格合并实现
|
|
|
|
|
var meshFilters = mapRoot.GetComponentsInChildren<MeshFilter>();
|
|
|
|
|
if (meshFilters.Length == 0) return;
|
|
|
|
|
|
|
|
|
|
// 按材质分组合并
|
|
|
|
|
var combineInstances = new List<CombineInstance>();
|
|
|
|
|
var materials = new List<Material>();
|
|
|
|
|
|
|
|
|
|
foreach (var meshFilter in meshFilters)
|
|
|
|
|
{
|
|
|
|
|
if (meshFilter.sharedMesh == null) continue;
|
|
|
|
|
|
|
|
|
|
var renderer = meshFilter.GetComponent<MeshRenderer>();
|
|
|
|
|
if (renderer == null || renderer.sharedMaterial == null) continue;
|
|
|
|
|
|
|
|
|
|
var combine = new CombineInstance
|
|
|
|
|
{
|
|
|
|
|
mesh = meshFilter.sharedMesh,
|
|
|
|
|
transform = meshFilter.transform.localToWorldMatrix
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
combineInstances.Add(combine);
|
|
|
|
|
|
|
|
|
|
// 禁用原始渲染器
|
|
|
|
|
renderer.enabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (combineInstances.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
// 创建合并的网格对象
|
|
|
|
|
var combinedObject = new GameObject("CombinedMesh");
|
|
|
|
|
combinedObject.transform.SetParent(mapRoot);
|
|
|
|
|
|
|
|
|
|
var combinedFilter = combinedObject.AddComponent<MeshFilter>();
|
|
|
|
|
var combinedRenderer = combinedObject.AddComponent<MeshRenderer>();
|
|
|
|
|
|
|
|
|
|
// 合并网格
|
|
|
|
|
var combinedMesh = new Mesh();
|
|
|
|
|
combinedMesh.CombineMeshes(combineInstances.ToArray());
|
|
|
|
|
combinedFilter.sharedMesh = combinedMesh;
|
|
|
|
|
|
|
|
|
|
// 设置材质(使用第一个材质)
|
|
|
|
|
if (materials.Count > 0)
|
|
|
|
|
combinedRenderer.sharedMaterial = materials[0];
|
|
|
|
|
|
|
|
|
|
Debug.Log($"网格合并完成,合并了 {combineInstances.Count} 个网格");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void BuildNavMesh()
|
|
|
|
|
{
|
|
|
|
|
var navMeshSurface = mapRoot.gameObject.GetComponent<NavMeshSurface>();
|
|
|
|
|
if (navMeshSurface == null)
|
|
|
|
|
navMeshSurface = mapRoot.gameObject.AddComponent<NavMeshSurface>();
|
|
|
|
|
|
|
|
|
|
navMeshSurface.BuildNavMesh();
|
|
|
|
|
Debug.Log("NavMesh 生成完成");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnDrawGizmos()
|
|
|
|
|
{
|
|
|
|
|
if (!showDebugGizmos) return;
|
|
|
|
|
|
|
|
|
|
// 绘制已放置模块的网格位置
|
|
|
|
|
Gizmos.color = Color.blue;
|
|
|
|
|
foreach (var kvp in placedPieces)
|
|
|
|
|
{
|
|
|
|
|
Vector3 worldPos = GridToWorld(kvp.Key);
|
|
|
|
|
Gizmos.DrawWireCube(worldPos, Vector3.one * gridSize * 0.8f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 绘制开放连接点
|
|
|
|
|
Gizmos.color = Color.red;
|
|
|
|
|
foreach (var connection in openConnections)
|
|
|
|
|
{
|
|
|
|
|
Vector3 worldPos = connection.localPosition;
|
|
|
|
|
Gizmos.DrawSphere(worldPos, 0.2f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Vector3 GridToWorld(Vector3Int gridPos)
|
|
|
|
|
{
|
|
|
|
|
return new Vector3(gridPos.x * gridSize, gridPos.y * gridSize, gridPos.z * gridSize);
|
|
|
|
|
}
|
|
|
|
|
}
|