没有走廊和房间依赖性的地牢生成


15

我正在用游戏开始时创建的程序生成的世界制作游戏,其中包括由网格表示的多个区域(例如8x8、9x6,大小理想情况下是任意的)。这些区域应该通过依赖关系列表相互连接。

当在这两个区域之间至少有3个网格暴露时存在连接。在该3个空间连接区域的中间单元中是区域之间的门口:

我一直在尝试找到一种连接它们的方法,但是随着您需要同时考虑更多领域,它变得越来越复杂。

我已经尝试过一些纸张原型制作,虽然在视觉上进行打印是一个非常简单的过程,但是我还没有找到一套好的数学表达式可以通过代码以相同的效率放置房间。

这是我目前正在苦苦挣扎的“简单”示例:

  • 区域“ a”需要连接到“ b”和“ c”
  • 区域“ b”需要连接到“ a”和“ d”
  • 区域“ c”需要连接到“ a”和“ d”
  • 区域“ d”需要连接到“ b”和“ c”

为简单起见,请考虑按房间的出现顺序将它们放在列表中(我尝试过其他方法)。因此,我正在将此作为您的标准程序Dungeon Generation算法。

我们将“ a”放置在板上的任何位置,因为它是第一个区域。接下来,我们随机选择一堵墙,由于该墙没有任何连接,因此可以在其中放置“ b”:

现在我们需要放置“ c”,但是“ a”已经在板上,并且已经占用了墙,因此我们决定将其放在另一墙上。但是并不是每个位置都可以,因为“ d”即将到来,并且它也需要连接到“ b”和“ c”:

我尝试了一个可能的限制,即具有相同依赖性集的2个房间不能在相对的墙上,但是即使这样也不能保证成功:

在其他情况下,如果区域的大小不同,则可以在相对的墙壁上工作:

另外,不考虑使用过的墙是一个错误的假设,因为它排除了有效的解决方案:

我曾尝试寻找其他过程生成算法或类似算法的研究,例如“最佳矩形包装”和“图形布局”算法,但通常这些算法没有考虑到此问题的所有约束,并且很难混合在一起。

我考虑过很多方法,包括放置区域和回溯直到找到合适的位置,但是它们似乎非常依赖于反复试验,并且在计算方面非常昂贵。但是,鉴于对我提到的最后两个问题的广泛研究,这可能是唯一/最佳解决方案?

我只是想看看过去是否有人遇到过类似的问题,或者愿意帮助我解决这个问题,并为我提供一些有关应该从哪里开始算法的建议。或者,否则,我将不得不放松设置的约束。


房间必须完全正方形吗?
wolfdawn

如果您的意思是如果他们必须有4堵墙并且不多于4堵墙,那么是的,但是我这样做是为了简化世界空间。我需要轻松地计算出每个区域所占的空间,因此我知道是否可以将所需的一切放到上面。
Joana Almeida

Answers:


6

这是一个很酷的问题。我相信可以通过在房间放置位置上的行动计划来解决。

定义国家世界如下:

//State:
//    A list of room states.
//    Room state:
//      - Does room exist?
//      - Where is room's location?
//      - What is the room's size?

约束定义为:

 // Constraint(<1>, <2>):
 //  - If room <1> and <2> exist, Room <1> is adjacent to Room <2>

如您所描述的,“相邻”(共享至少3个邻居)

约束被认为是无效每当两个房间相邻,并且两个室存在。

定义一个国家有效时:

// foreach Constraint:
//        The Constraint is "not invalidated".
// foreach Room:
//       The room does not intersect another room.

给定当前状态,将动作定义为房间的放置。该行动有效的,只要从动作产生的状态中是有效的。因此,我们可以为每个状态生成一个动作列表:

// Returns a list of valid actions from the current state
function List<Action> GetValidActions(State current, List<Constraint> constraints):
    List<Action> actions = new List<Action>();
    // For each non-existent room..
    foreach Room room in current.Rooms:
        if(!room.Exists)
            // Try to put it at every possible location
            foreach Position position in Dungeon:
                 State next = current.PlaceRoom(room, position)
                 // If the next state is valid, we add that action to the list.
                 if(next.IsValid(constraints))
                     actions.Add(new Action(room, position));

现在,你留下的是一个曲线图,其中美国是节点,和操作是链接。我们的目标是要找到一个国家有效,并且所有的房间都被放置。我们可以通过以任意方式搜索图形(也许使用深度优先搜索)来找到有效的放置位置。搜索将如下所示:

