【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏3(附项目源码)

news2024/9/28 1:17:05

文章目录

  • 先本文看看最终效果
  • 前言
  • 二叉空间分割算法
  • 房间优先生成
  • 使用走廊连接各个房间
  • BSP和随机游走
  • 源码
  • 完结

先本文看看最终效果

在这里插入图片描述
在这里插入图片描述

前言

前两期我们使用了随机游走算法已经实现了地牢的生成,本期再说另外一种生成地牢的方法,使用二叉空间分割算法,可以用来生成规则的房间或者不规则的地牢。

二叉空间分割算法

修改ProceduralGenerationAlgorithms,实现了二叉空间分割算法,用于将初始空间进行分割以创建房间

//二叉空间分割算法
public static List<BoundsInt> BinarySpacePartitioning(BoundsInt spaceToSplit, int minWidth, int minHeight)
{
    Queue<BoundsInt> roomsQueue = new Queue<BoundsInt>(); // 创建队列来保存分割的空间
    List<BoundsInt> roomsList = new List<BoundsInt>(); // 创建列表来保存最终的房间
    roomsQueue.Enqueue(spaceToSplit); // 将初始空间加入队列中

    while (roomsQueue.Count > 0)
    {
        var room = roomsQueue.Dequeue(); // 取出队列中的一个空间

        if (room.size.y >= minHeight && room.size.x >= minWidth) // 如果空间的宽度和高度都大于等于最小值
        {
            if (Random.value < 0.5f) // 随机选择垂直或水平分割
            {
                if (room.size.y >= minHeight * 2) // 如果空间的高度大于等于最小高度的两倍,则进行水平分割
                {
                    SplitHorizontally(minHeight, roomsQueue, room); // 水平分割空间
                }
                else if (room.size.x >= minWidth * 2) // 如果空间的宽度大于等于最小宽度的两倍,则进行垂直分割
                {
                    SplitVertically(minWidth, roomsQueue, room); // 垂直分割空间
                }
                else if (room.size.x >= minWidth && room.size.y >= minHeight) // 如果空间的宽度和高度都大于等于最小值,则将其添加到房间列表中
                {
                    roomsList.Add(room);
                }
            }
            else
            {
                if (room.size.x >= minWidth * 2) // 如果空间的宽度大于等于最小宽度的两倍,则进行垂直分割
                {
                    SplitVertically(minWidth, roomsQueue, room); // 垂直分割空间
                }
                else if (room.size.y >= minHeight * 2) // 如果空间的高度大于等于最小高度的两倍,则进行水平分割
                {
                    SplitHorizontally(minHeight, roomsQueue, room); // 水平分割空间
                }
                else if (room.size.x >= minWidth && room.size.y >= minHeight) // 如果空间的宽度和高度都大于等于最小值,则将其添加到房间列表中
                {
                    roomsList.Add(room);
                }
            }
        }
    }

    return roomsList; // 返回最终的房间列表
}

// 垂直分割空间
private static void SplitVertically(int minWidth, Queue<BoundsInt> roomsQueue, BoundsInt room)
{
    var xSplit = Random.Range(1, room.size.x); // 随机选择分割点的x坐标
    BoundsInt room1 = new BoundsInt(room.min, new Vector3Int(xSplit, room.size.y, room.size.z));
    BoundsInt room2 = new BoundsInt(new Vector3Int(room.min.x + xSplit, room.min.y, room.min.z),
        new Vector3Int(room.size.x - xSplit, room.size.y, room.size.z));
    roomsQueue.Enqueue(room1); // 添加分割后的两个新空间到队列中
    roomsQueue.Enqueue(room2);
}

// 水平分割空间
private static void SplitHorizontally(int minHeight, Queue<BoundsInt> roomsQueue, BoundsInt room)
{
    var ySplit = Random.Range(1, room.size.y); // 随机选择分割点的y坐标
    BoundsInt room1 = new BoundsInt(room.min, new Vector3Int(room.size.x, ySplit, room.size.z));
    BoundsInt room2 = new BoundsInt(new Vector3Int(room.min.x, room.min.y + ySplit, room.min.z),
        new Vector3Int(room.size.x, room.size.y - ySplit, room.size.z));
    roomsQueue.Enqueue(room1); // 添加分割后的两个新空间到队列中
    roomsQueue.Enqueue(room2);
}

