当前位置: 首页 > news >正文

【unity实战】使用Unity程序化生成3D随机地牢(附项目源码)

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
    • 1、理解程序生成的核心概念
    • 2、种子值的核心作用
    • 3、程序生成的实际应用
    • 4、主流程序生成技术概览
    • 5、选择合适的技术
  • 实战
    • 1、素材
    • 2、生成一面墙变换矩阵数据
    • 3、渲染墙壁
    • 4、加点随机不同的墙壁效果
    • 5、绘制四面墙壁
    • 4、在四个角落生成支柱
    • 5、生成地板
    • 6、墙壁接缝生成支柱
    • 7、为每个实例生成GameObject
    • 8、生成碰撞体
    • 9、放置随机物品
    • 10、设置随机种子
  • 源码
  • 参考
  • 专栏推荐
  • 完结

前言

1、理解程序生成的核心概念

程序生成(Procedural Generation)常被误解为随机生成,但二者有本质区别。程序生成是通过预设规则系统来创造内容,虽然结果可能看似不可预测,但实际上是确定性的。这种技术让我们能够精确控制输出内容的类型和特征。

2、种子值的核心作用

种子(Seed)是程序生成中的关键概念:

  • 作为随机数生成的起点值

  • 保证生成结果的可重复性

  • 相同的种子必然产生相同的输出序列

实际上,计算机中的"随机"函数是伪随机的确定性算法,通过对种子值进行数学变换来产生看似随机的序列。这种设计确保了结果的可靠性。

如果使用相同的种子,每次都会得到相同的随机值序列,这对于创建可重复的输出很有用。
在这里插入图片描述

3、程序生成的实际应用

程序生成在内容创作中(特别是游戏开发)有着广泛应用,其实现可分为两个层面:

  • 数据生成层 - 使用算法创造原始数据

  • 内容呈现层 - 将数据转化为可视/可交互内容

4、主流程序生成技术概览

技术典型应用场景特点
基于语法的生成建筑、城市生成使用规则系统定义结构
L-Systems植物、树木生成递归应用规则生成复杂形态
波函数坍缩地形、图案生成通过约束传播生成连贯结构
细胞自动机地形、纹理生成基于简单规则产生复杂行为
噪声函数自然地形、纹理产生有机的随机模式
二元空间分区室内空间划分递归分割空间创建布局

5、选择合适的技术

没有放之四海而皆准的解决方案,开发者应该:

  1. 广泛了解各种生成技术
  2. 将其视为工具箱中的不同工具
  3. 通过经验积累培养技术选型能力

实战

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);elsereturn 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)
{// 创建新物体的OBBOBB newOBB = new OBB(){center = position,    // 设置中心点extents = halfExtents, // 设置半尺寸rotation = rotation   // 设置旋转};// 遍历所有已放置物体的OBBforeach (var obb in existingOBBs){// 如果与任一已放置物体相交,返回trueif (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


专栏推荐

地址
【unity游戏开发入门到精通——C#篇】
【unity游戏开发入门到精通——unity通用篇】
【unity游戏开发入门到精通——unity3D篇】
【unity游戏开发入门到精通——unity2D篇】
【unity实战】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】
【unity游戏开发——模型篇】
【unity游戏开发——InputSystem】
【unity游戏开发——Animator动画】
【unity游戏开发——UGUI】
【unity游戏开发——联网篇】
【unity游戏开发——优化篇】
【unity游戏开发——shader篇】
【unity游戏开发——编辑器扩展】
【unity游戏开发——热更新】
【unity游戏开发——网络】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
在这里插入图片描述

http://www.lryc.cn/news/613539.html

相关文章:

  • 8.3.1 注册服务中心Etcd
  • 【感知机】感知机(perceptron)学习算法的对偶形式
  • Java包装类详解与应用指南
  • Caffeine 三种过期策略详解
  • Day 6: CNN卷积神经网络 - 计算机视觉的核心引擎
  • MCU中的USB
  • 论文解读:单个标点符号如何欺骗LLM,攻破AI评判系统
  • Linux总线,设备和驱动关系以及匹配机制解析
  • vue打包号的文件如何快速查找文件打包后的位置
  • Redis 编译错误:缺少静态库文件,如何解决?
  • 在NVIDIA Orin上用TensorRT对YOLO12进行多路加速并行推理时内存泄漏 (中)
  • PoE延长器——突破网络距离限制
  • 数据赋能(386)——数据挖掘——迭代过程
  • PyCharm 图标 c、m、f、F、v、p 的含义
  • 科技云报到:热链路革命:阿卡 CRM 的 GTM 定位突围
  • 健永科技工位RFID读卡器实现生产流水线物料跟踪与柔性化升级
  • 美食广场: 城市胃的便利店
  • MySQL UNION 操作符详细说明
  • 如何在GPU上安装使用Docker
  • SupChains团队:订单生产型供应链销量预测建模案例分享(六)
  • 容器之王--Docker的部署及基本操作演练
  • vLLM:彻底改变大型语言模型推理延迟和吞吐量
  • Aurora MySQL 8.0 性能分析账号创建完整指南
  • 神经网络入门指南:从零理解 PyTorch 的核心思想
  • 跨境电商增长突围:多维变局下的战略重构与技术赋能
  • 从“数字网格”到“空中交警” :星图低空云如何重构低空管理?
  • 鸿蒙 - 分享功能
  • MySql MVCC的原理总结
  • 软件加密工具-DSProtector使用说明
  • 2025年华数杯C题超详细解题思路