【Unity】基于波函数坍塌算法实现赛道自动化生成

前言:很久没有写博客了,最近忙里偷闲准备恢复写博客的习惯,一是整理之前的笔记,二是梳理下知识点以供回顾。想写的内容很多,准备先针对以往做过的项目写个总结,最近在网上看到利用波函数坍塌算法生成游戏地图的案例,想到这种算法可以用于赛道自动生成之中,于是手动实现了一下,在此做个记录。

波函数坍塌算法简介

波函数坍塌算法是用来干什么的?

波函数坍塌算法的名字很唬人,但是实际理解起来并没有那么难,既不需要理解什么是波函数,也不需要对量子力学有什么了解。波函数坍塌算法最开始在自动化生成游戏场景领域被提出,实现了一种高效、随机性强的游戏地图、游戏场景自动化生成方式。我们仅需定义一系列用于组成地图的基本元素后,波函数算法会利用这些基本元素自动化生成场景。

波函数坍塌算法是怎么做的?

简单来说,波函数坍塌算法运用了体素的概念,初始化的时候将一个大的场景分成很多个小的地块(或者称为插槽),赋予每个地块它可能存在的基本元素形式,比如直道、弯道、斜坡、墙壁等等,将这些多个可能性存为List称为可能性列表,而这些有很多种可能性的地块我们称其为未坍塌的地块,之后开始迭代确定每个地块唯一的基本元素是什么,这个过程就称为坍塌,直至每个地块都被确定,那么这个地图就生成成功了。

那怎么确定哪个单元进行坍塌呢?

算法定义了每次选择所有没有坍塌地块中可能性列表长度最少的地块进行坍塌,比如目前只有A,B两个地块没有被确认,A地块有[直道、左弯道]2种可能,B地块有[直道、左弯道、右弯道]3种可能,那么我们选择A地块进行坍塌,这个过程被成为寻找熵值最小的单元。

每个单元是怎么坍塌的?

坍塌的过程可以分为两步,第一步是确定本地块是什么,第二步是传播用递归的方式去掉周围地块的可能性列表中与本地块无法连接的地块。第一步非常简单,基于每种可能的地块权重随机一下就可以确定;第二步首先要去掉周围地块的可能性列表中不能与该地块连接的可能性。比如本地块坍塌为一个横着的直道,那么如果其右边相邻的地块的可能性列表中有墙壁,从实际情况来说这是不成立的,于是我们将这种墙壁的可能性去除,最后以DFS的方式更新整个地图地块的可能性列表。

以赛道生成为例

准备预制体并配置面的属性

考虑到自己完全不会建模,捣鼓了半天3dmax才整出两个赛道组件,就先制作了直道、弯道两个预制体,大小都为12*12,如下图所示:

接着为每个预制体标记每个面,若两个面可以互相拼接在一起则给予它们一样的标签,如1,2,3,4...,有几种不同的切面就有多少种标签。若一个面不可以连接任何其他面则给予特殊的标签,如-1。在二维的生成地图中只需要标记4个面,而三维的生成地图需要标记6个面,针对简单的赛道生成,我们将有赛道的面(边)标为1,赛道不可达的面标为-1即可,标记后的预制体如下:

将每个预制体旋转90°,180°,270°可以获得另外不同预制体的形式,将这些都以Json的形式保证配置,每个预制体配置的格式如下:

prehab{
    mesh_name//网格名称
    mesh_rotation//旋转方向
    posX//标签
    negX//标签
    posY//标签
    negY//标签
    posZ//标签
    megZ//标签
    weight//生成权重

因为整个赛道不可能是满满当当的,所以需要添加一些空地块,这些空地块四周不能连接赛道故将其所有标记置为-1,同时为了让空地块看上去丰富一点,我准备了在空地块上随机生成一些环境物体,如下图所示:


至此有了7种预制体地块的类型(直道旋转2种+弯道旋转4种+空地块1种)

开始生成

做完配置就可以开始生成了,先进行初始化,将整个地图划分为20*20的地块,并用possibleList记录每个地块可能的基本元素[直道旋转0°,直道旋转90°,弯道旋转0°,弯道旋转90°,弯道旋转180°,弯道旋转270°,空白地块],每次循环需要三个步骤:选择熵最少的地块、随机确认该地块的内容、传播更新全图的可能性列表。

选择熵最少的地块

直接遍历地图选择possibleList最少的地块需要O(n^2)的时间复杂度,其实在初始化的时候可以用指针保存熵值最小的地块,在传播的时候更新这个指针即可。若有多个地块的possibleList长度都是最小则随机选择一个地块作为此次坍塌的地块。

坍塌与传播

根据每个预制体的生成权重来随机生成该地块是什么,然后更新全局所有地块的possibleList,这个步骤可以用栈来保存地块,先将目前生成的地块压栈,当栈不为空时,从栈中不断取出地块并更新该地块相邻四周的地块的possibleList,然后将相邻地块再存入栈中。具体的判断过程需要基于我们给地块标记的标签,比如若一个地块的posX的标签为-1,说明其地块再posX方向上没有赛道,则其posX方向上相邻地块的possibleList中negX不为-1的可能性就需要被去除。当更新完全局所有的possibleList后就可以进行下一个循环的生成了。

回溯与剪枝

当每个循环的过程中有两点需要注意,一是其实在传播更新的过程中会发生冲突的存在,比如某个更新后某个地块的possibleList中的可能性都被删除了,这就说明此次的坍塌是不成功的,需要重新选择地块进行回溯。二是在每次更新地块possibleList的过程需要遍历整个地图,非常废时间,我们可以进行一些剪枝优化,比如某个地块在更新时没有删除地块,则说明这个方向上的传播到该地块就结束了,可以不用将其入栈,节省性能。

结果

自此就完成了整个波函数坍塌生成赛道的方式,当不添加空白赛道时随机生成若干次的结果如下:

添加环境元素是生成的结果如下:
总之,这个赛道生成的例子颇为简单,波函数坍塌算法不仅仅可以生成2维地图还可以生成3维地图、六边形地图等等这只需要我们制作更复杂、更丰富的地图基本元素并打上相应的标签即可。我们可以利用波函数坍塌算法可以生成各式各样、随机的游戏场景,但是波函数坍塌算法也由其局限性,比如其运算时间复杂度较高,只能依赖于基本元素生成地图等等。
posted @ 2022-12-22 20:54  一只雷史莱姆  阅读(1015)  评论(0编辑  收藏  举报