本來一直覺得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這個函數是有些小問題的。它是通過引用修改的當前小球的狀態,至于為什么有很少量的小球未修改狀態,這個我還沒弄明白。總之,這個函數檢查了爆炸小球的數量,一旦爆炸小球的數量符合本關的要求,那么就可以進入下一關,可以看到我們進入下一關的提示是一個對話框,不是很友好,可以設計為一個圖片較好,可惜我沒時間找美術。

點擊確定,我們玩下一關

小球多了很多,找個好點的位置,能捕捉很多小球。
看一下爆炸過程吧:

這是第十八關的一個爆炸情形,更具體的內容歡迎看具體代碼吧,要不還講好長時間。
做完這個例子,發現其實非常多的小游戲很好設計,可惜沒那么多時間,再說設計別人設計過的游戲也不是我的目標。設計一些有趣的小游戲到手機里,這個倒是個不錯的方向