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

文章目录

  • 先看看最终效果
  • 前言
  • 生成走廊
  • 生成房间
  • 修复死胡同
  • 增加走廊宽度
    • 获取走廊位置信息集合
    • 方法一
    • 方法二
  • 源码
  • 完结

先看看最终效果

在这里插入图片描述

前言

上期已经实现了房间的生成,本期紧跟着上期内容,生成走廊并结合上期内容生成连通的房间。

生成走廊

修改ProceduralGenerationAlgorithms

/// <summary>
/// 随机生成走廊的方法。
/// </summary>
/// <param name="startPosition">起始位置</param>
/// <param name="corridorLength">走廊长度</param>
/// <returns>走廊位置列表</returns>
public static List<Vector2Int> RandomWalkCorridor(Vector2Int startPosition, int corridorLength)
{
    // 创建走廊位置列表
    List<Vector2Int> corridor = new List<Vector2Int>();

    // 随机选择一个基本方向
    var direction = Direction2D.GetRandomCardinalDirection();

    // 将当前位置设置为起始位置
    var currentPosition = startPosition;

    // 将起始位置添加到走廊位置列表中
    corridor.Add(currentPosition);

    // 沿着基本方向移动,并将每个新位置添加到走廊位置列表中
    for (int i = 0; i < corridorLength; i++)
    {
        currentPosition += direction;
        corridor.Add(currentPosition);
    }

    // 返回走廊位置列表
    return corridor;
}

修改SimpleRandomWalkDungeonGenerator

HashSet<Vector2Int> floorPositions = RunRandomWalk(randomWalkParameters); // 获取地牢地板坐标集合

protected HashSet<Vector2Int> RunRandomWalk(SimpleRandomWalkSO parameters)
{
   //。。。
}

新增CorridorFirstDungeonGenerator,走廊优先地牢生成器

// 走廊优先地牢生成器,继承自简单随机行走地牢生成器
public class CorridorFirstDungeonGenerator : SimpleRandomWalkDungeonGenerator
{
    [SerializeField, Header("走廊长度")] private int corridorLength = 14;
    [SerializeField, Header("走廊数量")] private int corridorCount = 5;
    [SerializeField, Header("房间占比")] [Range(0.1f, 1)] private float roomPercent = 0.8f;

    // 执行过程化生成
    protected override void RunProceduralGeneration()
    {
        CorridorFirstGeneration();
    }

    // 走廊优先生成方法
    private void CorridorFirstGeneration()
    {
        HashSet<Vector2Int> floorPositions = new HashSet<Vector2Int>();  // 地板位置集合
        CreateCorridors(floorPositions);  // 创建走廊
        tilemapVisualizer.PaintFloorTiles(floorPositions);  // 绘制地板瓦片
        WallGenerator.CreateWalls(floorPositions, tilemapVisualizer);  // 创建墙壁
    }

    // 创建走廊的方法
    private void CreateCorridors(HashSet<Vector2Int> floorPositions)
    {
        var currentPosition = startPosition;  // 当前位置设为起始位置
        for (int i = 0; i < corridorCount; i++)  // 循环生成指定数量的走廊
        {
            var corridor = ProceduralGenerationAlgorithms.RandomWalkCorridor(currentPosition, corridorLength);  // 生成随机走廊
            currentPosition = corridor[corridor.Count - 1];  // 更新当前位置为走廊的最后一个位置
            floorPositions.UnionWith(corridor);  // 将走廊位置添加到地板位置集合中
        }
    }
}

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

效果,创建了走廊
在这里插入图片描述

生成房间

所以后续只需要在走廊的末端生成房间即可连通各个房间,添加走廊长度以防止房间之间相交

修改CorridorFirstDungeonGenerator

