>

用webgl打造自己的3D迷宫游戏

- 编辑:至尊游戏网站 -

用webgl打造自己的3D迷宫游戏

用webgl塑造自个儿的3D迷宫游戏

2016/09/19 · JavaScript · WebGL

最先的作品出处: AlloyTeam   

背景:近日本人以致迷路了,有感而发就想开写三个方可令人迷失的小游戏,能够消(bao)遣(fu)时(she)间(hui)

未曾行使threejs,就连glMatrix也从不用,纯原生webgl干,写起来依然挺累的,可是代码结构依旧挺清晰的,注释也挺全的,点开全文早先迷宫之旅至尊游戏网站,~

到底要赚一点PV,所以最先未有贴地址,今后贴地址:

github:

在线试玩:

娱乐操作:鼠标调整方向,w前行,s后退,纪事方向键没用啊!

迷宫本人的可比简陋,没加光和影子啥的,挺赶的三个demo。可是那篇作品不是介绍webgl本领为主的,主如若教课整个游戏支付的情况,let’s go~

 

1、生成2D迷宫

迷宫游戏嘛,明确迷宫是重头戏。大家能够从游戏中观察,我们的迷宫分为2D迷宫和3D迷宫,首先说2D迷宫,它是3D迷宫的前提

转移迷宫有三种方式

a卡塔 尔(英语:State of Qatar)深度优先

一言不合贴源码:

先看一下用深度优先法生成迷宫的图吧

至尊游戏网站 1

大家看下迷宫的特性,开采存一条很确定的主路,是否能精晓算法名中“深度优先”的意义了。简要介绍一下算法的准则:

至尊游戏网站 2

明亮了规律,我们早先来制作2D迷宫~

第一得规定墙和路的涉嫌,思虑到迷宫转变为3D之后墙立体一点,大家就不用用1px的线来模拟墙了,那样3D之后远远不足充沛~

