游戏地图动态生成

随机迷宫生成

游戏地图的动态生成要追溯到传统的随机迷宫生成。随机迷宫生成可以描述成这样的问题:在一个m*n的网格中,每一个网格的边代表一堵墙,初始时所有网格彼此不联通,现在要随意打穿一些墙,使得特殊的两个网格(起点和终点)能够联通。 以图论来描述就是:初始其实是一个 连通图,每一个网格代表图的节点,墙代表图的边,每一个随机生成的迷宫对应这个图的一个生成树。

完美迷宫

完美迷宫就是没有回路,没有不可达区域的迷宫。在图论中这就是一条最小生成树了。下面介绍的算法都是生成完美迷宫的。

DFS(深度优先的搜索)生成完美迷宫

1. 在网格中随机取一个点作为搜索起始点;

2. 标记当前点为已经访问过,取周围的网格点(上,下,左,右四个方向),如果有一点未访问,则打穿连接这个邻居节点的墙,并选取为当前点重复步骤2,如果不存在任何未访问过的邻居,则表示进到死胡同了,这时候要向上回溯一个节点再重复步骤2;

3. 一直重复步骤2,直到所有节点都被标记为访问过了。

这个算法简单明了,复杂度是网格个数,但是有可能搜索的层次太深了,耗费大量内存,如果是递归的实现就会栈溢出。代码如下:

template <size_t R, size_t C >

void DFS (unsigned char ver_walls [R][ C+1], unsigned char hor_walls[R +1][C]) {

unsigned char visited[ R][C ] = {0};

stack<Node > path;

int rv = rand() % ( R*C );

Node start (rv / C, rv % C);

memset(ver_walls , 1, sizeof( unsigned char )*R*( C+1));

memset(hor_walls , 1, sizeof( unsigned char )*(R+1)* C);

path.push (start);

visited[start ._x][ start._y ] = 1;

unsigned int num_visited = 1;

while ( num_visited != R* C) {

Node cur = path. top();

Node avaliable [4];

BT bts [DIR];

int cnt = 0;

// to left;

if (cur ._y - 1 >= 0 && ! visited[cur ._x][ cur._y -1]) {

avaliable[cnt ]._x = cur._x ;

avaliable[cnt ]._y = cur._y - 1;

bts[cnt ] = LEFT;

cnt++;

                                }

// to right;

if (cur ._y + 1 < C && !visited [cur. _x][cur ._y+1]) {

avaliable[cnt ]._x = cur._x ;

avaliable[cnt ]._y = cur._y + 1;

bts[cnt ] = RIGHT;

cnt++;

                                }

// to top;

if (cur ._x - 1 >= 0 && ! visited[cur ._x-1][ cur._y ]) {

avaliable[cnt ]._x = cur._x - 1;

avaliable[cnt ]._y = cur._y ;

bts[cnt ] = TOP;

cnt++;

                                }

// to bottom;

if (cur ._x + 1 < R && !visited [cur. _x+1][cur ._y]) {

avaliable[cnt ]._x = cur._x + 1;

avaliable[cnt ]._y = cur._y ;

bts[cnt ] = BOTTOM;

cnt++;

                                }

if (cnt > 0) {

int iselected = rand() % cnt;

path.push (avaliable[ iselected]);

visited[path .top(). _x][path .top(). _y] = 1;

num_visited++;

switch (bts [iselected]) {

case BOTTOM :                     hor_walls[cur ._x+1][ cur._y ] = 0;        break;

case TOP :                                               hor_walls[cur ._x][ cur._y ]   = 0;          break;

case LEFT :                                             ver_walls[cur ._x][ cur._y ]    = 0;          break;

case RIGHT :                                           ver_walls[cur ._x][ cur._y +1] = 0;         break;

default:   break ;

                                                }

                                } else {

path.pop ();

assert(path .size() > 0);

                                }

                }              

}

最小生成树Prim算法

在一个顶点集合为V,边集为E的加权连通图的中,求其最小生成树的Prim算法过程如下。

(1)随机一个顶点置入closed集合中,剩下的顶点集合叫做opened:closed = {x}, x为随机任意顶点,V = closed U opened;

(2)重复下列操作,直到 closed = v:

          在所有连接opened和closed的边中找到一条最小权值的边,把在opened中那一端的顶点挪动   到closed中,该边就是最小生成树的一条边;

随机生成迷宫中每一个网格点是一个顶点,网格点与上下左右网格点连通,权值都是1,这样这个网格就也是一个加权连通图了。而且,因为知道最小权值的边就是上下左右的边,所以可以在closed集合中随机一个顶点,然后在4条边中随机一条没有使用过的边当作最小生成树的边,把墙挖通,代码如下:

template<size_t R, size_t C >