// 走廊优先生成方法
private void CorridorFirstGeneration()
{
    HashSet<Vector2Int> floorPositions = new HashSet<Vector2Int>();  // 地板位置集合

    HashSet<Vector2Int> potentialRoomPositions = new HashSet<Vector2Int>();// 房间位置集合
    CreateCorridors(floorPositions, potentialRoomPositions);// 创建走廊

    HashSet<Vector2Int> roomPositions = CreateRooms(potentialRoomPositions);// 创建房间
    floorPositions.UnionWith(roomPositions);// 将房间位置添加到地板位置集合中

    tilemapVisualizer.PaintFloorTiles(floorPositions);  // 绘制地板瓦片
    WallGenerator.CreateWalls(floorPositions, tilemapVisualizer);  // 创建墙壁
}

// 创建走廊的方法
private void CreateCorridors(HashSet<Vector2Int> floorPositions, HashSet<Vector2Int> potentialRoomPositions)
{
    var currentPosition = startPosition;  // 当前位置设为起始位置

    potentialRoomPositions.Add(currentPosition);// 将当前位置添加到潜在房间位置集合中

    for (int i = 0; i < corridorCount; i++)  // 循环生成指定数量的走廊
    {
        var corridor = ProceduralGenerationAlgorithms.RandomWalkCorridor(currentPosition, corridorLength);  // 生成随机走廊
        currentPosition = corridor[corridor.Count - 1];  // 更新当前位置为走廊的最后一个位置

        potentialRoomPositions.Add(currentPosition);// 将当前位置添加到潜在房间位置集合中

        floorPositions.UnionWith(corridor);  // 将走廊位置添加到地板位置集合中
    }
}

// 创建房间的方法
private HashSet<Vector2Int> CreateRooms(HashSet<Vector2Int> potentialRoomPositions)
{
    HashSet<Vector2Int> roomPositions = new HashSet<Vector2Int>();
    int roomToCreateCount = Mathf.RoundToInt(potentialRoomPositions.Count * roomPercent);// 计算需要创建的房间数量
    // 根据潜在房间位置集合随机选择要创建的房间位置
    List<Vector2Int> roomsToCreate = potentialRoomPositions.OrderBy(x => Guid.NewGuid()).Take(roomToCreateCount).ToList();
    foreach (var roomPosition in roomsToCreate)
    {
        var roomFloor = RunRandomWalk(randomWalkParameters, roomPosition);// 在选定的位置运行随机行走算法以生成房间地板
        roomPositions.UnionWith(roomFloor);// 将房间地板位置添加到房间位置集合中
    }
    return roomPositions;
}

修改SimpleRandomWalkDungeonGenerator

// 获取地牢地板坐标集合
HashSet<Vector2Int> floorPositions = RunRandomWalk(randomWalkParameters, startPosition);

protected HashSet<Vector2Int> RunRandomWalk(SimpleRandomWalkSO parameters, Vector2Int position)
{
    var currentPosition = position; // 当前位置初始化为起始位置
    //。。。
}

配置参数,增加走廊长度
在这里插入图片描述
效果
在这里插入图片描述

修复死胡同

前面生成房间存在一些死胡同,这非常不好,我么需要修复一下

修改CorridorFirstDungeonGenerator

// 走廊优先生成方法
private void CorridorFirstGeneration()
{
    HashSet<Vector2Int> floorPositions = new HashSet<Vector2Int>();  // 地板位置集合

    HashSet<Vector2Int> potentialRoomPositions = new HashSet<Vector2Int>();// 房间位置集合
    CreateCorridors(floorPositions, potentialRoomPositions);// 创建走廊

    HashSet<Vector2Int> roomPositions = CreateRooms(potentialRoomPositions);// 创建房间

    List<Vector2Int> dradEnds = FindAllDeadEnds(floorPositions);
    CreateRoomsAtDeadEnd(dradEnds, roomPositions);

    floorPositions.UnionWith(roomPositions);// 将房间位置添加到地板位置集合中

    tilemapVisualizer.PaintFloorTiles(floorPositions);  // 绘制地板瓦片
    WallGenerator.CreateWalls(floorPositions, tilemapVisualizer);  // 创建墙壁
}

