Dungeon Generation Algorithm

http://www.gamasutra.com/blogs/AAdonaac/20150903/252889/Procedural_Dungeon_Generation_Algorithm.php

This post explains a technique for generating randomized dungeons that was first described by TinyKeepDev here. I‘ll go over it in a little more detail than the steps in the original post. The general way the algorithm works is this:

Generate Rooms

First you wanna generate some rooms with some width and height that are placed randomly inside a circle. TKdev‘s algorithm used the normal distribution for generating room sizes and I think that this is generally a good idea as it gives you more parameters to play with. Picking different ratios between width/height mean and standard deviation will generally result in different looking dungeons.

One function you might need to do this is getRandomPointInCircle:

function getRandomPointInCircle(radius)
  local t = 2*math.pi*math.random()
  local u = math.random()+math.random()
  local r = nil
  if u > 1 then r = 2-u else r = u end
  return radius*r*math.cos(t), radius*r*math.sin(t)
end

You can get more info on how that works exactly here. And after that you should be able to do something like this:

One very important thing that you have to consider is that since you‘re (at least conceptually) dealing with a grid of tiles you have to snap everything to that same grid. In the gif above the tile size is 4 pixels, meaning that all room positions and sizes are multiples of 4. To do this I wrap position and width/height assignments in a function that rounds the number to the tile size:

function roundm(n, m) return math.floor(((n + m - 1)/m))*m end

-- Now we can change the returned value from getRandomPointInCircle to:
function getRandomPointInCircle(radius)
  ...
  return roundm(radius*r*math.cos(t), tile_size),
         roundm(radius*r*math.sin(t), tile_size)
end

Separate Rooms

Now we can move on to the separation part. There‘s a lot of rooms mashed together in one place and they should not be overlapping somehow. TKdev used the separation steering behavior to do this but I found that it‘s much easier to just use a physics engine. After you‘ve added all rooms, simply add solid physics bodies to match each room‘s position and then just run the simulation until all bodies go back to sleep. In the gif I‘m running the simulation normally but when you‘re doing this between levels you can advance the physics simulation faster.

The physics bodies themselves are not tied to the tile grid in any way, but when setting the room‘s position you wrap it with the roundm call and then you get rooms that are not overlapping with each other and that also respect the tile grid. The gif below shows this in action as the blue outlines are the physics bodies and there‘s always a slight mismatch between them and the rooms since their position is always being rounded:

One issue that might come up is when you want to have rooms that are skewed horizontally or vertically. For instance, consider the game I‘m working on:

Combat is very horizontally oriented and so I probably want to have most rooms being bigger in width than they are in height. The problem with this lies in how the physics engine decides to resolve its collisions whenever long rooms are near each other:

As you can see, the dungeon becomes very tall, which is not ideal. To fix this we can spawn rooms initially inside a thin strip instead of on a circle. This ensures that the dungeon itself will have a decent width to height ratio:

To spawn randomly inside this strip we can just change the `getRandomPointInCircle` function to spawn points inside an ellipse instead (in the gif above I used ellipse_width = 400 and ellipse_height = 20):

function getRandomPointInEllipse(ellipse_width, ellipse_height)
  local t = 2*math.pi*math.random()
  local u = math.random()+math.random()
  local r = nil
  if u > 1 then r = 2-u else r = u end
  return roundm(ellipse_width*r*math.cos(t)/2, tile_size),
         roundm(ellipse_height*r*math.sin(t)/2, tile_size)
end

Main Rooms

The next step simply determines which rooms are main/hub rooms and which ones aren‘t. TKdev‘s approach here is pretty solid: just pick rooms that are above some width/height threshold. For the gif below the threshold I used was 1.25*mean, meaning, if width_mean and height_mean are 24 then rooms with width and height bigger than 30 will be selected.

Delaunay Triangulation + Graph

Now we take all the midpoints of the selected rooms and feed that into the Delaunay procedure. You either implement this procedure yourself or find someone else who‘s done it and shared the source. In my case I got lucky and Yonaba already implemented it. As you can see from that interface, it takes in points and spits out triangles:

