Unity 编写代码,生成随机洞穴(类似蜂巢)(2D、3D地图迷宫),平滑地图块,渲染地图。

参考官网教程:Procedural Cave Generation tutorial
完整Github工程:CaveGeneration

  跟着官方教程走了一遍,基本明白如何创建一个随机地图了。主要是算法的问题,如用广度优先获取区域(房间或墙)大小,用深度优先递归查找区域边界,还有计算两点之间经过结点的梯度变化。吐槽一下官方教程,教程中把结构和类全都放一起,不少方法耦合度很高,需要自己就优化了一下。


数据结构

结构、类名 说明 图释
Node 顶点。
Vector3 position;// 坐标
int vertexIndex;// 索引号
ControlNode 继承于Node,地图位置的基本单位。
bool active; //是否有效(True为墙,False为洞)
Node above,right; //上结点和右结点
Square 包含八个方位的结点,渲染地图的基本单位。
ControlNode topLeft, topRight, bottomRight, bottomLeft; //四个角的点
Node centreTop, centreRight, centreBottom, centreLeft;//四边中点
int configuration; //标志位,用于判断四个角哪些是激活状态的
SquareGrid 地图集。如图8 x 8个ControlNode,包含7 x 7个Square(深绿色框框),亮绿色的是不需要用到的结点。
Square[,] squares; //包含小于ControlNode一行和一列的Square
Coord 包含xy两个坐标。
int tileX,tileY;
略。
Triangle 三角形。包含三个点的索引值。
int vertexIndexA,vertexIndexB,vertexIndexC;
略。
Room 房间。成员变量包含所有结点,边界结点们,直接相连的房间们,房间大小(结点个数),是否连接(直接或间接)到主房间,是否是主房间。主要方法:
SetAccessibleFromMainRoom() :如果本身可以连接到主房间,则使其与之相连的其他房间们都设置为可以连接到主房间(传递性)。
ConnectRooms(Room roomA, Room roomB) :连接AB两个房间,并根据房间属性修改相应的值。
略。

MapGenerator(地图生成器)

1. 产生随机的地图结点(RandomFillMap())。

 1.1. 根据给宽高还有填充百分比,随机分配洞或墙结点(就像二维码)。

2. 平滑结点们生成房间(SmoothMap())。

 2.1. 遍历每个结点,计算其周围8个结点为墙个数,等于4个时保持不变,大于一半则自己也变成墙,反之为洞。

3. 清除小的墙体、空洞(ProcessMap())。

 3.1. 先删掉小墙体,这样有些房间就会变大。

 3.2. 删掉小空洞,并且把没删掉的作为房间存起来,最后把房间最大的作为主房间。

 3.3. 获取区域的大小时用广度优先的方法来查找(GetRegionTiles(x,y))。