房间优先生成

新增RoomFirstDungeonGenerator,这段代码实现了一个基于房间的地牢生成器,通过使用偏移量,我们可以在房间的边界周围保留一定的间距,使得房间之间更加清晰可辨,避免它们彼此连接或重叠。

public class RoomFirstDungeonGenerator : SimpleRandomWalkDungeonGenerator
{
    [SerializeField, Header("最小房间宽度和高度")]
    private int minRoomWidth = 4, minRoomHeight = 4;
    
    [SerializeField, Header("地牢宽度和高度")]
    private int dungeonWidth = 20, dungeonHeight = 20;
    
    [SerializeField, Header("偏移量")]
    [Range(0, 10)]
    private int offset = 1;

    protected override void RunProceduralGeneration()
    {
        CreateRooms(); // 创建房间
    }

    private void CreateRooms()
    {
        var roomsList = ProceduralGenerationAlgorithms.BinarySpacePartitioning(new BoundsInt((Vector3Int)startPosition,
            new Vector3Int(dungeonWidth, dungeonHeight, 0)), minRoomWidth, minRoomHeight); // 使用二叉空间分割算法创建房间列表
        HashSet<Vector2Int> floor = new HashSet<Vector2Int>(); // 用于保存地板坐标的集合
        floor = CreateSimpleRooms(roomsList); // 创建简单房间
        tilemapVisualizer.PaintFloorTiles(floor); // 绘制地板砖块
        WallGenerator.CreateWalls(floor, tilemapVisualizer); // 创建墙壁
    }

    private HashSet<Vector2Int> CreateSimpleRooms(List<BoundsInt> roomsList)
    {
        HashSet<Vector2Int> floor = new HashSet<Vector2Int>(); // 用于保存地板坐标的集合
        foreach (var room in roomsList) // 遍历房间列表
        {
            for (int col = offset; col < room.size.x - offset; col++) // 遍历列
            {
                for (int row = offset; row < room.size.y - offset; row++) // 遍历行
                {
                    Vector2Int position = (Vector2Int)room.min + new Vector2Int(col, row); // 计算地板坐标
                    floor.Add(position); // 添加地板坐标到集合中
                }
            }
        }
        return floor; // 返回地板集合
    }
}

挂载脚本,配置参数
在这里插入图片描述
效果
在这里插入图片描述

使用走廊连接各个房间

修改RoomFirstDungeonGenerator

private void CreateRooms()
{
    var roomsList = ProceduralGenerationAlgorithms.BinarySpacePartitioning(new BoundsInt((Vector3Int)startPosition,
        new Vector3Int(dungeonWidth, dungeonHeight, 0)), minRoomWidth, minRoomHeight); // 使用二叉空间分割算法创建房间列表
    HashSet<Vector2Int> floor = new HashSet<Vector2Int>(); // 用于保存地板坐标的集合
    floor = CreateSimpleRooms(roomsList); // 创建简单房间

    List<Vector2Int> roomCenters = new List<Vector2Int>(); // 存储所有房间中心坐标的列表
    foreach (var room in roomsList) // 遍历所有房间
    {
        roomCenters.Add((Vector2Int)Vector3Int.RoundToInt(room.center)); // 将房间中心坐标转换为Vector2Int类型后添加到列表中
    }
    HashSet<Vector2Int> corridors = ConnectRooms(roomCenters); // 连接所有房间,得到走廊的坐标集合
    floor.UnionWith(corridors); // 将走廊坐标集合和地板坐标集合合并

    tilemapVisualizer.PaintFloorTiles(floor); // 绘制地板砖块
    WallGenerator.CreateWalls(floor, tilemapVisualizer); // 创建墙壁
}

