最终效果
文章目录
前言
1、理解程序生成的核心概念
程序生成(Procedural Generation)常被误解为随机生成,但二者有本质区别。程序生成是通过预设规则系统来创造内容,虽然结果可能看似不可预测,但实际上是确定性的。这种技术让我们能够精确控制输出内容的类型和特征。
2、种子值的核心作用
种子(Seed)是程序生成中的关键概念:
作为随机数生成的起点值
保证生成结果的可重复性
相同的种子必然产生相同的输出序列
实际上,计算机中的"随机"函数是伪随机的确定性算法,通过对种子值进行数学变换来产生看似随机的序列。这种设计确保了结果的可靠性。
如果使用相同的种子,每次都会得到相同的随机值序列,这对于创建可重复的输出很有用。
3、程序生成的实际应用
程序生成在内容创作中(特别是游戏开发)有着广泛应用,其实现可分为两个层面:
数据生成层 - 使用算法创造原始数据
内容呈现层 - 将数据转化为可视/可交互内容
4、主流程序生成技术概览
技术 | 典型应用场景 | 特点 |
---|---|---|
基于语法的生成 | 建筑、城市生成 | 使用规则系统定义结构 |
L-Systems | 植物、树木生成 | 递归应用规则生成复杂形态 |
波函数坍缩 | 地形、图案生成 | 通过约束传播生成连贯结构 |
细胞自动机 | 地形、纹理生成 | 基于简单规则产生复杂行为 |
噪声函数 | 自然地形、纹理 | 产生有机的随机模式 |
二元空间分区 | 室内空间划分 | 递归分割空间创建布局 |
5、选择合适的技术
没有放之四海而皆准的解决方案,开发者应该:
- 广泛了解各种生成技术
- 将其视为工具箱中的不同工具
- 通过经验积累培养技术选型能力
实战
1、素材
https://assetstore.unity.com/packages/3d/environments/dungeons/lite-dungeon-pack-low-poly-3d-art-by-gridness-242692
2、生成一面墙变换矩阵数据
//生成地牢函数
public class GenerateDungeon : MonoBehaviour
{
[Header("房间尺寸(X为宽度,Y为长度)")]
public Vector2 roomSize = new Vector2(10, 10);
[Header("墙壁生成相关")]
public Mesh wallMesh; // 普通墙壁网格
public Material wallMaterial; // 墙壁材质
List<Matrix4x4> _wallMatricesN; // 普通墙壁变换矩阵列表
void Start()
{
CreateWalls();
}
// 创建墙面物体的函数
void CreateWalls()
{
// 初始化存储墙面变换矩阵的列表
_wallMatricesN = new List<Matrix4x4>();
// 计算需要的墙面数量:房间宽度除以单面墙宽度,至少1面墙
int wallCount = Mathf.Max(1, (int)(roomSize.x / wallMesh.bounds.size.x));
// 计算每面墙的缩放比例:使墙面正好填满房间宽度
float scale = (roomSize.x / wallCount) / wallMesh.bounds.size.x;
// 循环生成每面墙的变换矩阵
for (int i = 0; i < wallCount; i++)
{
// 计算墙面位置:
// 1. 从房间左边界开始(-roomSize.x/2)
// 2. 加上半面墙宽度(wallSize.x*scale/2)
// 3. 加上当前墙的偏移量(i*scale*wallSize.x)
// Y轴位置为房间上边界(+roomSize.y/2)
Vector3 position = transform.position +
new Vector3(-roomSize.x / 2 + wallMesh.bounds.size.x * scale / 2 +
i * scale * wallMesh.bounds.size.x,
0,
+roomSize.y / 2);
// 使用父物体的旋转
Quaternion rotation = transform.rotation;
// 设置缩放:X轴按计算比例缩放,Y和Z轴保持原大小
Vector3 scaleVec = new Vector3(scale, 1, 1);
// 创建变换矩阵(位置/旋转/缩放)
Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scaleVec);
// 将矩阵添加到列表
_wallMatricesN.Add(matrix);
}
}
}
3、渲染墙壁
在 Unity 中,Graphics.DrawMeshInstanced 是一种高性能的渲染技术,特别适用于需要绘制大量相同或相似网格(如地牢墙壁、植被、子弹等)的场景。
void Update()
{
RenderWalls();
}
// 渲染墙面物体的函数
void RenderWalls()
{
// 渲染普通墙体
if (_wallMatricesN != null && _wallMatricesN.Count > 0)
{
Graphics.DrawMeshInstanced(
wallMesh,
0,
wallMaterial,
_wallMatricesN.ToArray(),
_wallMatricesN.Count
);
}
}
效果
4、加点随机不同的墙壁效果
public Mesh wallMeshB; // 特殊墙壁网格
List<Matrix4x4> _wallMatricesNB; // 特殊墙壁变换矩阵列表
void CreateWalls()
{
// 初始化列表,用于存储不同类型墙壁的变换矩阵
_wallMatricesN = new List<Matrix4x4>(); // 普通墙壁
_wallMatricesNB = new List<Matrix4x4>(); // 类型B墙壁
// 计算沿x轴需要放置的墙壁数量,至少为1面墙
int wallCount = Mathf.Max(1, (int)(roomSize.x / wallMesh.bounds.size.x));
// 计算墙壁的缩放比例,使墙壁正好填满房间的x轴长度
float scale = (roomSize.x / wallCount) / wallMesh.bounds.size.x;
// 循环创建每一面墙壁
for (int i = 0; i < wallCount; i++)
{
// 计算墙壁位置:从房间左侧开始,等间距排列
var t = transform.position = new Vector3(
-roomSize.x / 2 + wallMesh.bounds.size.x * scale / 2 + i * scale * wallMesh.bounds.size.x, // x位置
0, // y位置
-roomSize.y / 2); // z位置
// 使用当前物体的旋转
var r = transform.rotation;
// 设置缩放:x轴根据计算的比例缩放,y和z保持原样
var s = new Vector3(scale, 1, 1);
// 创建变换矩阵(位置、旋转、缩放)
var mat = Matrix4x4.TRS(t, r, s);
// 随机决定墙壁类型(0或1)
var rand = Random.Range(0, 2);
// 根据随机数将墙壁分配到不同的列表
if (rand < 1)
{
_wallMatricesN.Add(mat); // 类型0 - 普通墙壁
}
else if (rand < 2)
{
_wallMatricesNB.Add(mat); // 类型1 - B类墙壁
}
}
}
// 渲染墙面物体的函数
void RenderWalls()
{
// 。。。
// 渲染B类型墙体
if (_wallMatricesNB != null && _wallMatricesNB.Count > 0)
{
Graphics.DrawMeshInstanced(
wallMeshB,
0,
wallMaterial,
_wallMatricesNB.ToArray(),
_wallMatricesNB.Count
);
}
}
效果
5、绘制四面墙壁
每面墙壁的生成非常类似,我们可以把生成单面墙壁进行封装
// 创建所有墙壁
void CreateWalls()
{
_wallMatricesN = new List<Matrix4x4>();
_wallMatricesNB = new List<Matrix4x4>();
// 生成四条边的墙体
CreateWallEdge(Vector3.forward, roomSize.y / 2, false); // 北墙
CreateWallEdge(Vector3.back, roomSize.y / 2, false); // 南墙
CreateWallEdge(Vector3.right, roomSize.x / 2, true); // 东墙
CreateWallEdge(Vector3.left, roomSize.x / 2, true); // 西墙
}
// 创建单边墙体
void CreateWallEdge(Vector3 direction, float offset, bool isVertical)
{
// 确定墙体方向
Vector3 axis = isVertical ? Vector3.up : Vector3.zero;
float rotationAngle = isVertical ? 90f : 0f;
Vector2 size = isVertical ? new Vector2(roomSize.y, 0) : new Vector2(roomSize.x, 0);
// 计算墙体数量和缩放比例
int wallCount = Mathf.Max(1, (int)(size.x / wallMesh.bounds.size.x));
float scale = (size.x / wallCount) / wallMesh.bounds.size.x;
// 创建该边的所有墙体
for (int i = 0; i < wallCount; i++)
{
// 计算墙体位置
Vector3 position = transform.position + direction * offset;
if (isVertical)
{
position += Vector3.forward * (-size.x / 2 + wallMesh.bounds.size.x * scale / 2 + i * scale * wallMesh.bounds.size.x);
}
else
{
position += Vector3.right * (-size.x / 2 + wallMesh.bounds.size.x * scale / 2 + i * scale * wallMesh.bounds.size.x);
}
// 设置旋转和缩放
Quaternion rotation = transform.rotation * Quaternion.AngleAxis(rotationAngle, axis);
Vector3 scaleVec = new Vector3(scale, 1, 1);
// 创建变换矩阵
Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scaleVec);
// 随机分配墙体类型
if (Random.Range(0, 2) == 0)
_wallMatricesN.Add(matrix);
else
_wallMatricesNB.Add(matrix);
}
}
效果
4、在四个角落生成支柱
#region 支柱生成相关
[Header("支柱生成相关")]
public Mesh pillarMesh; // 支柱网格
public Material pillarMaterial; // 支柱材质
List<Matrix4x4> _pillarMatrices; // 支柱变换矩阵列表
//在四个角落添加支柱
void CreatePillars()
{
_pillarMatrices = new List<Matrix4x4>();
// 定义房间四个角落的位置
Vector3[] corners = new Vector3[]
{
new Vector3(-roomSize.x/2, 0, -roomSize.y/2), // 西南角
new Vector3(-roomSize.x/2, 0, roomSize.y/2), // 西北角
new Vector3(roomSize.x/2, 0, -roomSize.y/2), // 东南角
new Vector3(roomSize.x/2, 0, roomSize.y/2) // 东北角
};
// 为每个角落创建支柱
foreach (Vector3 corner in corners)
{
Vector3 position = transform.position + corner;
Quaternion rotation = transform.rotation;
Vector3 scale = Vector3.one; // 支柱默认缩放
Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scale);
_pillarMatrices.Add(matrix);
}
}
// 渲染支柱
void RenderPillars()
{
if (_pillarMatrices != null && _pillarMatrices.Count > 0)
{
Graphics.DrawMeshInstanced(
pillarMesh,
0,
pillarMaterial,
_pillarMatrices.ToArray(),
_pillarMatrices.Count
);
}
}
#endregion
效果
5、生成地板
生成地板可以使用两种模式——缩放方式
和平铺方式
,具体选择其一即可
#region 地板生成相关
[Header("地板生成相关")]
public Mesh floorMesh; // 地板网格
public Material floorMaterial; // 地板材质
List<Matrix4x4> _floorMatrices; // 地板变换矩阵列表
// 创建地板(缩放方式)
void createFloorScale()
{
_floorMatrices = new List<Matrix4x4>();
// 计算地板位置(房间中心)
Vector3 position = transform.position;
Quaternion rotation = transform.rotation;
// 根据房间尺寸缩放地板
Vector3 scale = new Vector3(roomSize.x, 1, roomSize.y);
Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scale);
_floorMatrices.Add(matrix);
}
// 创建地板(平铺方式)
void CreateFloorTile()
{
_floorMatrices = new List<Matrix4x4>();
// 获取地板网格的原始尺寸
float floorWidth = floorMesh.bounds.size.x;
float floorLength = floorMesh.bounds.size.z;
// 计算X轴和Z轴需要的地板数量
int floorCountX = Mathf.Max(1, Mathf.CeilToInt(roomSize.x / floorWidth));
int floorCountZ = Mathf.Max(1, Mathf.CeilToInt(roomSize.y / floorLength)); // 注意:RoomSize.y对应的是Z轴长度
// 计算实际缩放比例(使地板正好铺满房间)
float scaleX = (roomSize.x / floorCountX) / floorWidth;
float scaleZ = (roomSize.y / floorCountZ) / floorLength;
// 起始位置(左下角)
Vector3 startPos = transform.position + new Vector3(-roomSize.x / 2 + floorWidth * scaleX / 2, 0, -roomSize.y / 2 + floorLength * scaleZ / 2);
// 平铺地板
for (int x = 0; x < floorCountX; x++)
{
for (int z = 0; z < floorCountZ; z++)
{
// 计算每块地板的位置
Vector3 position = startPos + new Vector3(
x * floorWidth * scaleX,
0,
z * floorLength * scaleZ
);
// 保持默认旋转
Quaternion rotation = transform.rotation;
// 设置缩放(保持Y轴不变)
Vector3 scale = new Vector3(scaleX, 1, scaleZ);
// 创建变换矩阵
Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, scale);
_floorMatrices.Add(matrix);
}
}
}
// 渲染地板
void RenderFloor()
{
if (_floorMatrices != null && _floorMatrices.Count > 0)
{
Graphics.DrawMeshInstanced(
floorMesh,
0,
floorMaterial,
_floorMatrices.ToArray(),
_floorMatrices.Count
);
}
}
#endregion
效果
6、墙壁接缝生成支柱
#region 墙壁接缝支柱
[Header("墙壁接缝支柱")]
public Mesh junctionPillarMesh; // 接缝支柱网格
public Material junctionPillarMaterial; // 接缝支柱材质
List<Matrix4x4> _junctionPillarMatrices; // 接缝支柱变换矩阵
void AddWallJunctionPillars()
{
if (junctionPillarMesh == null) return;
_junctionPillarMatrices = new List<Matrix4x4>();
// 获取墙体网格尺寸(假设所有墙体网格尺寸相同)
float wallWidth = wallMesh.bounds.size.x;
// 计算水平和垂直墙体的数量
int horizontalWallCount = Mathf.Max(1, (int)(roomSize.x / wallWidth));
int verticalWallCount = Mathf.Max(1, (int)(roomSize.y / wallWidth));
// 计算墙体缩放比例
float hScale = (roomSize.x / horizontalWallCount) / wallWidth;
float vScale = (roomSize.y / verticalWallCount) / wallWidth;
// 添加南北墙的接缝支柱(东西走向的墙)
for (int i = 1; i < horizontalWallCount; i++)
{
// 北墙接缝
float xPos = -roomSize.x / 2 + i * wallWidth * hScale;
AddPillar(transform.position + new Vector3(xPos, 0, roomSize.y / 2));
// 南墙接缝
AddPillar(transform.position + new Vector3(xPos, 0, -roomSize.y / 2));
}
// 添加东西墙的接缝支柱(南北走向的墙)
for (int i = 1; i < verticalWallCount; i++)
{
// 东墙接缝
float zPos = -roomSize.y / 2 + i * wallWidth * vScale;
AddPillar(transform.position + new Vector3(roomSize.x / 2, 0, zPos));
// 西墙接缝
AddPillar(transform.position + new Vector3(-roomSize.x / 2, 0, zPos));
}
}
// 在指定位置添加支柱
void AddPillar(Vector3 position, float scaleMultiplier = 1f)
{
Quaternion rotation = transform.rotation;
Vector3 scale = Vector3.one * scaleMultiplier;
_junctionPillarMatrices.Add(Matrix4x4.TRS(position, rotation, scale));
}
// 渲染接缝支柱
void RenderJunctionPillars()
{
if (_junctionPillarMatrices != null && _junctionPillarMatrices.Count > 0)
{
Graphics.DrawMeshInstanced(
junctionPillarMesh,
0,
junctionPillarMaterial,
_junctionPillarMatrices.ToArray(),
_junctionPillarMatrices.Count
);
}
}
#endregion
效果,这里我没有特殊支柱的素材,就还有用四个角落的支柱代替好了
7、为每个实例生成GameObject
这个一般只在需要的使用,如果你想更灵活的操作自己的对象,比如实现可破坏的墙壁效果等等,这里我以墙体为例,其他大家自行扩展即可,方法都一样。
#region 为每个实例生成GameObject
// 修改后的创建方法示例(以墙体为例)
List<GameObject> _wallInstances = new List<GameObject>();
void CreateWallsWithColliders()
{
// 销毁旧实例(如果存在)
foreach (var wall in _wallInstances) Destroy(wall);
_wallInstances.Clear();
// 生成带碰撞体的墙体
foreach (var matrix in _wallMatricesN)
{
GameObject wall = new GameObject("WallInstance");
wall.transform.SetPositionAndRotation(matrix.GetPosition(), matrix.rotation);
wall.transform.localScale = matrix.lossyScale;
// 添加网格组件
MeshFilter filter = wall.AddComponent<MeshFilter>();
filter.mesh = wallMesh;
MeshRenderer renderer = wall.AddComponent<MeshRenderer>();
renderer.material = wallMaterial;
// 添加碰撞体(根据需求选择类型)
MeshCollider collider = wall.AddComponent<MeshCollider>();
collider.sharedMesh = wallMesh;
collider.convex = false; // 对于静态墙体设为false
_wallInstances.Add(wall);
}
}
#endregion
效果
8、生成碰撞体
#region 碰撞体生成
private GameObject _collidersParent;
void CreateCombinedCollider(List<Matrix4x4> matrices, Mesh mesh)
{
if (mesh == null) return;
if (_collidersParent == null)
{
_collidersParent = new GameObject("Colliders");
_collidersParent.transform.SetParent(transform);
}
// 合并所有网格
CombineInstance[] combines = new CombineInstance[matrices.Count];
for (int i = 0; i < matrices.Count; i++)
{
combines[i].mesh = mesh;
combines[i].transform = matrices[i];
}
Mesh combinedMesh = new Mesh();
combinedMesh.CombineMeshes(combines);
// 创建碰撞体父物体
GameObject colliderGameObject = new GameObject(mesh.name + "Colliders");
colliderGameObject.transform.SetParent(_collidersParent.transform);
MeshCollider collider = colliderGameObject.AddComponent<MeshCollider>();
collider.sharedMesh = combinedMesh;
}
#endregion
效果
9、放置随机物品
放置随机物品就比较复杂了,不过我加了很多详细的注释,大家可以查看理解。
新增道具配置类
using UnityEngine;
// 道具配置类
[System.Serializable]
public class DungeonProp
{
public GameObject prefab; // 预制体
public enum PositionType { Wall, Corner, Middle, Anywhere }// 放置位置类型
public PositionType positionType; // 放置位置类型
[Range(0, 1)] public float spawnProbability; // 生成概率(0~1)
public int minCount; // 最小数量
public int maxCount; // 最大数量
public Vector3 offset; //放置偏移量
public bool randomRotation; // 是否随机旋转
}
编写道具放置系统逻辑,我这里使用了OBB (SAT) (有向包围盒),精准的检测物品是否相交,且保持良好的性能。
不了解什么是OBB的可以参考:【unity知识】unity使用AABB(轴对齐包围盒)和OBB(定向包围盒)优化碰撞检测
#region 道具放置系统
[Header("道具放置系统")]
public DungeonProp[] dungeonProps; // 可放置的道具配置
private float _wallThickness; // 墙体一半厚度
private GameObject _propsParent; //父物体
// 放置道具
void PlaceProps()
{
if (dungeonProps == null || dungeonProps.Length == 0) return;
_wallThickness = wallMesh.bounds.extents.z;
// 计算实际可用区域(考虑墙体厚度)
float usableWidth = roomSize.x - 2 * _wallThickness;
float usableHeight = roomSize.y - 2 * _wallThickness;
// 计算房间边界(考虑墙体厚度)
float minX = transform.position.x - usableWidth / 2;
float maxX = transform.position.x + usableWidth / 2;
float minZ = transform.position.z - usableHeight / 2;
float maxZ = transform.position.z + usableHeight / 2;
// 创建道具父物体
if (_propsParent == null)
{
_propsParent = new GameObject("PropsParent");
_propsParent.transform.SetParent(transform);
}
// 存储已放置物体的OBB信息
List<OBB> placedOBBs = new List<OBB>();
foreach (var prop in dungeonProps)
{
// 计算实际要放置的数量
int count = Random.Range(prop.minCount, prop.maxCount + 1);
for (int i = 0; i < count; i++)
{
// 根据概率决定是否生成
if (Random.value > prop.spawnProbability) continue;
Vector3 position = Vector3.zero;
Quaternion rotation = Quaternion.identity;
Vector3 halfExtents = Vector3.zero;
// 根据位置类型确定位置和朝向
switch (prop.positionType)
{
case DungeonProp.PositionType.Wall:
if (!TryFindWallPosition(prop.prefab, minX, maxX, minZ, maxZ,
out position, out rotation, out halfExtents))
continue;
break;
case DungeonProp.PositionType.Corner:
if (!TryFindCornerPosition(prop.prefab, minX, maxX, minZ, maxZ,
out position, out rotation, out halfExtents))
continue;
break;
case DungeonProp.PositionType.Middle:
if (!TryFindMiddlePosition(prop.prefab, minX, maxX, minZ, maxZ,
out position, out rotation, out halfExtents))
continue;
break;
case DungeonProp.PositionType.Anywhere:
if (!TryFindAnyPosition(prop.prefab, minX, maxX, minZ, maxZ,
out position, out rotation, out halfExtents))
continue;
break;
}
// 应用随机旋转
if (prop.randomRotation)
{
rotation = Quaternion.Euler(0, Random.Range(0, 360), 0);
}
// 检查碰撞并放置道具
if (!CheckOverlap(position, halfExtents, rotation, placedOBBs))
{
GameObject propInstance = Instantiate(prop.prefab, position + prop.offset, rotation);
propInstance.transform.SetParent(_propsParent.transform);
placedOBBs.Add(new OBB()
{
center = position,
extents = halfExtents,
rotation = rotation
});
}
}
}
}
// 尝试在墙边放置道具
bool TryFindWallPosition(GameObject prefab, float minX, float maxX, float minZ, float maxZ,
out Vector3 position, out Quaternion rotation, out Vector3 halfExtents)
{
position = Vector3.zero;
rotation = Quaternion.identity;
halfExtents = Vector3.zero;
// 获取预制体大小
Bounds bounds = GetPrefabBounds(prefab);
if (bounds.size == Vector3.zero) return false;
//获取包围盒(Bounds)的半尺寸
halfExtents = bounds.extents;
// 随机选择一面墙
int wallIndex = Random.Range(0, 4);
switch (wallIndex)
{
case 0: // 北墙 (z最大)
position = new Vector3(
Random.Range(minX + halfExtents.x, maxX - halfExtents.x),
0,
maxZ - halfExtents.z
);
rotation = Quaternion.Euler(0, 180, 0); // 面朝南
break;
case 1: // 南墙 (z最小)
position = new Vector3(
Random.Range(minX + halfExtents.x, maxX - halfExtents.x),
0,
minZ + halfExtents.z
);
rotation = Quaternion.identity; // 面朝北
break;
case 2: // 东墙 (x最大)
position = new Vector3(
maxX - halfExtents.z,
0,
Random.Range(minZ + halfExtents.x, maxZ - halfExtents.x)
);
rotation = Quaternion.Euler(0, 270, 0); // 面朝西
break;
case 3: // 西墙 (x最小)
position = new Vector3(
minX + halfExtents.z,
0,
Random.Range(minZ + halfExtents.x, maxZ - halfExtents.x)
);
rotation = Quaternion.Euler(0, 90, 0); // 面朝东
break;
}
return true;
}
// 尝试在角落放置道具
bool TryFindCornerPosition(GameObject prefab, float minX, float maxX, float minZ, float maxZ,
out Vector3 position, out Quaternion rotation, out Vector3 halfExtents)
{
position = Vector3.zero;
rotation = Quaternion.identity;
halfExtents = Vector3.zero;
// 获取预制体大小
Bounds bounds = GetPrefabBounds(prefab);
if (bounds.size == Vector3.zero) return false;
//获取包围盒(Bounds)的半尺寸
halfExtents = bounds.extents;
// 随机选择一个墙角
int cornerIndex = Random.Range(0, 4);
float cornerOffset = _wallThickness + Mathf.Max(halfExtents.x, halfExtents.z);
switch (cornerIndex)
{
case 0: // 西北角
position = new Vector3(
minX + cornerOffset,
0,
maxZ - cornerOffset
);
rotation = Quaternion.Euler(0, 225, 0); // 面向东南
break;
case 1: // 东北角
position = new Vector3(
maxX - cornerOffset,
0,
maxZ - cornerOffset
);
rotation = Quaternion.Euler(0, 315, 0); // 面向西南
break;
case 2: // 西南角
position = new Vector3(
minX + cornerOffset,
0,
minZ + cornerOffset
);
rotation = Quaternion.Euler(0, 135, 0); // 面向东北
break;
case 3: // 东南角
position = new Vector3(
maxX - cornerOffset,
0,
minZ + cornerOffset
);
rotation = Quaternion.Euler(0, 45, 0); // 面向西北
break;
}
return true;
}
// 尝试在中间区域放置道具
bool TryFindMiddlePosition(GameObject prefab, float minX, float maxX, float minZ, float maxZ,
out Vector3 position, out Quaternion rotation, out Vector3 halfExtents)
{
position = Vector3.zero;
rotation = Quaternion.identity;
halfExtents = Vector3.zero;
// 获取预制体大小
Bounds bounds = GetPrefabBounds(prefab);
if (bounds.size == Vector3.zero) return false;
//获取包围盒(Bounds)的半尺寸
halfExtents = bounds.extents;
// 中间区域(避开墙边)
float safeMargin = Mathf.Max(halfExtents.x, halfExtents.z) * 2;
position = new Vector3(
Random.Range(minX + safeMargin, maxX - safeMargin),
0,
Random.Range(minZ + safeMargin, maxZ - safeMargin)
);
return true;
}
// 尝试在任意位置放置道具
bool TryFindAnyPosition(GameObject prefab, float minX, float maxX, float minZ, float maxZ,
out Vector3 position, out Quaternion rotation, out Vector3 halfExtents)
{
// 50%概率放在墙边,25%概率放在墙角,25%概率放在中间
float rand = Random.value;
if (rand < 0.5f)
return TryFindWallPosition(prefab, minX, maxX, minZ, maxZ, out position, out rotation, out halfExtents);
else if (rand < 0.75f)
return TryFindCornerPosition(prefab, minX, maxX, minZ, maxZ, out position, out rotation, out halfExtents);
else
return TryFindMiddlePosition(prefab, minX, maxX, minZ, maxZ, out position, out rotation, out halfExtents);
}
// 获取预制体的包围盒
Bounds GetPrefabBounds(GameObject prefab)
{
Renderer renderer = prefab.GetComponentInChildren<Renderer>();
if (renderer != null) return renderer.bounds;
// 如果预制体没有渲染器,尝试使用碰撞器
Collider collider = prefab.GetComponentInChildren<Collider>();
if (collider != null) return collider.bounds;
// 默认大小
return new Bounds(Vector3.zero, Vector3.one * 0.5f);
}
/// <summary>
/// 检查新物体是否与已放置物体发生OBB重叠
/// </summary>
/// <param name="position">新物体的中心位置</param>
/// <param name="halfExtents">新物体的半尺寸</param>
/// <param name="rotation">新物体的旋转</param>
/// <param name="existingOBBs">已放置物体的OBB列表</param>
/// <returns>true表示有重叠,false表示无重叠</returns>
bool CheckOverlap(Vector3 position, Vector3 halfExtents, Quaternion rotation, List<OBB> existingOBBs)
{
// 创建新物体的OBB
OBB newOBB = new OBB()
{
center = position, // 设置中心点
extents = halfExtents, // 设置半尺寸
rotation = rotation // 设置旋转
};
// 遍历所有已放置物体的OBB
foreach (var obb in existingOBBs)
{
// 如果与任一已放置物体相交,返回true
if (newOBB.Intersects(obb))
return true;
}
// 没有发现重叠
return false;
}
#endregion
}
配置数据
效果,实现在不同位置,按概率生成不同物品
10、设置随机种子
Random.InitState
是 Unity 引擎中用于初始化随机数生成器的方法,它的作用是设定随机数生成的种子(Seed),从而控制随机序列的起始点。它的核心作用是:
确定性随机:使用相同的种子时,Random 产生的随机数序列会完全一致。
Random.InitState(123); // 初始化种子为123 Debug.Log(Random.Range(0, 100)); // 固定输出某个值(如42)
每次运行程序,只要种子是 123,第一个 Random.Range(0, 100) 必定返回相同的值。
取消真正的随机性:默认情况下,Unity 使用系统时间作为种子,结果不可预测。而 InitState 会覆盖这一行为,使随机结果可重现。
所以我们可以给我们的项目添加随机种子功能
[Header("随机种子")]
[SerializeField] private int _seed; // 随机种子
[SerializeField] private bool _useSeed; // 是否使用指定种子
void Start()
{
// 初始化随机种子
if (_useSeed)
{
Random.InitState(_seed); // 使用指定的种子
}
else
{
int randomSeed = Random.Range(1, 1000000); // 生成随机种子
Random.InitState(randomSeed);
Debug.Log(randomSeed); // 输出种子以便重现
}
//...
}
我们使用相同的种子,一直会生成相同的地牢
源码
https://gitee.com/unity_data/unity6-urpgeneration-dungeon
参考
https://www.youtube.com/watch?v=PhLcNhK9aro
专栏推荐
完结
好了,我是向宇
,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!