4. 清除后幸存房间相互连接(ConnectClosestRooms(survivingRooms))。

 4.1. 首先依次为每个房间(还没连接过任何房间的),通过每个房间边界(room.edgeTiles)找到距离最近的房间,并且连接(CreatePassage(bestRoomA, bestRoomB, bestTileA, bestTileB) //连接距离A最近的房间B,最近的两个点bestTileA和bestTileB)。 但不一定所有房间都能互相连通。

 4.2. 将所有房间连通到主房间,分两列,一列是能连通主房间的房间列表,另一列不连通主房间,同样方法,找到两个队列最近的房间和最近的点,相互连接。ConnectClosestRooms(allRooms, true)

 4.3. 通过上面一步还不一定就能连接完所有房间,需要继续递归调用ConnectClosestRooms(allRooms, true),知道最后找不到需要连接的房间。

5. 相互连接时,创建通道(CreatePassage(roomA, roomB, tileA, tileB))。

 5.1. 通过给的tileA和tileB获取一条线段(梯度变化的结点列表)(GetLine(tileA,tileB)),原理很简单,就是先看成直角坐标,计算两个点产生的直线,可以求出线的斜率(梯度),通过斜率可以计算出下一个移动位置。

 5.2. 根据算出来的线段(List<Coord>),已经给的通道宽度,给每个线段结点,以通道宽度为半径挖洞(DrawCircle(coord,passageWidth))。

6. 最后给地图加一层墙(外边框),避免有洞出来(CrateStaticBorder())。

7. 最最后就是把做好的地图丢给网格生成器(MeshGenerator),用于渲染还有碰撞检测。


生成一个简单的8x8随机地图。

说明:
1. 黑色:ControlNode.active == true,墙体。
2. 白色:ControlNode.active == false,空洞(房间)。
3. 橙色:ControlNode,一个位置结点,包含上面和右边的蓝色结点。
4. 蓝色:Node,是橙色结点的子节点(ControlNode.above, ControlNode.right)。
5. 绿色:Square,包含四个橙色结点。共7x7个。


MeshGenerator(网格生成器)

1. 首先将每个Square(上图绿色部分)重新绘制(TriangulateSquare(squareGrid.squares[x, y]))成一系列三角形,以便于绘制网格。

 1.1. Square有一个成员变量configuration,就是标志位。用于标志周围四个ControlNode的状态(墙还是洞),如下。

一个Square含有8个主要方位结点(如图粉色)。

简化出来,看成一个绿色方框,四个角分别代表四个标志位如下图。

实例 说明 划分三角形顺序(深至浅)
四个角只有左下角是墙。
四个角下面两个是墙。
左下角右上角是墙。
只有左上角是洞。
四个都是墙。

 1.2. 划分出的三角形放入列表中(连续添加三个顶点索引),还有找到的结点们也放入列表中。需要注意的是,添加三角形顶点时要按顺时针依次添加,渲染原理:左手法则,顺时针后正面面向外部。

2. 然后把获得的结点们,和三角形们,添加到Cave.mesh中,就可以产生平滑的地图了(setCaveMesh(map.GetLength(0) * squareSize))。

 2.1. 根据上面8x8的地图,会产生如下图的平滑边框(橙色)。

 2.2. 去除自己渲染的Gizmos,就可以看到平滑的地图了。

3. 计算出房间的边缘(CalculateMeshOutlines()),存到List<List<int>> outlines 中,及如果有多个房间独立开来的,那么这个变量意思就是存放每个房间的边缘,而每个边缘含有一系列结点索引。

 3.1. 遍历所有所有三角形顶点。通过遍历包含同一顶点的所有三角形(GetConnectedOutlineVertex(vertexIndex)),找到下一个能和其组成单面墙的顶点(其原理就是,判断这条边是否只被一个三角形占有,因为如果一条边同时被两个三角形占有时,说明他两边都是墙。)

 3.2. 如果通过上一步成功找到下一个边缘顶点,在添加到边缘列表(outlines)之后,那么根据这个新顶点继续找下一个边缘顶点(FollowOutline(newOutlineVertex, outlines.Count - 1))。

 3.3. 在找下一个顶点时,其实就是递归了(FollowOutline(nextVertexIndex, outlineIndex);),结束条件就是找不到下一个顶点了。

 3.4. 找出一条边缘后,要记得加上第一个顶点,使这个边缘线闭合。之后就可以找下一条边缘线了(回到3.1步骤)。

4. 可以添加一条最外边(AddBorderLine()),及整个地图的矩形外轮廓,原理和步骤3一样。

5. 如果是3D场景,则创建边缘有高度的墙网格(CreateWallMesh())。

 5.1. 遍历所有房间的外边缘(outlines),每两个点之间产生一片墙,创建方法如下图。

说明:
白色顶点:上面两个顶点是外边缘连续两个顶点。通过加上高度,产生多两个白色顶点,一共四个白色顶点添加到墙顶点列表(wallVertices)中以用来绘制mesh。
红色,蓝绿色三角形:同之前划分三角形一个意思,用来组成mesh的三角形单位。

6. 如果是2D场景(需要把Cave Mesh和其他相关组件旋转270°(-90°)),则只需画出一条边界碰撞框就好了(Generate2DColliders())。

 6.1. 遍历一遍边缘顶点,转换成2D坐标,加到EdgeCollider2D就好了。


测试地图。

1. 3D场景

  创建一个Player(小球),还有个跟踪相机,丢到场景中。

  监视板变量如下。

  需要注意的是MeshCollider是单面,如果从背面看,是完全透明的,及如果小球在绿色墙体里面,是可以出来的,但是不能从外面正面穿过MeshCollider。同样,对光线来说其背面也是透明的,所以如果没有最外层的Mesh,光线可以直接穿过墙体。

2. 2D场景

  同样使用小球和跟踪相机测试。

  监视板如下。

  注意到下面创建有两个Edge Collider,是因为含有一层内部房间轮廓,还有最外面一圈矩形。


完整Github工程:CaveGeneration


Unity 生成随机房间、洞穴(2D、3D地图)总结相关推荐

  1. vue3.0 使用vue脚手架生成vue项目 运行mapbox 3D地图例子

    一.脚手架生成vue项目 1.安装脚手架:npm install -g @vue/cli 2.以图形界面创建vue项目 https://cli.vuejs.org/zh/guide/creating- ...

  2. Unity插件 FogOfWord教程 1分钟快速生成战争迷雾(2D/3D)

    前言 官网地址:点击前往 学习版插件下载地址:点击前往 笔记OneNote下载:点击前往 简介 1分钟上手 导入,然后找到脚本文件的位置 主镜头,绑个这个脚本 玩家身上绑上这个 怪物身上绑这个(迷雾隐 ...

  3. 3D地图+智能导航,用微信小程序轻松实现校园内导航

    大学都读完还没有走过学校的每一个角落已经是老生常谈了,但新生入学一般更绝望.一般的新生入学,学校需要发动大量的志愿者对新生进行引导,从报道处再到宿舍楼.教学楼.食堂.各办公楼都为新生一一进行讲解,但因 ...

  4. echarts:实现3D地图版块叠加动效散点+轮播高亮效果

    需求描述 如下图所示,展示3D效果的地图版块,并叠加显示动效散点: 实现思路 首先是3D地图版块效果的实现,可以参考广州3D地图:而动效散点的实现,可以参考地图发散分布. 这里再提一个经过尝试并不行的 ...

  5. [Unity]Roguelike随机地图生成(一)

    初步随机地图生成 基础房间生成 房间门的判断 基础房间生成 首先需要确定每次房间生成都是在上一个房间的哪个方向上生成,为此需要声明一个枚举值来记录上下左右四个方向 创建脚本RoomGenerator, ...

  6. Unity使用波函数坍缩 (Wave Collapse Function)算法生成随机地图

    在游戏领域和人工智能领域有一个随机生成地图用的比较多的算法叫做波函数坍缩 (Wave Collapse Function)算法,这个算法可以根据自己定制的规则生成随机地图. 根据波函数坍缩算法的源码 ...

  7. unity随机方向的代码_基于 Unity 的 Roguelike 随机房间生成的方法

    本文主要是为了实现各种随机大小不一样的方块基础房间,便于 Roguelike 游戏地图使用,希望对各位独立游戏的新手有所帮助,同时也希望各位大牛可以给出宝贵意见. 由于我的思路是地图=基础房间+特殊房 ...

  8. Unity中利用柏林噪音生成随机地图

    利用柏林噪音生成随机地图<2/2/2018> 什么是柏林噪音?(请自行百度)Perlin噪声 ( Perlin noise )指由Ken Perlin发明的自然噪声生成算法 . 原理学习可 ...

  9. python生成随机骨料模型代码_Abaqus Python脚本-3D随机球形骨料的生成

    hello,大家晚上好,今天是2020/11/30,周一晚上11点30.脚本时间 又是一个安静的晚上,一个人躲在房间里....写专栏.在脚本文集里,上次分享了一个关于脚本的工具,上上次分享了一个2D的 ...

  10. unity2d随机生成物体_unity2D游戏关卡地图编辑随机生成系统Strata Easy 2D Level Generator 1.3...

    unity2D游戏关卡地图编辑随机生成系统Strata Easy 2D Level Generator 1.3,是一个易于使用的程序工具包,用于使用手动和随机生成2D关卡场景.它可以在Unity编辑器 ...

最新文章

  1. python笔记之变量(variable)
  2. Express使用nodemailer完成邮箱验证功能详细流程(含封装,可作自定义模块)
  3. 去除utf8文件的bom标记
  4. android 不同项目代码合并在一块,android - 是否可以将两个(正在积极开发中的)Android应用程序模块合并到同一个Android Studio项目中? - 堆栈内存溢出...
  5. mobile webiste 中的css的font-size em及line-height等换算
  6. gcc4.4下实现backtrace代码
  7. 项目中为什么用Docker?
  8. 计算机主板的电路,电脑主板电路图全).pdf
  9. Spring Boot(三):Spring Boot Jpa的介绍及Jpa的使用
  10. 我的世界服务器空岛删除怎么找回,我的世界-删除空岛指令 岛屿删除指令分享...
  11. 100offer 为专业程序打造的招聘平台
  12. 在mac上使用PDF Expert中常见问题解答
  13. TLV2333IDR的工作原理?
  14. excel怎么算复购率(EXCEL怎么算平均分)
  15. MyBatis一对多,多对一,多对多
  16. android 百度云语音,手机安卓百度云AI智能之语音合成
  17. unicode和字符串之间的转换有两种方式
  18. 这是我的第一个博客有关javascrit的
  19. Mat结构及其它数据类型详解
  20. 阿德莱德大学计算机科学,School of Computer Science

热门文章

  1. Xcode中蓝色和黄色文件夹的区别
  2. 系统设计之时间维度[数据仓库]
  3. Reducing Participation Costs via Incremental Verification for Ledger Systems学习笔记
  4. ps修改图片上文字的几种方法 图+文
  5. java deflate_Java实现deflate算法的压缩和解压
  6. Visual reasoning
  7. python把英语句子成分字母_如何标注英语句子成分?
  8. WLAN中的MIMO技术详解
  9. 移动开发即服务,腾讯云移动开发平台打造开发新模式
  10. editplus java显示竖线_EditPlus对齐竖线怎么调出来?