// Given a start state (with all rooms set to *not exist*), and a set of
// constraints, finds a valid end state where all the constraints are met,
// using a depth-first search.
// Notice that this gives you the power to pre-define the starting conditions
// of the search, to for instance define some key areas of your dungeon by hand.
function State GetFinalState(State start, List<Constraint> constraints)
    Stack<State> stateStack = new Stack<State>();
    State current = start;
    stateStack.push(start);
    while not stateStack.IsEmpty():
        current = stateStack.pop();
        // Consider a new state to expand from.
        if not current.checked:
            current.checked = true;
            // If the state meets all the constraints, we found a solution!
            if(current.IsValid(constraints) and current.AllRoomsExist()):
                return current;

            // Otherwise, get all the valid actions
            List<Action> actions = GetValidActions(current, constraints);

            // Add the resulting state to the stack.
            foreach Action action in actions:
                State next = current.PlaceRoom(action.room, action.position);
                stateStack.push(next);

    // If the stack is completely empty, there is no solution!
    return NO_SOLUTION;

现在生成的地牢的质量将取决于考虑房间和动作的顺序。您可以通过随机排列每个阶段所执行的动作,从而随机浏览状态动作图,来获得有趣且不同的结果。搜索效率将在很大程度上取决于您可以拒绝无效状态的速度。每当您要查找有效动作时,它可能有助于从约束条件生成有效状态。


有趣的是您应该提到此解决方案。之前我和一个朋友聊天,他提到我可能应该研究基于树的搜索算法,但是我不确定如何在这种情况下使用它们。您的帖子大开眼界!如果您管理分支生成并进行一些优化以尽快切掉不良分支,那么这无疑是一个可行的解决方案。
Joana Almeida

7

您的一代优先级有冲突。生成关卡时,您的第一个目标应该是平面(不重叠)的连接点的网,而与比例。然后从该网络中的点继续创建房间。一般来说,首先创建房间形状是一个错误。首先创建连通性,然后查看其中可以容纳哪些房间形式。

通用算法

  1. 使用2D阵列或图像创建足够大的量化地板网格以支持您的水平。

  2. 在这个空的地面空间上随机散布点。您可以在每个图块上使用简单的随机检查来查看它是否获得点,或使用标准/高斯分布来分散点。为每个点分配唯一的颜色/数值。这些是ID。(PS:如果在完成此步骤后,您觉得必须扩大空间,请这样做。)

  3. 对于每个这样产生的点,在顺序,递增地生长一个边界圈或边界由一个单一的步骤(通常0.5-1.0细胞每步/像素的速率)在矩形出xy。您可以并行生长所有边界,所有边界都从零开始,并在同一步骤中生长;也可以在不同时间以不同速率开始生长,从而限制早先开始的边界(想象幼苗在生长,有些迟到)。“增长”是指用这些边界的起点唯一的颜色/ ID填充新增加的边界。一个比喻是将记号笔握在纸的背面,并观察不同颜色的墨迹增长,直到它们碰面为止。

  4. 在增长步骤中,有时某个点和另一个点的边界会发生冲突。在这一点上,您应该停止增加这两个点的界限-至少在步骤3中描述的统一意义上。

  5. 一旦尽可能扩大了所有点的边界并停止了所有增长过程,您将获得一张应该在很大程度上但不完全填充的地图。现在,您可能希望收拾那些空白,我假设它是白色的,就像在纸上着色一样。

后处理空间填充

在第5步中,可以使用多种技术来填充剩余的空白/空白:

  • 通过用颜色填充整个相邻区域,使相邻的已经着色的区域占据该空间,从而将其合并在一起。
  • 充斥尚未使用的新颜色/数字/ ID,使它们形成全新的区域。
  • 循环法,使每个已经填充的相邻区域“长大”到空白区域。想一想在水坑附近喝水的动物:它们都喝了一些水。
  • 不要完全填满空白区域,只需将其穿过以使用直通通道将现有区域连接起来即可。

摄动

作为使事物看起来更加有机的最后一步,您可以对区域的边缘单元进行不同程度的边缘扰动。只要确保不阻塞关键的运动路线即可。

理论,为利益着想

这类似于Voronoi图/ Delaunay三角剖分中所采用的方法,不同的是,在上面您并没有明确创建边线-而是当边界区域发生碰撞时,生长停止了。您会注意到Voronoi图充满了空间;这是因为它们并不仅仅是在接触时停止增长,而是在某种程度的重叠上停止增长。您可以尝试类似。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.