背景:前段时间自己居然迷路了,有感而发就想到写一个可以让人迷路的小游戏,可以消(bao)遣(fu)时(she)间(hui)
没有使用threejs,就连glMatrix也没有用,纯原生webgl干,写起来还是挺累的,不过代码结构还是挺清晰的,注释也挺全的,点开全文开始迷宫之旅~
毕竟要赚一点PV,所以开头没有贴地址,现在贴地址:
github:https://github.com/westAnHui/3Dmaze
在线试玩:https://westanhui.github.io/3Dmaze/3Dmaze2.html
游戏操作:鼠标控制方向,w前进,s后退,切记方向键没用啊!
迷宫本身的比较简陋,没加光和阴影啥的,挺赶的一个demo。不过这篇文章不是介绍webgl技术为主的,主要是讲解整个游戏开发的情况,let’s go~
1、生成2D迷宫
迷宫游戏嘛,肯定迷宫是主体。大家可以从游戏中看到,我们的迷宫分为2D迷宫和3D迷宫,首先说2D迷宫,它是3D迷宫的前提
生成迷宫有三种方式
a)深度优先
一言不合贴源码:https://github.com/westAnHui/3Dmaze/blob/master/maze1.html
先看一下用深度优先法生成迷宫的图吧
我们看下迷宫的特点,发现有一条很明显的主路,是不是能理解算法名中“深度优先”的含义了。简单介绍一下算法的原理:
知道了原理,我们着手来制造2D迷宫~
首先得确定墙和路的关系,考虑到迷宫转化为3D之后墙立体一点,我们就不要用1px的线来模拟墙了,那样3D之后不够饱满~
这里我们设置墙的厚度为路的宽度,都是10px,然后我们的底图应该是这样子的(注:理解这幅图最为关键):
白色部分是路,也可以理解为原理中所说的邻格,这是可以达到的
灰色部分是墙,这个墙可能会打通,也可能没有打通
黑色部分是墙,这个墙是不可能打通的!!
如果脑子没转过来就看下图,转化理解
红线就是玩家的路径啦,其中我们看到穿过了三个黑色的矩形,这就是上面所说的灰色格,可能打通,也可能没打通,蓝色那块就是没打通的情况;而黑色部分正对应上面的黑色格,这是不可能打通的,如果把墙看成一个面(灰色蓝色部分再压缩),黑色就变成一个点,是横墙与竖墙的交点,玩家不会走交点上面走的~
好,下面就是套算法的过程啦,写的过程中我把墙的部分给省略了,全部考虑成路
以宽度来解释,Canvas宽度390px,有18列的路(20列的墙暂时被我无视),不理解的可以对照图看一下
initNeighbor方法是获得邻格用的,注意最后有一个随机,将它的邻格打乱,这样我们在getNeighbor 中获取邻格就很方便了
这里比较核心,注释给的也比较全,结合前面的原理图应该很好懂
再看下maze里面的findPath方法,在这里面调用的getNeighbor方法
可以看到parent和children属性是不是本能的就反应起树的概念了,那不就是深度的思想么~
核心的代码讲解了,其他的画图部分就不介绍了,在drawPath方法里面,原理就是先画一个节点(一个格子),然后它的children格和它打通(前面图中灰色格子转为白色),再去画children格……
注:开头给的试玩demo用的不是深度优先算法,下面这个是深度优先生成的迷宫游戏,可以感受一下,这样与开头的有一个对比
https://westanhui.github.io/3Dmaze/3Dmaze1.html
b)广度优先(prim随机)
一言不合贴源码:https://github.com/westAnHui/3Dmaze/blob/master/maze2.html
再看一下广度优先生成的迷宫图~可以和上面的对比一下
前面说的深度优先算法挺好理解的,人类语言表达出来就是“一直走,能走多远走多远,发现不通了,死路了,再回去想想办法”。
但是,用深度优先算法在迷宫游戏中有很致命的一个缺点,就是简单,那条明显的主路让玩家不看2D地图都能轻松的绕出来(路痴退散),这明显不符合开头所说的消(bao)遣(fu)时(she)间(hui)的主题,那么正主来啦~
prim(普里姆)算法是传统迷宫游戏的标准算法,岔路多,复杂。我觉得有广度优先的思想,所有自己也称广度优先算法,正好和上一个对应上。贴原理图~
人类语言表达出来就是“随机的方式将地图上的墙尽可能打通”,还记得这个底图么,照着这个底图我解释一下
选择1为起点,并标记。1的邻墙有2,3,放入数组中。
此时数组[2, 3],随机选择一个,比如我们选到了2,2的对面格是4,此时4没有被标记过,打通2(将2由灰变成白色),并将4标记,并把5,6放入数组
此时数组[2, 3, 5, 6],继续随机……
结合一下源码,发现这次写法和上次的完全不同了,在深度优先中我们直接没考虑墙的存在,主体是路(白色的格子),将他们变成树的结构即可,在后面绘制部分再会考虑墙的位置
而在广度优先中,我认为主体是墙(灰色的格子),所以算法中一定要把墙的概念带上,在initNeighbor方法中路(白色格)的邻格已经是+2而不是之前的+1了,因为+1是墙(灰色格)
再看重要的getNeighbor方法