// 连接所有房间并返回地板坐标集合
private HashSet<Vector2Int> ConnectRooms(List<Vector2Int> roomCenters)
{
    HashSet<Vector2Int> corridors = new HashSet<Vector2Int>();
    var currentRoomCenter = roomCenters[Random.Range(0, roomCenters.Count)]; // 随机选择一个房间中心作为当前房间
    roomCenters.Remove(currentRoomCenter); // 从房间中心列表中移除当前房间中心
    while (roomCenters.Count > 0) // 当还有未连接的房间时循环
    {
        Vector2Int closest = FindClosestPointTo(currentRoomCenter, roomCenters); // 找到距离当前房间中心最近的房间中心
        roomCenters.Remove(closest); // 从房间中心列表中移除最近的房间中心
        HashSet<Vector2Int> newCorridor = CreateCorridor(currentRoomCenter, closest); // 创建当前房间中心和最近房间中心之间的连接通道
        currentRoomCenter = closest; // 将最近房间中心设置为当前房间中心
        corridors.UnionWith(newCorridor); // 将新创建的通道添加到总通道集合中
    }
    return corridors; // 返回所有通道的地板坐标集合
}

// 寻找当前房间中心到最近房间的路径上的点
private Vector2Int FindClosestPointTo(Vector2Int currentRoomCenter, List<Vector2Int> roomCenters)
{
    Vector2Int closest = Vector2Int.zero; // 最近的点的坐标
    float distance = float.MaxValue; // 初始距离设为最大值
    foreach (var position in roomCenters) // 遍历所有的房间中心
    {
        float currentDistance = Vector2.Distance(position, currentRoomCenter); // 计算当前点与当前房间中心之间的距离
        if (currentDistance < distance) // 如果当前距离比之前记录的最小距离小
        {
            distance = currentDistance; // 更新最小距离
            closest = position; // 更新最近的点的坐标
        }
    }
    return closest; // 返回最近的点的坐标
}

// 创建连接两个房间的走廊
private HashSet<Vector2Int> CreateCorridor(Vector2Int currentRoomCenter, Vector2Int destination)
{
    HashSet<Vector2Int> corridor = new HashSet<Vector2Int>(); // 存储走廊坐标的集合
    var position = currentRoomCenter; // 初始位置设为当前房间中心
    corridor.Add(position); // 将初始位置添加到走廊坐标集合中
    while (position.y != destination.y) // 沿着y轴移动直到到达目标位置的y坐标
    {
        if (destination.y > position.y) // 如果目标位置的y坐标大于当前位置的y坐标
        {
            position += Vector2Int.up; // 向上移动一格
        }
        else if (destination.y < position.y) // 如果目标位置的y坐标小于当前位置的y坐标
        {
            position += Vector2Int.down; // 向下移动一格
        }
        corridor.Add(position); // 将新位置添加到走廊坐标集合中
    }
    while (position.x != destination.x) // 沿着x轴移动直到到达目标位置的x坐标
    {
        if (destination.x > position.x) // 如果目标位置的x坐标大于当前位置的x坐标
        {
            position += Vector2Int.right; // 向右移动一格
        }
        else if (destination.x < position.x) // 如果目标位置的x坐标小于当前位置的x坐标
        {
            position += Vector2Int.left; // 向左移动一格
        }
        corridor.Add(position); // 将新位置添加到走廊坐标集合中
    }
    return corridor; // 返回走廊坐标的集合
}

生成效果
在这里插入图片描述

BSP和随机游走

前面生成的房间都是方形的,我们加点随机元素

修改RoomFirstDungeonGenerator

private void CreateRooms()
{
	//。。。

	// floor = CreateSimpleRooms(roomsList); // 创建简单房间
	if (randomWalkRooms)
	{
	    floor = CreateRoomsRandomly(roomsList);// 创建随机房间
	}
	else
	{
	    floor = CreateSimpleRooms(roomsList);// 创建简单房间
	}
	
	//。。。
}

private HashSet<Vector2Int> CreateRoomsRandomly(List<BoundsInt> roomsList)
{
    HashSet<Vector2Int> floor = new HashSet<Vector2Int>(); // 存储地板坐标的集合
    for (int i = 0; i < roomsList.Count; i++) // 遍历所有房间
    {
        var roomBounds = roomsList[i]; // 获取当前房间的边界
        var roomCenter = new Vector2Int(Mathf.RoundToInt(roomBounds.center.x), Mathf.RoundToInt(roomBounds.center.y)); // 计算当前房间的中心坐标
        var roomFloor = RunRandomWalk(randomWalkParameters, roomCenter); // 使用随机步行算法获取当前房间的地板坐标集合
        foreach (var position in roomFloor) // 遍历当前房间的地板坐标集合
        {
            // 如果坐标在房间边界加上偏移量的范围内,将其添加到地板坐标集合中
            if (position.x >= (roomBounds.xMin + offset) && position.x <= (roomBounds.xMax - offset) && position.y >= (roomBounds.yMin - offset) && position.y <= (roomBounds.yMax - offset))
            {
                floor.Add(position);
            }
        }
    }
    return floor; // 返回地板坐标的集合
}