After you have the triangles you can then generate a graph. This procedure should be fairly simple provided you have a graph data structure/library at hand. In case you weren‘t doing this already, it‘s useful that your Room objects/structures have unique ids to them so that you can add those ids to the graph instead of having to copy them around.

Minimum Spanning Tree

After this we generate a minimum spanning tree from the graph. Again, either implement this yourself or find someone who‘s done it in your language of choice.

The minimum spanning tree will ensure that all main rooms in the dungeon are reachable but also will make it so that they‘re not all connected as before. This is useful because by default we usually don‘t want a super connected dungeon but we also don‘t want unreachable islands. However, we also usually don‘t want a dungeon that only has one linear path, so what we do now is add a few edges back from the Delaunay graph:

This will add a few more paths and loops and will make the dungeon more interesting. TKdev arrived at 15% of edges being added back and I found that around 8-10% was a better value. This may vary depending on how connected you want the dungeon to be in the end.

Hallways

For the final part we want to add hallways to the dungeon. To do this we go through each node in the graph and then for each other node that connects to it we create lines between them. If the nodes are horizontally close enough (their y position is similar) then we create a horizontal line. If the nodes are vertically close enough then we create a vertical line. If the nodes are not close together either horizontally or vertically then we create 2 lines forming an L shape.

The test that I used for what ~close enough~ means calculates the midpoint between both nodes‘ positions and checks to see if that midpoint‘s x or y attributes are inside the node‘s boundaries. If they are then I create the line from that midpoint‘s position. If they aren‘t then I create two lines, both going from the source‘s midpoint to the target‘s midpoint but only in one axis.

In the picture above you can see examples of all cases. Nodes 62 and 47 have a horizontal line between them, nodes 60 and 125 have a vertical one and nodes 118 and 119 have an L shape. It‘s also important to note that those aren‘t the only lines I‘m creating. They are the only ones I‘m drawing but I‘m also creating 2 additional lines to the side of each one spaced by tile_size, since I want my corridors to be at least 3 tiles wide in either width or height.

Anyway, after this we check to see which rooms that aren‘t main/hub rooms that collide with each of the lines. Colliding rooms are then added to whatever structure you‘re using to hold all this by now and they will serve as the skeleton for the hallways:

Depending on the uniformity and size of the rooms that you initially set you‘ll get different looking dungeons here. If you want your hallways to be more uniform and less weird looking then you should aim for low standard deviation and you should put some checks in place to keep rooms from being too skinny one way or the other.

For the last step we simply add 1 tile sized grid cells to make up the missing parts. Note that you don‘t actually need a grid data structure or anything too fancy, you can just go through each line according to the tile size and add grid rounded positions (that will correspond to a 1 tile sized cell) to some list. This is where having 3 (or more) lines happening instead of only 1 matters.

And then after this we‘re done!

END

The data structures I returned from this entire procedure were: a list of rooms (each room is just a structure with a unique id, x/y positions and width/height); the graph, where each node points to a room id and the edges have the distance between rooms in tiles; and then an actual 2D grid, where each cell can be nothing (meaning its empty), can point to a main/hub room, can point to a hallway room or can be hallway cell. With these 3 structures I think it‘s possible to get any type of data you could want out of the layout and then you can figure out where to place doors, enemies, items, which rooms should have bosses, and so on.



Dungeon Generation Algorithm

时间: 2024-10-08 11:13:22

Dungeon Generation Algorithm的相关文章

任务五十二:王牌特工

面向人群: 有一定的JavaScript基础 难度: 难 重要说明 百度前端技术学院的课程任务是由百度前端工程师专为对前端不同掌握程度的同学设计.我们尽力保证课程内容的质量以及学习难度的合理性,但即使如此,真正决定课程效果的,还是你的每一次思考和实践. 课程多数题目的解决方案都不是唯一的,这和我们在实际工作中的情况也是一致的.因此,我们的要求不仅仅是实现设计稿的效果,更是要多去思考不同的解决方案,评估不同方案的优劣,然后使用在该场景下最优雅的方式去实现.那些最终没有被我们采纳的方案,同样也可以帮

