zoukankan      html  css  js  c++  java
  • 游戏地图动态生成

    随机迷宫生成

    游戏地图的动态生成要追溯到传统的随机迷宫生成。随机迷宫生成可以描述成这样的问题:在一个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();
    
                    }

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

  • 相关阅读:
    VijosP1274:神秘的咒语
    2009年浙大 :找出直系亲属
    django用户信息扩展
    缓存
    自定义认证
    自定义admin
    权限的配置和使用
    form表单
    过滤器 自定义查询
    中间件
  • 原文地址:https://www.cnblogs.com/persistentsnail/p/3484591.html
Copyright © 2011-2022 走看看