配置参数
在这里插入图片描述

效果,现在就更像是地牢了
在这里插入图片描述

源码

源码会放在本项目最后一篇

完结

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

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1235991.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python数据结构与算法】--- 递归算法应用-五行代码速解汉诺塔问题.

&#x1f308;个人主页: Aileen_0v0 &#x1f525;系列专栏:PYTHON数据结构与算法学习系列专栏&#x1f4ab;"没有罗马,那就自己创造罗马~" 汉诺塔 两层汉诺塔的演示 三层汉诺塔的走法演示 我不知道有没有朋友跟我一样有一个疑问,如果我们顶端的先放到中间柱子呢?…

【算法】二分查找-20231122

这里写目录标题 一、1089. 复写零二、917. 仅仅反转字母三、88. 合并两个有序数组四、283. 移动零 一、1089. 复写零 提示 简单 266 相关企业 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&a…

visionOS空间计算实战开发教程Day 2 使用RealityKit显示3D素材

我们在​​Day1​​中学习了如何创建一个visionOS应用&#xff0c;但在第一个Demo应用中我们的界面内容还是2D的&#xff0c;看起来和其它应用并没有什么区别。接下来我们先学习如何展示3D素材&#xff0c;苹果为方便开发人员&#xff0c;推出了RealityKit&#xff0c;接下来看…

Qml使用cpp文件的信号槽

文章目录 一、C文件Demo二、使用步骤1. 初始化C文件和QML文件&#xff0c;并建立信号槽2.在qml中调用 一、C文件Demo Q_INVOKABLE是一个Qt元对象系统中的宏&#xff0c;用于将C函数暴露给QML引擎。具体来说&#xff0c;它使得在QML代码中可以直接调用C类中被标记为Q_INVOKABLE的…

ventoy安装操作系统

下载ventoy https://github.com/ventoy/Ventoy/releases/download/v1.0.96/ventoy-1.0.96-windows.zip 解压后执行 Ventoy2Disk 2、安装后将ISO放入U盘大的分区&#xff0c;通过U盘启动就可以识别到ISO镜像开始装系统

2021秋招-总目录

2021秋招-目录 知识点总结 预训练语言模型: Bert家族 1.1 BERT、attention、transformer理解部分 B站讲解–强烈推荐可视化推倒结合代码理解代码部分常见面试考点以及问题: word2vec 、 fasttext 、elmo;BN 、LN、CN、WNNLP中的loss与评价总结 4.1 loss_function&#xff1…

AI大发展:人机交互、智能生活全解析

目录 ​编辑 人工智能对我们的生活影响有多大 人工智能的应用领域 一、机器学习与深度学习 二、计算机视觉 三、自然语言处理 四、机器人技术 五、智能推荐系统 六、智能城市和智能家居 ​编辑 自己对人工智能的应用 自己的人工智能看法&#xff1a;以ChatGPT为例 …

el-table 表格表头、单元格、滚动条样式修改

.2023.11.21今天我学习了如何对el-table表格样式进行修改&#xff0c;如图&#xff1a; 运用的两个样式主要是 1.header-cell-class-name&#xff08;设置表头&#xff09; 2.class-name&#xff08;设置行单元格&#xff09; 代码如下&#xff1a; <el-table :data&quo…

人工智能基础_机器学习045_逻辑回归的梯度下降公式推导_更新公式---人工智能工作笔记0085

然后我们上面有了逻辑回归的损失函数,以后,我们再来看 逻辑回归的梯度下降公式 可以看到上面是逻辑回归的梯度下降公式,这里的阿尔法是学习率,这里的 后面的部分是梯度也就是步长,这个阿尔法是,通过调节这个来控制梯度下降的快和慢对吧 然后我们再来看逻辑回归 可以看到这里…

万字解析设计模式之 适配器模式

