本来一直觉得js是个让人混乱不堪的语言,html5的出现改变了我对它的看法。到了html5的时代,各种犀利的设计就更明显的需要js了。看了一些小游戏设计,忽然来了兴趣,于是写了几个小游戏,这是其中一个。已经开源,欢迎下载https://github.com/yangyusong/ChainReaction。
游戏叫连锁反应,这个游戏是看到有人在ipad上面玩的游戏,觉得好玩,自己实现一遍。游戏是这样的,一群小球在区域内弹来弹去,玩家鼠标点击一个地方,在一个圆的范围内,小球碰上就会爆炸,爆炸的过程中其他小球碰上也会发生爆炸,这就叫连锁反应。每一关爆破一定数量的小球就算胜利。
我的设计中一个有20关,数值增长比较平和,运气不是太差的话都能一次通关。在说有个再玩本级的功能,过一关的压力是一点都没有。这样设计是为了给工作后的朋友缓解压力。我们轻松的一点就爆炸一片。
看看截图
大的那个灰色的(其实是半透明的)圆是鼠标范围。其他是弹来弹去的小球。可以看到,小球是各种颜色的。
我们看看小球的定义
function Circle(x, y, xSpeed, ySpeed, radius, color, liveTime, state){
//圆心坐标
this.x = x;
this.y = y;
//运动速度
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
//半径
this.radius = radius;
//颜色
this.color = color;
//生存计数器
this.liveTime = liveTime;
//状态:
this.state = state;
}
其中生存计数器是要和状态结合使用的,状态分为如下5个状态
//小球状态
var SMALL = 0;
var BIG = 1;
var EXPEND = 2;
var END = 3;
var DIS_VISIBLE = 4;
当处于EXPEND状态的时候,就说明小球进入爆炸状态,这时候生存计数器就用上了。计数器是个倒计时,计时到零,小球进入DIS_VISIBLE状态。这时候小球就不再渲染出来。
我们的小球有不同大小,不同颜色,看看小球的初始化就知道了,代码在ObjectMgr.js中
for(i = 0; i < g_StepsArr[g_Steps].ballsNum; i++){
//_Util.dump_obj(_Color.color_str(new Color(Math.random(), Math.random(), Math.random())));
var raduis = _Util.random_range(SMALL_RADIUS1, SMALL_RADIUS2);
this.circles.push(new Circle(
_Util.random_range(raduis*2, this.canvasWidth-2*raduis),
_Util.random_range(raduis*2, this.canvasHeight-2*raduis),
_Util.random(SPEED_MIN, SPEED_MAX),
_Util.random(SPEED_MIN, SPEED_MAX),
raduis,
_Color.color_rgba_str(new Color1(Math.random(), Math.random(), Math.random(), 0.8)),
MID_LIVE_TIME,
SMALL
));
}
其中g_StepsArr负责我们关卡的管理,有这一关的小球数,和通关需要爆破的小球数。总之,这里按照本关需要的小球数初始化小球,可以看到里面有很多的随机函数使用。小球的半径处于如下两个数之间
var SMALL_RADIUS1 = 3;
var SMALL_RADIUS2 = 10;
通过random_range来进行这个范围随机。我们看到速度也是随机的,范围是
var SPEED_MIN = 10;
var SPEED_MAX = 50;
颜色中的color_rgba_str函数的第四个参数说明我们的每个小球的透明度是0.8,这样我们就能在爆破的时候,或弹动的时候仍然看清其他小球。这段代码就说到这。
我们讲讲主要流程,其实其中的详细注释,我觉得已经可以教会很多初学者。不过还是讲讲好。主要流程就在Main.js中。负责初始化,渲染和循环。开始我们设置了一堆全局变量。
var g_ObjectMgr = null;
var g_MouseEventDispatch = new MouseEventDispatch();
var g_MouseMgr = null;//g_MouseMgr在g_ObjectMgr初始化后才初始化
//当前关
var g_Steps = 1;//todo 显示出来
//关卡数组
var g_StepsArr = [];
g_StepsArr = stepsInit();
//爆炸开始标识
var _ExpendStart = false;
if(DEBUG){
_CircleLib.test();
}
var _Main = { 。。。
包括关卡数组,当前关数,爆炸标识等。居然还设置了一个是否调试的状态量,其实我也不知道js调试怎样才好,基本就按自己的方式调。 _Main是个很大的结构。我更宁愿把它当做单例来思考。主要是,它包括了渲染,这个渲染不具通用性,仅此一例就够。其他地方用了且不是画出什么就难说了。当然已经设计其实我会更多考虑通用性的设计,尽量不设计成这种单例。
这个_Main结构中有我们的画布canvas,我们的初始化函数,每关调用一次,它来负责2d对象的初始化,游戏对象的初始化。鼠标监听初始化。然后就是进入我们的循环。循环很简单,就干四件事情
/*
* 循环绘图
* 1.清空画面
* 2.游戏对象关系处理
* 3.渲染出来
* 4.循环调用
*/
step: function(){
this.clear();
g_ObjectMgr.step();
this.render();
_this = this;
this._st = setTimeout(function(){
_this.step();
}, 50);
}
看看我们的下一关都干些什么
/*
* 下一关
*/
nextStep: function(){
clearTimeout(this._st);
if(this.canvas.getContext)
{
g_MouseEventDispatch.start();
this.initObjects();
this.step();
}
}
它就是清除计时器,重新分配事件,初始化对象。然后进入循环,为什么是这样呢?清除计时器以使我们之前的循环停止。因为我们马上有新的循环了,其实事件可以看做有两个状态,我们按下鼠标的时候,这个事件就不可用了,下次使用必须初始化。小球数量变了,必须按照本关的需求来初始化。进入循环,新的循环开始。
看看再玩一次(本级)按钮的调用:
/*
* 再玩一次(本级)
*/
again: function(){
this.nextStep()
}
为什么居然是调用下一关呢?只能说我设计的太懒惰,nextStep()本身根本不管关卡的变更。关卡的变更完全在爆炸检查函数里,一旦发现小球爆炸,就会修改当前关卡。而单纯调用nextStep所使用的关卡是未改变过的,故而是在玩本级。
我们再看一下ObjectMgr.js中的爆炸检查函数
expendCheck: function(){
if(_ExpendStart){
this.expendNum = _CircleLib.intersect(this.circles, g_MouseMgr.mouseCircle);
// _Util.dump_obj(g_StepsArr[g_Steps])
if(this.expendNum >= g_StepsArr[g_Steps].killNum){
var next = g_Steps + 1;
alert("成功爆破超过"+this.expendNum+"个小球,恭喜进入第"+ next + "关,\n\
下一关需要爆破" +g_StepsArr[g_Steps + 1].killNum + "个小球");
g_Steps++;
_Main.init();
}
}
}
还记得_ExpendStart这个变量的意思么?就是说鼠标是否按下了,按下的话我们就要检查是否有小球撞上鼠标范围或撞上爆炸中的小球。其实这里的调用_CircleLib.intersect这个函数是有些小问题的。它是通过引用修改的当前小球的状态,至于为什么有很少量的小球未修改状态,这个我还没弄明白。总之,这个函数检查了爆炸小球的数量,一旦爆炸小球的数量符合本关的要求,那么就可以进入下一关,可以看到我们进入下一关的提示是一个对话框,不是很友好,可以设计为一个图片较好,可惜我没时间找美术。
点击确定,我们玩下一关
小球多了很多,找个好点的位置,能捕捉很多小球。
看一下爆炸过程吧:
这是第十八关的一个爆炸情形,更具体的内容欢迎看具体代码吧,要不还讲好长时间。
做完这个例子,发现其实非常多的小游戏很好设计,可惜没那么多时间,再说设计别人设计过的游戏也不是我的目标。设计一些有趣的小游戏到手机里,这个倒是个不错的方向