// 在死胡同处创建房间的方法
private void CreateRoomsAtDeadEnd(List<Vector2Int> deadEnds, HashSet<Vector2Int> roomFloors)
{
    foreach (var position in deadEnds)
    {
        if (roomFloors.Contains(position) == false)
        {
            var room = RunRandomWalk(randomWalkParameters, position);  // 在选定的位置运行随机行走算法以生成房间地板
            roomFloors.UnionWith(room);  // 将房间地板位置添加到房间位置集合中
        }
    }
}

// 查找所有死胡同的方法
private List<Vector2Int> FindAllDeadEnds(HashSet<Vector2Int> floorPositions)
{
    // 创建一个空的死路位置列表
    List<Vector2Int> deadEnds = new List<Vector2Int>();

    // 对于每个位置,检查其周围的位置数量
    foreach (var position in floorPositions)
    {
        int neighboursCount = 0;
        // 遍历四个基本方向(上下左右),如果相邻位置在floorPositions中,则增加邻居计数
        foreach (var direction in Direction2D.cardinalDirectionsList)
        {
            if (floorPositions.Contains(position + direction))
            {
                neighboursCount++;
            }
        }
        // 如果邻居计数为1,则将该位置添加到死路列表中
        if (neighboursCount == 1)
        {
            deadEnds.Add(position);
        }
    }
    // 返回所有死路的位置列表
    return deadEnds;
}

效果,我们可以把走廊长度扩大,这样就实现了生成不同房间的功能
在这里插入图片描述
效果
在这里插入图片描述

增加走廊宽度

获取走廊位置信息集合

修改CorridorFirstDungeonGenerator

List<List<Vector2Int>> corridors = CreateCorridors(floorPositions, potentialRoomPositions);

private List<List<Vector2Int>> CreateCorridors(HashSet<Vector2Int> floorPositions, HashSet<Vector2Int> potentialRoomPositions)
{
    var currentPosition = startPosition;  // 当前位置设为起始位置

    potentialRoomPositions.Add(currentPosition);// 将当前位置添加到潜在房间位置集合中

    List<List<Vector2Int>> corridors = new List<List<Vector2Int>>(); // 声明并初始化走廊列表

    for (int i = 0; i < corridorCount; i++)  // 循环生成指定数量的走廊
    {
        var corridor = ProceduralGenerationAlgorithms.RandomWalkCorridor(currentPosition, corridorLength);  // 生成随机走廊
        currentPosition = corridor[corridor.Count - 1];  // 更新当前位置为走廊的最后一个位置

        potentialRoomPositions.Add(currentPosition);// 将当前位置添加到潜在房间位置集合中

        floorPositions.UnionWith(corridor);  // 将走廊位置添加到地板位置集合中
        corridors.Add(corridor);
    }
    return corridors;
}

下面IncreaseCorridorSizeByOne() 和 IncreaseCorridorBrush3by3()两种办法都可以增加走廊宽度走廊,但它们的实现方式不同。
IncreaseCorridorSizeByOne()方法比较简单粗暴,适合用于简单的走廊扩展
而 IncreaseCorridorBrush3by3()方法则更加考虑周围环境,生成的走廊形状更加自然
但是,由于 IncreaseCorridorBrush3by3()方法的实现比较复杂,可能会增加代码的复杂度和运行时间,因此需要权衡使用场景。

方法一

修改CorridorFirstDungeonGenerator

// 走廊优先生成方法
private void CorridorFirstGeneration()
{
	//...
	
	// 对每条走廊进行遍历,增加走廊的大小并更新地板位置集合
	for (int i = 0; i < corridors.Count; i++)
	{
	    // 增加走廊大小的方法 生成3x3走廊
	    corridors[i] = IncreaseCorridorSizeByOne(corridors[i]);//方法1
	    
	    // 将更新后的走廊位置添加到地板位置集合中
	    floorPositions.UnionWith(corridors[i]);
	}
}

