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

发布于:2025-08-09 ⋅ 阅读:(22) ⋅ 点赞:(0)

最终效果

在这里插入图片描述

前言

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);
    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


专栏推荐

地址
【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,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

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


网站公告

今日签到

点亮在社区的每一天
去签到