一、 适配器模式 1.1概述 将一个接口转换成客户希望的另一个接口&#xff0c;适配器模式使接口不兼容的那些类可以一起工作。 适配器模式分为类适配器模式和对象适配器模式&#xff0c;前者类之间的耦合度比后者高&#xff0c;且要求程序员了解现有组件库中的相关组件的内部结…

【ArcGIS Pro二次开发】(77):ArcGIS Pro中图层的获取与解析

一、最简单的获取图层方式 通常情况下&#xff0c;如果要获取当前地图中的图层&#xff0c;可以用2种方法获取。 以下图为例&#xff1a; 一种是【map.Layers】属性获取&#xff0c;结果如下&#xff1a; 可以看出&#xff0c;这里只获取到了第一层级的图层&#xff0c;图层组…

创新无界:通义灵码在测试过程中展现的独特魅力

通义灵码基于通义大模型&#xff0c;提供代码智能生成、研发智能问答能力。本文就来介绍下通义灵码在测试过程中的应用。 操作手册&#xff1a; 通义灵码, 阿里云提供的一款基于通义大模型的智能编码辅助工具_云效-阿里云帮助中心 1. 什么是通义灵码 是阿里云出品的一款基于通…

数据结构与算法实验(黑龙江大学)

实验一 顺序存储的线性表&#xff08;2 学时&#xff09; 一、实验目的 1 、掌握线性表的逻辑结构特征。 2、熟练掌握线性表的顺序存储结构的描述方法。 3 、熟练掌握顺序表上各种基本操作的实现。 二、实验内容 1 、设线性表的数据元素都为整数&#xff0c;存放在顺序表…

C++使用Tensorflow2.6训练好的模型进行预测

要在C语言中调用训练好的TensorFlow模型,需要使用TensorFlow C API。 https://tensorflow.google.cn/install/lang_c?hl=zh-cnten TensorFlow 提供了一个 C API,该 API 可用于为其他语言构建绑定。该 API 在 c_api.h 中定义,旨在实现简洁性和一致性,而不是便利性。 下载…

深度学习卫星遥感图像检测与识别 -opencv python 目标检测 计算机竞赛

文章目录 0 前言1 课题背景2 实现效果3 Yolov5算法4 数据处理和训练5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **深度学习卫星遥感图像检测与识别 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐…

UE5的TimeLine的理解

一直以来&#xff0c;我对动画的理解一直是这样的&#xff1a; 所谓动画&#xff0c;就是可导致可视化内容变化的参数和时间的对应关系。 我不能说这个观点现在过时了&#xff0c;只能说自己狭隘了。因为UE的TimeLine的设计理念真让人竖大拇指。 当我第一次看到TimeLine节点的…

SAP 调取http的x-www-form-urlencoded形式的接口

一、了解下x-www-form-urlencoded形式对于SAP来说有啥区别 简单来说&#xff0c; 1.raw格式就是标准的json格式&#xff1a;{“Name”:“John Smith”&#xff0c;“Age”: 23} 2.x-www格式是要转化一下的&#xff1a;NameJohnSmith&Age23 字段与字段相互连接要用 & 符…

直播间弹幕直播游戏开发教程

随着直播技术的不断发展&#xff0c;交互式弹幕直播游戏成为吸引用户参与的新兴方式。这种游戏融合了实时弹幕互动和直播视频&#xff0c;为观众和主播提供了更加丰富的互动体验。在这篇文章中&#xff0c;我们将探讨从概念到实现的步骤&#xff0c;帮助你打造一款引人入胜的交…

MidJourney笔记(2)-面板使用

MidJourney界面介绍 接着上面的疑问。U1、U2、U3、U4、V1、V2、V3、V4分别代表着什么? U1、U2、U3、U4: U按钮是用于放大图片,数字即表示对应的图片,可以立即生成1024X1024像素大小的图片。这样大家在使用的时候,也方便单独下载。 其中数字顺序如下:

从零开始的搭建指南:开发高效的抖音预约服务小程序

预约服务小程序提高了效率&#xff0c;节省了用户时间。下文&#xff0c;小编将与大家一同探讨如何从零开始打造预约服务小程序。 第一步&#xff1a;明确需求和目标 确定你的小程序主要服务领域是什么&#xff1f;是医疗预约、美容美发、餐厅预订还是其他行业&#xff1f;明…