void Prim (unsigned char ver_walls [R][ C+1], unsigned char hor_walls[R +1][C]) {

unsigned char used[ R][C ] = {0};

Node nodes_avaliable [R* C];

BT bts [R* C];

size_t num_avaliable = 0

                                ;

int rv = rand()%( R*C );

nodes_avaliable[num_avaliable ] = Node( rv/C , rv% C);

used[rv /C][ rv%C ] = 1;

bts[num_avaliable ] = EMPTY;

num_avaliable++;

memset(ver_walls , 1, sizeof( unsigned char )*R*( C+1));

memset(hor_walls , 1, sizeof( unsigned char )*(R+1)* C);

while(num_avaliable >0){

int iselected = rand()% num_avaliable;

Node cur = nodes_avaliable[ iselected];

BT from = bts[ iselected];

nodes_avaliable[iselected ] = nodes_avaliable[ num_avaliable-1];

bts[iselected ] = bts[ num_avaliable-1];

num_avaliable--;

switch(from ) {

case LEFT :                             ver_walls[cur ._x][ cur._y +1]= 0; break;

case RIGHT :     ver_walls[ cur._x ][cur. _y] = 0; break ;

case TOP :                               hor_walls[cur ._x+1][ cur._y ] = 0; break;

case BOTTOM :    hor_walls[ cur._x ][cur. _y] = 0; break ;

default:break ;

                                }

// LEFT

if(cur ._y - 1 >=0 && ! used[cur ._x][ cur._y -1]) {

nodes_avaliable[num_avaliable ] = Node( cur._x , cur. _y-1);

bts[num_avaliable ] = LEFT;

num_avaliable++;

used[cur ._x][ cur._y -1]=1;

                                }

// RIGHT

if(cur ._y + 1 < C && !used [cur. _x][cur ._y+1]) {

nodes_avaliable[num_avaliable ] = Node( cur._x , cur. _y + 1);

bts[num_avaliable ] = RIGHT;

num_avaliable++;

used[cur ._x][ cur._y +1]=1;

                                }

// TOP

if (cur ._x- 1 >= 0 && ! used[cur ._x-1][ cur._y ]) {

nodes_avaliable[num_avaliable ] = Node( cur._x -1, cur. _y);

bts[num_avaliable ] = TOP;

num_avaliable++;

used[cur ._x-1][ cur._y ]=1;

                                }

// BOTTOM

if (cur ._x+1 < R && !used [cur. _x+1][cur ._y]) {

nodes_avaliable[num_avaliable ] = Node( cur._x +1, cur. _y);

bts[num_avaliable ] = BOTTOM;

num_avaliable++;

used[cur ._x+1][ cur._y ]=1;

                                }

                }

}

当然最小生成树算法还有kruskal,另外还有一些有趣的随机迷宫生成算法,维基百科描述的最清楚详尽了:http://en.wikipedia.org/wiki/Maze_generation_algorithm,以下是生成的15*15迷宫地图效果:

Image(2)

细胞自动机生成游戏地图

相对于传统的随机迷宫生成,这种方式更加注重模拟自然状态,如下是生成20*50地图的效果:

Image(3)

关于细胞自动机算法,具体描述google cellular automata,这里主要看我们是怎么用 cellular automata生成地图的:

在一个m*n的网格中,每一个cell有且有两个状态(WALL, FLOOR),每一个cell有8个邻居(上,下,左,右,左上,左下,右上,右下)。初始时把每一个cell随机置为WALL或者FLOOR,然后对每一个cell使用这样的规则,若周围是WALL的邻居个数大于5,则把自己置为WALL,若个数小于4,则把自己置为4,否则自己保持原样不变,过程中应保证边框总是WALL。这个叫做4-5规则,4和5是 cellular automata规则应用的两个参数,可以调整。这样生成出来的图就是如下效果:

Image(4)

看起来与真实地理环境比较像了,暂且把挖空的空间叫做cave,图中出现了7个彼此不连通的cave,现在需要把7个cave打通,让其不存在不可达的区域。算法思想就是,让每个cave朝图的中间延伸,最终所有cave在中间聚合,具体实现就是采用并查集这样的数据结构,每一个身为FLOOR的Cell归属于一个cave集合,初始时把FLOOR cell归属到各自的cave集合中,之后针对每一个cave,取其中一点向中心移动,遇到是WALL的Cell则挖空成FLOOR,直到遇到一个是FLOOR的Cell且和自己不是一个Cave的(代表两个Cave相聚了),或者到达了中心点就停止延伸。

细胞自动算法生成游戏地图的整个流程,代码如下:

void generation (double init_open_ratio, int low_rule_param, int up_rule_param ) {

if (_w <= 2 || _h <=2) return;

init_map(init_open_ratio );

cellular_automata(low_rule_param , up_rule_param);

make_cave();

connection();

                }

以上代码在这里可以得到。

posted @ 2013-12-20 21:42  persistentsnail  阅读(1777)  评论(0编辑  收藏  举报