public List<Vector2Int> IncreaseCorridorSizeByOne(List<Vector2Int> corridor)
{
    List<Vector2Int> newCorridor = new List<Vector2Int>(); // 新走廊坐标的列表
    Vector2Int previousDirection = Vector2Int.zero; // 上一个方向的单位向量

    for (int i = 1; i < corridor.Count; i++) // 遍历走廊坐标列表
    {
        Vector2Int directionFromCell = corridor[i] - corridor[i - 1]; // 获取当前坐标与上一个坐标之间的方向向量

        if (previousDirection != Vector2Int.zero && directionFromCell != previousDirection)
        {
            // 处理转角情况
            for (int x = -1; x < 2; x++)
            {
                for (int y = -1; y < 2; y++)
                {
                    newCorridor.Add(corridor[i - 1] + new Vector2Int(x, y)); // 将转角坐标添加到新走廊坐标列表中
                }
            }
            previousDirection = directionFromCell; // 更新上一个方向的单位向量
        }
        else
        {
            Vector2Int newCorridorTileOffset = GetDirection90From(directionFromCell); // 获取当前方向的90度偏移向量
            newCorridor.Add(corridor[i - 1]); // 添加当前坐标到新走廊坐标列表中
            newCorridor.Add(corridor[i - 1] + newCorridorTileOffset); // 添加当前坐标及偏移向量到新走廊坐标列表中
        }
    }
    return newCorridor; // 返回新走廊坐标的列表
}

private Vector2Int GetDirection90From(Vector2Int direction)
{
    if (direction == Vector2Int.up)
    {
        return Vector2Int.right; // 如果输入方向向量是向上的,返回向右的方向向量
    }
    if (direction == Vector2Int.right)
    {
        return Vector2Int.down; // 如果输入方向向量是向右的,返回向下的方向向量
    }
    if (direction == Vector2Int.down)
    {
        return Vector2Int.left; // 如果输入方向向量是向下的,返回向左的方向向量
    }
    if (direction == Vector2Int.left)
    {
        return Vector2Int.up; // 如果输入方向向量是向左的,返回向上的方向向量
    }

    return Vector2Int.zero; // 如果输入方向向量不是上述情况之一,则返回零向量
}

生成效果,可以看到对于复杂走廊增加宽度的效果不是很好,有些走廊只有两格隔宽度
在这里插入图片描述

方法二

修改CorridorFirstDungeonGenerator

// 走廊优先生成方法
private void CorridorFirstGeneration()
{
	//...
	
	// 对每条走廊进行遍历,增加走廊的大小并更新地板位置集合
	for (int i = 0; i < corridors.Count; i++)
	{
	    // 增加走廊大小的方法 生成3x3走廊
	    // corridors[i] = IncreaseCorridorSizeByOne(corridors[i]);//方法1
	    corridors[i] = IncreaseCorridorBrush3by3(corridors[i]);//方法2
	    // 将更新后的走廊位置添加到地板位置集合中
	    floorPositions.UnionWith(corridors[i]);
	}
}

public List<Vector2Int> IncreaseCorridorBrush3by3(List<Vector2Int> corridor)
{
    List<Vector2Int> newCorridor = new List<Vector2Int>(); // 新走廊坐标的列表

    for (int i = 1; i < corridor.Count; i++) // 遍历走廊坐标列表
    {
        for (int x = -1; x < 2; x++) // 在x轴方向上遍历-1到1的范围
        {
            for (int y = -1; y < 2; y++) // 在y轴方向上遍历-1到1的范围
            {
                newCorridor.Add(corridor[i - 1] + new Vector2Int(x, y)); // 将当前坐标的周围九个坐标添加到新走廊坐标列表中
            }
        }
    }

    return newCorridor; // 返回新走廊坐标的列表
}

生成效果,稳定生成3格宽度的走廊
在这里插入图片描述

源码

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

完结

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WPF Button点击鼠标左键弹出菜单

目录 ContextMenu介绍WPF实现点击鼠标左键弹出菜单如何禁用右键菜单如何修改菜单样式菜单位置设置 本篇博客介绍WPF点击按钮弹出菜单&#xff0c;效果如下&#xff1a; 菜单的位置、央视可以自定义。 实现技巧&#xff1a;不在xaml里菜单&#xff0c;在按钮左键按下的点击事件里…

Linux系统编程 系统编程概念