A Universally Unique IDentifier (UUID) URN Namespace

w Network Working Group P. Leach Request for Comments: 4122 Microsoft Category: Standards Track M. Mealling Refactored Networks, LLC R. Salz DataPower Technology, Inc. July 2005 A Universally Unique IDentifier (UUID) URN Namespace Status of This Memo

A look at WeChat security

原文地址:http://blog.emaze.net/2013/09/a-look-at-wechat-security.html TL;DR: Any (unprivileged) application installed on an Android phone can instruct WeChat to send an hash of your password to an external, attacker-controlled server. If you are a WeChat

Deobfuscation of Angler Exploit Kit in Recently 0Day Attack

Angler Exploit在最近变得非常火热,主要是最近的几次flash 0day都出现在该Exploit Kit中,该Exploit Kit最大的特点就是混淆的十分厉害,相比国内比较流行DoSWF,这个的混淆方式简直灭绝人性==!,下面就是该Exploit Kit使用的一个CVE-2014-0569样本: 一个个不知道有多长的点点杠杠会看的人整个精神分裂.四肢无力.头昏目眩.口吐白沫…最近的Angler Exploit Kit中用到了两种不同的混淆方式.在CVE-2014-0569.CVE-

008-运维管理链码

以下,将通过一个块链网络运营商Noah的眼睛来探索链码.于诺亚的兴趣,我们将重点关注链码生命周期操作;作为块代码在块链网络中的操作生命周期的函数的链式代码的封装,安装,实例化和升级过程. 一.链码生命周期 Hyperledger Fabric API允许与块链网络中的各种节点(对等体,订户和MSP)进行交互,并且还允许在批准的对等节点上进行包代码,安装,实例化和升级链码.Hyperledger Fabric语言特定的SDK提取了Hyperledger Fabric API的细节,以促进应用程序开

Android jPBC 2.0.0配置与测试

我在前面的一片博客中,介绍了jPBC 2.0.0在PC平台上面的配置和测试.既然jPBC是Java平台上面实现的,那么jPBC能不能在Android这个以Java为主要语言的平台上运行呢?这样一来,各种在jPBC上撰写的有关双线性对的函数就都能够在移动终端上面用了.我个人的想法就是把最新的密码学算法应用到工程里面,而这确实是我想法的一个很好的跨越.因此,我在第一时间公开整个配置的过程以及我测试的方法,以供广大国内密码学研究者们进行尝试.整个配置过程实际上是非常简单的,这也要感谢jPBC库的编写者

【安全牛学习笔记】WEP加密、RC4算法

WEP加密                                                            使用Rivest Cipher 4 (RC4)算法加密流量内容,实现机密性               CRC32算法检查数据完整性                                             标准采用使用24位initialization vector (IV)                          受美国加密技术出口限制法律

后门设置总结

前言 没有最好的后门,只有最合适的后门. 时光荏苒,岁月如梭,在这篇文章里,我将对后门的各种形式进行解读和总结,这不仅仅是能帮你找到回忆,更希望的是能给予大家帮助.启发思考. 后门种类繁多,林林总总,根据不同的需求出现了很多奇怪好玩的后门.它可以是一行简单的代码,也可以是复杂的远控木马,它可以是暴力的,也可以是很优雅的. 在整体架构上,一个优秀的后门应该充分考虑其功能.触发方式和通信方式等方面.针对不同的方面,杀软也会根据其特征进行处理.为了进一步的持续性控制以及后渗透,后门越显复杂化.从后门的

PatentTips - Safe general purpose virtual machine computing system

BACKGROUND OF THE INVENTION The present invention relates to virtual machine implementations, and in particular to a safe general purpose virtual machine that generates optimized virtual machine computer programs that are executable in a general purp