那边大家设置墙的厚度为路的升幅,都是10px,然后我们的底图应该是那样子的(注:明白这幅图最为重要卡塔尔:

至尊游戏网站 3

白灰部分是路,也可以驾驭为原理中所说的邻格,那是能够到达的

橄榄绿部分是墙,那几个墙唯恐会开采,也可能未有开采

白灰部分是墙,这一个墙是不容许打通的!!

假使头脑没转过来就看下图,转变精晓

至尊游戏网站 4

红线就是游戏的使用者的路线啦,个中我们看来穿过了多个湖蓝的矩形,那便是地点所说的青黑格,大概打通,也恐怕没发现,浅湖蓝那块便是没开采的情况;而金色部分正对应下面的深墨蓝格,那是不容许打通的,要是把墙看成一个面(赤褐中黄部分再压缩),碧绿就成为八个点,是横墙与竖墙的交点,游戏的使用者不会走交点下边走的~

好,下边正是套算法的进度啦,写的经过中自己把墙的一些给省略了,全部设想成路

JavaScript

var maxX = 18; var maxY = 13;

1
2
var maxX = 18;
var maxY = 13;

以小幅度来解释,Canvas宽度390px,有18列的路(20列的墙一时被小编不留意卡塔 尔(英语:State of Qatar),不知晓的能够相比较图看一下

initNeighbor方法是获取邻格用的,注意最后有三个随便,将它的邻格打乱,那样大家在getNeighbor 中得到邻格就很有益了

JavaScript

Grid.prototype.getNeighbor = function() {     var x, y, neighbor;     this.choosed = true; // 标志当前格     for(var i = 0; i < this.neighbor.length; i++) {         x = this.neighbor[i].x;         y = this.neighbor[i].y;         neighbor = maze.grids[y][x];         if(!neighbor.choosed) { // 邻格是不是标识过             neighbor.parent = this; // 选中的邻格父级为当前格             return neighbor;         }     }     if(this.parent === firstGrid) {         return 0; // 结束     } else {         return 1; // 这里是邻格都被标志过,重返父级     } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Grid.prototype.getNeighbor = function() {
    var x, y, neighbor;
    this.choosed = true; // 标记当前格
    for(var i = 0; i < this.neighbor.length; i++) {
        x = this.neighbor[i].x;
        y = this.neighbor[i].y;
        neighbor = maze.grids[y][x];
        if(!neighbor.choosed) { // 邻格是否标记过
            neighbor.parent = this; // 选中的邻格父级为当前格
            return neighbor;
        }
    }
    if(this.parent === firstGrid) {
        return 0; // 结束
    } else {
        return 1; // 这里是邻格都被标记过,返回父级
    }
};

此地比较基本,注释给的也相比较全,结合前边的原理图应该很好懂

再看下maze里面的findPath方法,在那面调用的getNeighbor方法

JavaScript

Maze.prototype.findPath = function() {     var tmp;     var curr = firstGrid; // 先鲜明源点     while(1) {         tmp = curr.getNeighbor(); // 获得邻格         if(tmp === 0) {             console.log('路线寻觅停止');             break;         } else if(tmp === 1) { // 邻格都被标识,回到父级             curr = curr.parent;         } else { // 找到了一个没被标识的邻格,存起来             curr.children[curr.children.length] = tmp;             curr = tmp;         }     } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Maze.prototype.findPath = function() {
    var tmp;
    var curr = firstGrid; // 先确定起点
    while(1) {
        tmp = curr.getNeighbor(); // 获得邻格
        if(tmp === 0) {
            console.log('路径找寻结束');
            break;
        } else if(tmp === 1) { // 邻格都被标记,回到父级
            curr = curr.parent;
        } else { // 找到了一个没被标记的邻格,存起来
            curr.children[curr.children.length] = tmp;
            curr = tmp;
        }
    }
};

能够看来parent和children属性是否本能的就影响起树的概念了,那不正是深浅的思谋么~

大旨的代码批注了,别的的图案部分就不介绍了,在drawPath方法里面,原理正是先画一个节点(二个格子),然后它的children格和它打通(前边图豆樱桃红格子转为白灰),再去画children格……

注:开首给的试玩demo用的不是深度优先算法,上边这些是深浅优先生成的迷宫游戏,能够体会一下,那样与伊始的有贰个对照

b卡塔尔广度优先(prim随机)

一言不合贴源码:

再看一下广度优先生成的迷宫图~能够和下面的自己检查自纠一下

至尊游戏网站 5

前边说的深度优先算法蛮好驾驭的,人类语言表明出来就是“一向走,能走多少间距走多少间隔,开采不通了,死路了,再回到思忖办法”。

唯独,用深度优先算法在迷宫游戏中有很致命的叁个劣点,正是轻松,那条鲜明的主路让游戏者不看2D地形图都能轻轻巧松的绕出来(路痴退散),那鲜明不相符初叶所说的消(bao)遣(fu)时(she)间(hui)的主题,那么正主来啊~

prim(普Rim卡塔尔国算法是金钱观迷宫游戏的正规算法,岔路多,复杂。作者以为有广度优先的思维,全部自身也称广度优先算法,适逢其会和上多少个八方呼应上。贴原理图~

至尊游戏网站 6

人类语言表达出来便是“随机的艺术将地图上的墙尽恐怕打通”,还记得那几个底图么,照着那些底图小编解释一下

至尊游戏网站 7

选料1为起源,并标识。1的邻墙有2,3,放入数组中。

当时数组[2, 3],随机选拔三个,比方大家选到了2,2的对面格是4,那时4尚无被标志过,打通2(将2由灰产生黄绿卡塔 尔(英语:State of Qatar),并将4符号,并把5,6放入数组

此刻数组[2, 3, 5, 6],继续轻松……

结合一下源码,开掘本次写法和上次的一丝一毫差别了,在深度优先中我们平素没思索墙的存在,主导是路(中黄的格子卡塔尔,将她们产生树的构造即可,在背后绘制部分再会设想墙的职位

而在广度优先中,小编以为重点是墙(紫藤色的格子卡塔 尔(英语:State of Qatar),所以算法中势供给把墙的定义带上,在initNeighbor方法南路(墨紫格)的邻格已是+2而不是事先的+1了,因为+1是墙(深灰蓝格)

再注重大的getNeighbor方法

JavaScript

Grid.prototype.getNeighbor = function() {     var x, y, neighbor, ret = [];     this.choosed = true;     for(var i = 0; i < this.neighbor.length; i++) {         x = this.neighbor[i].x;         y = this.neighbor[i].y;         neighbor = maze.grids[y][x];         neighbor.wallX = this.x + (x - this.x)/2; // 重要!         neighbor.wallY = this.y + (y - this.y)/2; // 重要!         if(!neighbor.choosed) {             ret.push(neighbor);         }     }     return ret; };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Grid.prototype.getNeighbor = function() {
    var x, y, neighbor, ret = [];
    this.choosed = true;
    for(var i = 0; i < this.neighbor.length; i++) {
        x = this.neighbor[i].x;
        y = this.neighbor[i].y;
        neighbor = maze.grids[y][x];
        neighbor.wallX = this.x + (x - this.x)/2; // 重要!
        neighbor.wallY = this.y + (y - this.y)/2; // 重要!
        if(!neighbor.choosed) {
            ret.push(neighbor);
        }
    }
    return ret;
};

看起来大家拿到的是邻格,但实际我们要的是挂载在邻格上的wallX和wallY属性,所以大家得以把neighbor抽象的就当做是墙!!在下边findPath方法中就是如此用的

JavaScript

Maze.prototype.findPath = function() {     var tmp;     var curr = firstGrid;     var index;     var walls = this.walls;     tmp = curr.getNeighbor();     curr.isClear = true; // 标记     walls.push.apply(walls, tmp);     while(walls.length) {         index = (Math.random() * walls.length) >> 0; // 随机取         wall = walls[index];         if(!wall.isClear) { // 假设不是通路             wall.isClear = true;             this.path.push({                 x: wall.wallX, // 主要!                 y: wall.wallY // 主要!             });             tmp = wall.getNeighbor();             walls.push.apply(walls, tmp); // 参加更多的墙         } else {             walls.splice(index, 1); // 如若是通路了就移除         }     }     console.log('路线找出停止', this.path); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Maze.prototype.findPath = function() {
    var tmp;
    var curr = firstGrid;
    var index;
    var walls = this.walls;
    tmp = curr.getNeighbor();
    curr.isClear = true; // 标记
    walls.push.apply(walls, tmp);
    while(walls.length) {
        index = (Math.random() * walls.length) >> 0; // 随机取
        wall = walls[index];
        if(!wall.isClear) { // 如果不是通路
            wall.isClear = true;
            this.path.push({
                x: wall.wallX, // 重要!
                y: wall.wallY // 重要!
            });
            tmp = wall.getNeighbor();
            walls.push.apply(walls, tmp); // 加入更多的墙
        } else {
            walls.splice(index, 1); // 如果是通路了就移除
        }
    }
    console.log('路径找寻结束', this.path);
};

只要感到有一些绕的话能够构成原理图再稳步的看代码,核情感解的少数就是getNeighbor方法再次来到的x,y对应是路(茶绿格卡塔尔,而它的wallX,wallY对应的是墙(宝蓝格卡塔 尔(阿拉伯语:قطر‎

画图部分很简短

JavaScript

for(i = 0; i <= 290; i+=20) { // 隔行画横线(横墙)     ctx.fillRect(0, i, 390, 10); }   for(i = 0; i <= 390; i+=20) { // 隔行画竖线(竖墙)     ctx.fillRect(i, 0, 10, 290); }   ctx.fillStyle = 'white';   for(i = 0; i < this.path.length; i++) { // 打通墙     ctx.fillRect(10 + this.path[i].x * 10, 10 + this.path[i].y * 10, 10, 10); }

1
2
3
4
5
6
7
8
9
10
11
12
13
for(i = 0; i <= 290; i+=20) { // 隔行画横线(横墙)
    ctx.fillRect(0, i, 390, 10);
}
 
for(i = 0; i <= 390; i+=20) { // 隔行画竖线(竖墙)
    ctx.fillRect(i, 0, 10, 290);
}
 
ctx.fillStyle = 'white';
 
for(i = 0; i < this.path.length; i++) { // 打通墙
    ctx.fillRect(10 + this.path[i].x * 10, 10 + this.path[i].y * 10, 10, 10);
}

c卡塔尔国递归分割法

本条实际上是顶级轻易,原理轻巧,算法轻松,笔者就不介绍啦。一来那么些转变的迷宫也一流不难,平常不用于古板迷宫游戏;二来背后还会有众多要介绍的,不浪费口舌在这里了

 

2、生成3D迷宫

当时大家已经有三个2D迷宫,大家得以将其看做是俯视图,上边就是将其转变为3D极端音信

注:那篇小说不担负介绍webgl!!作者也硬着头皮逃避webgl知识,通俗一点的牵线给我们~

将2D转3D,首先非常关键的有些正是坐标系的转会

2D的坐标系是如此的

至尊游戏网站 8

3D的坐标系是那样的

至尊游戏网站 9

感到到到蛋疼就对了~前面思量到油画机近平面包车型客车撞击总括还得蛋碎呢~

实际上这一个坐标调换并轻易,首先大家先经过2D迷宫获得墙面包车型大巴新闻(茶青部分卡塔 尔(阿拉伯语:قطر‎

下边这段代码是获得横墙新闻的

JavaScript

function getRowWall() {     var i = 0;     var j = 0;     var x1, x2;     console.log('getRowWall');     for(; i < height; i += 10) {         rowWall[i] = [];         j = 0;         while(j < width) {             if(isBlack(j, i)) {                 x1 = j; // 记录横墙牵头点                                  j += 10;                 while(isBlack(j, i) && j < width) {                     j += 10;                 }                   x2 = j; // 记录横墙结束点                 if((x2 - x1) > 10) { // 那步很首要!!                     rowWall[i].push({                         x1: 2 * (x1 / width) - 1,                         x2: 2 * (x2 / width) - 1                     });                 }             }               j += 10;         }     }       // console.log(rowWall); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function getRowWall() {
    var i = 0;
    var j = 0;
    var x1, x2;
    console.log('getRowWall');
    for(; i < height; i += 10) {
        rowWall[i] = [];
        j = 0;
        while(j < width) {
            if(isBlack(j, i)) {
                x1 = j; // 记录横墙开始点
                
                j += 10;
                while(isBlack(j, i) && j < width) {
                    j += 10;
                }
 
                x2 = j; // 记录横墙结束点
                if((x2 - x1) > 10) { // 这步很关键!!
                    rowWall[i].push({
                        x1: 2 * (x1 / width) - 1,
                        x2: 2 * (x2 / width) - 1
                    });
                }
            }
 
            j += 10;
        }
    }
 
    // console.log(rowWall);
}

结果会拿走叁个数组,注意一下注脚中很要紧的一步,为何要大于10

下面两张图给您答案

至尊游戏网站 10

至尊游戏网站 11

小结正是稍差于等于10px的横墙,那它的本体一定是竖墙,10px也是那意气风发行偏巧见到的,我们就将她们过滤掉了

拿到竖墙音信同理,源码可以预知,小编就不贴出来了

上面这段代码是2D坐标转化为尖峰音讯

JavaScript

// k1和k2算作Z轴 for(i = 0; i < rowWall.length; i += 10) { // rowWall.length     item = rowWall[i];     while((tmp = item.pop())) {         k1 = (2 * i / height) - 1;         k2 = (2 * (i + 10) / height) - 1;         po_data.push.apply(po_data, [             tmp.x1*120+0.01, -1.09, k1*120, // 左下             tmp.x2*120+0.01, -1.09, k1*120, // 右下             tmp.x2*120+0.01, 0.2, k1*120, // 右上             tmp.x1*120+0.01, 0.2, k1*120, // 左上               tmp.x2*120+0.01, -1.09, k1*120,             tmp.x2*120+0.01, -1.09, k2*120,             tmp.x2*120+0.01, 0.2, k2*120,             tmp.x2*120+0.01, 0.2, k1*120,               tmp.x1*120+0.01, -1.09, k2*120,             tmp.x2*120+0.01, -1.09, k2*120,             tmp.x2*120+0.01, 0.2, k2*120,             tmp.x1*120+0.01, 0.2, k2*120,               tmp.x1*120+0.01, -1.09, k1*120,             tmp.x1*120+0.01, -1.09, k2*120,             tmp.x1*120+0.01, 0.2, k2*120,             tmp.x1*120+0.01, 0.2, k1*120,               tmp.x1*120+0.01, 0.2, k1*120,             tmp.x2*120+0.01, 0.2, k1*120,             tmp.x2*120+0.01, 0.2, k2*120,             tmp.x1*120+0.01, 0.2, k1*120         ]);     } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// k1和k2算作Z轴
for(i = 0; i < rowWall.length; i += 10) { // rowWall.length
    item = rowWall[i];
    while((tmp = item.pop())) {
        k1 = (2 * i / height) - 1;
        k2 = (2 * (i + 10) / height) - 1;
        po_data.push.apply(po_data, [
            tmp.x1*120+0.01, -1.09, k1*120, // 左下
            tmp.x2*120+0.01, -1.09, k1*120, // 右下
            tmp.x2*120+0.01, 0.2, k1*120, // 右上
            tmp.x1*120+0.01, 0.2, k1*120, // 左上
 
            tmp.x2*120+0.01, -1.09, k1*120,
            tmp.x2*120+0.01, -1.09, k2*120,
            tmp.x2*120+0.01, 0.2, k2*120,
            tmp.x2*120+0.01, 0.2, k1*120,
 
            tmp.x1*120+0.01, -1.09, k2*120,
            tmp.x2*120+0.01, -1.09, k2*120,
            tmp.x2*120+0.01, 0.2, k2*120,
            tmp.x1*120+0.01, 0.2, k2*120,
 
            tmp.x1*120+0.01, -1.09, k1*120,
            tmp.x1*120+0.01, -1.09, k2*120,
            tmp.x1*120+0.01, 0.2, k2*120,
            tmp.x1*120+0.01, 0.2, k1*120,
 
            tmp.x1*120+0.01, 0.2, k1*120,
            tmp.x2*120+0.01, 0.2, k1*120,
            tmp.x2*120+0.01, 0.2, k2*120,
            tmp.x1*120+0.01, 0.2, k1*120
        ]);
    }
}

乘以120是本身3D空间中X轴和Z轴各拓展了120倍,没有写在模型转换矩阵里面,Y轴的办法在模型变化矩阵中,但是那不主要。

数组中四个单位为有个别,多个点为三个面,三个面为3D迷宫中意气风发堵墙(底面的无论卡塔尔国

末端是webgl里面符合规律操作,种种矩阵、绑定buffer、绑定texture等等balabala,原生webgl写起来是相比较累,无视了光和阴影还要写这么多T_T

 

3、摄像机碰撞检查实验

若是说后面包车型客车代码写着很累瞧着累,那这里的就更累了……

摄像机是如何?在3D中录制机就是游戏的使用者的观念,正是通过鼠标和w,s来运动的webgl可视区,那么在2D中录像机映射为何吧?

2D中录像机正是甲午革命的那二个圈圈的右点,如图!

至尊游戏网站 12

那正是说大的框框只是便于看而已……

碰撞检查评定的效果与利益是幸免现身透视现象,透视现象如下图所示:

至尊游戏网站 13

要介绍透视现象现身的案由,就得先领悟一下视锥体,如图:

至尊游戏网站 14

观看近平面了呢,当物体穿过近平面,就能够并发透视现象了

至尊游戏网站 15

我们娱乐中近平面间隔是0.1,所以或然看成围绕原点有三个矩形,只要让矩形蒙受不边,那就不会现出透视现象

矩形的增长幅度笔者设置为2,设大了有个别,也没供给让游戏用户贴墙贴的那么近……

咱俩由此调用录制机的move方法触发Role.prototype.update方法

JavaScript

move: function(e){     // 只思考x和z轴移动,cx,cy是退换为2D的势头    cx = Math.sin(-this.rot) * e;     cy = Math.cos(-this.rot) * e;         this.x += cx;     this.z += cy;       ret = role.check(-this.x/120, this.z/242, -cx, cy); // 后五个参数代表方向       if(ret.x === 0) {         this.x -= cx;     } else {         role.x = ret.x;     }       if(ret.y === 0) {         this.z -= cy;     } else {         role.y = ret.y;     }       role.update(); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
move: function(e){
    // 只考虑x和z轴移动,cx,cy是转换为2D的方向
    cx = Math.sin(-this.rot) * e;
    cy = Math.cos(-this.rot) * e;
 
 
    this.x += cx;
    this.z += cy;
 
    ret = role.check(-this.x/120, this.z/242, -cx, cy); // 后两个参数代表方向
 
    if(ret.x === 0) {
        this.x -= cx;
    } else {
        role.x = ret.x;
    }
 
    if(ret.y === 0) {
        this.z -= cy;
    } else {
        role.y = ret.y;
    }
 
    role.update();
}

而update方法里面更新x0,x2,y0,y2正是对应那些点,这七个点在check方法里面用到,check通过则运动摄像机,不然不移动

摄像机与墙的总体格检查测在Role.prototype.isWall中,注意这里有七个参数,cx和cy,这一个是可行性,确切的乃是快要移动的方向,然后我们依据方向,只会从那多个点中取八个来判别会不会有相撞

至尊游戏网站 16

各样点的检查实验通过Role.prototype.pointCheck方法,通过像一向剖断的,发掘是土黑值(rgb中的r为0)那么就感到撞上了,会在2D中标识深灰。假若你贴着墙走,就能够意识灰绿的墙都被染成浅青啦~

 

结语:

写累死,那如故在把webgl里面知识点抢先57%甩掉的情景下。迷宫全体比较容易,就两张贴图,地面也很简陋,近期需求比很多,很忙,没太多时光去美化。风乐趣的同桌能够做黄金时代款归属本人棒棒的迷宫游戏~

感兴趣十分的能够留言一同交换~

4 赞 2 收藏 评论

至尊游戏网站 17

本文由门户名站发布,转载请注明来源:用webgl打造自己的3D迷宫游戏