1.系统调用 系统调用&#xff08;system call&#xff09;其实是 Linux 内核提供给应用层的应用编程接口&#xff08;API&#xff09;&#xff0c;是 Linux 应用层进入内核的入口。不止 Linux 系统&#xff0c;所有的操作系统都会向应用层提供系统调用&#xff0c;应用程序通过…

每日汇评:美日在两个月低点附近似乎较为脆弱,熊市可能会在FOMC会议纪要公布前暂停

美元/日元跌至两个月低点&#xff0c;并受到多种因素的压力&#xff1b; 美联储鸽派预期和美国债券收益率下降继续令美元承压&#xff1b; 美日利差缩小以及日本央行政策转变的押注提振了日元&#xff1b; 美元/日元货币对在周二持续第四天承受着沉重的卖压&#xff0c;同时也标…

jenkins-2.426.1-1.1.noarch.rpm 的公钥没有安装

执行命令 yum install jenkins 报错 jenkins-2.426.1-1.1.noarch.rpm 的公钥没有安装 下载的软件包保存在缓存中&#xff0c;直到下次成功执行事务。 您可以通过执行 yum clean packages 删除软件包缓存。 错误&#xff1a;GPG 检查失败 解决办法&#xff1a; 1、安装新的公…

Linux上通过SSL/TLS和start tls连接到LDAP服务器(附C++代码实现认证流程)

一&#xff0c;大致流程。 1.首先在Linux上搭建一个LDAP服务器 2.在LDAP服务器上安装CA证书&#xff0c;服务器证书&#xff0c;因为SSL/TLS&#xff0c;start tls都属于机密通信&#xff0c;需要客户端和服务器都存在一个相同的证书认证双方的身份。3.安装phpldapadmin工具&am…

集群创建(flannel)时候,没有自动创建出cni0网卡

给旧的集群加入四台新的服务器启动时候发现都是正常的&#xff0c;但是pod通信报错 集群通信失败&#xff0c;第一时刻想看看是不是cni0和flannel.1的网段是不是通的&#xff0c;点进去一看发现cni0网卡没有生成。 部署是通过kubeadm方式部署的集群&#xff0c;目前有两种解决…

数据库表的内连接和外连接

1.内连接查询语法 -- 隐式内链接 SELECT 字段列表 FROM 表1&#xff0c;表2WHERE 条件&#xff1b; -- 显示内连接 select 字段列表 from 表1 [inner] join 表2 on 条件&#xff1b; 如果两个表没用进行内连接&#xff0c;会生成笛卡尔积。A集合和B集合全部元素进行排列组合。 …

Oracle数据库安装踩坑记录

Oracle数据库安装踩坑记录 踩坑目录 可能会用到的教程1. 管理员用户&#xff08;sys&#xff09;登录oracle命令2. 默认密码&#xff1a;三个 如果忘记改密码参考 1. 登录后修改密码3. 查看账号密码&#xff1a;只有sys用户登录后才能查看4. sqldeveloper 连接oracle数据库5. o…

logic-flow 使用过程中遇到的bug - 拖动节点到画布的时候,鼠标松开,节点不落在画布,仍旧跟着鼠标走

背景&#xff1a; 插件&#xff1a;logicFlow 用途&#xff1a;画流程图 bug表现&#xff1a; 初始化的样子&#xff1a; bug的样子&#xff1a; 拖动第一个节点的时候&#xff0c;一切正常&#xff08;无论哪个节点作为第一个节点&#xff0c;都是正常的&#xff0c;但是拖动…

渗透实例------2个星期艰难的渗透纪实

2个星期艰难的渗透纪实 kyo327 入侵原因,需删一帖子,目标用www.111.com代替,前期通过初期的网站文件暴力猜解,扫描到robots.txt这个文件,有以下目录。如图1: 图1 再通过对这些文件的访问,从3gadm.php文件的标题栏得到该网站采用的是diy-page8.3的cms,自然可以先用搜索…

SpringBoot-Docker容器化部署发布

在生产环境都是怎么部署 Spring Boot? 打成 jar 直接一键运行打成 war 扔到 Tomcat 容器中运行容器化部署 一、准备Docker 在 CentOS7 上安装好 Docker 修改 Docker 配置&#xff0c;开启允许远程访问 Docker 的功能&#xff0c;开启方式很简单&#xff0c;修改 /usr/lib/s…

使你的软文更具说服力的技巧,媒介盒子分享

软文想要写好除了日常素材积累和思维的训练外&#xff0c;还需要充分的前期策划&#xff0c;这样才能使软文的写作效果更好&#xff0c;今天媒介盒子就来和大家分享&#xff0c;使你的软文更具说服力和吸引力的技巧&#xff01; 一、 受众分析 了解受众的基本属性这样能使写作…

每日一练:X加上100为完全平方数,再加上168任然为完全平方数

题目 一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上168又是一个完全平方数&#xff0c;请问该数是多少&#xff1f; 实现方式1 解题思路 设整数为x&#xff0c;根据题意建立方程&#xff1a;   (1) x 100 是一个完全平方数&#xff0c;即存在整数a满…

京东商品详情数据接口【京东API接口开发系列】,监控京东价格走势,接口代码示例,可高并发批量获取

京东开放平台提供了API接口来访问京东商品详情。通过这个接口&#xff0c;您可以获取到商品的详细信息&#xff0c;如商品名称、价格、库存量、描述等。 以下是使用京东商品详情API接口的一般步骤&#xff1a; 注册并获取API权限&#xff1a;您需要在京东开放平台上注册并获取…

QT程序打包

1.须知 自用&#xff0c;不能保完全适合你的需求 只适用简单的QT工程 1.标准qmake工程&#xff0c;一些cmake工程不能保证适用 2.不涉及太多第三方库&#xff0c;linuxdeployqt不能递归拷贝库文件&#xff0c;如果打包完运行显示缺少库&#xff0c;可以手动把缺少的库拷进来…

QT修改windowTitle的名字以及图片

1.修改名字:点击ui的QMainWindow,然后找到windowTitle的选项修改即可 2.修改windowTitle的图片,依旧是找到windowIcon,选择资源,这个资源可以是你放到qrc里面的图片也可以是外置的图片 3.然后运行就可以看到效果了

01-了解微服务架构的演变过程和微服务技术栈

微服务 微服务架构演变 单体架构:将业务的所有功能集中在一个项目中开发最后打成一个包部署 优点: 架构简单, 部署成本低,适合小型项目缺点: 耦合度高, 升级维护困难 分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发称为一个服务 优点: 降低服务耦合…

MySQL数据库入门到大牛_基础_10_创建和管理表(创建和管理数据库;创建表;修改表;重命名表;删除表;清空表,内容扩展)

前面我们完成了查询结构的介绍&#xff0c;本章介绍DDL和DCL中的COMMIT和ROLL BACK。 文章目录 1. 基础知识1.1 一条数据存储的过程1.2 标识符命名规则1.3 MySQL中的数据类型 2. 创建和管理数据库2.1 创建数据库2.2 使用数据库2.3 修改数据库2.4 删除数据库 3. 创建表3.1 创建…

基于Docker的安装和配置Canal

基本介绍 Canal介绍&#xff1a;Canal 是用 Java 开发的基于数据库增量日志解析&#xff0c;提供增量数据订阅&消费的中间件&#xff08;数据库同步需要阿里的 Otter 中间件&#xff0c;基于 Canal&#xff09;。 Canal背景&#xff1a;阿里巴巴 B2B 公司&#xff0c;因为…

【图像分类】【深度学习】【Pytorch版本】GoogLeNet(InceptionV4)模型算法详解

【图像分类】【深度学习】【Pytorch版本】GoogLeNet(InceptionV4)模型算法详解 文章目录 【图像分类】【深度学习】【Pytorch版本】GoogLeNet(InceptionV4)模型算法详解前言GoogLeNet(InceptionV4)讲解Stem结构Inception-A结构Inception- B结构Inception-C结构Redution-A结构Re…
最新文章