一個(gè)小游戲ChainReaction的設(shè)計(jì)(html5)
本來一直覺得js是個(gè)讓人混亂不堪的語言,html5的出現(xiàn)改變了我對它的看法。到了html5的時(shí)代,各種犀利的設(shè)計(jì)就更明顯的需要js了。看了一些小游戲設(shè)計(jì),忽然來了興趣,于是寫了幾個(gè)小游戲,這是其中一個(gè)。已經(jīng)開源,歡迎下載https://github.com/yangyusong/ChainReaction。
游戲叫連鎖反應(yīng),這個(gè)游戲是看到有人在ipad上面玩的游戲,覺得好玩,自己實(shí)現(xiàn)一遍。游戲是這樣的,一群小球在區(qū)域內(nèi)彈來彈去,玩家鼠標(biāo)點(diǎn)擊一個(gè)地方,在一個(gè)圓的范圍內(nèi),小球碰上就會爆炸,爆炸的過程中其他小球碰上也會發(fā)生爆炸,這就叫連鎖反應(yīng)。每一關(guān)爆破一定數(shù)量的小球就算勝利。
我的設(shè)計(jì)中一個(gè)有20關(guān),數(shù)值增長比較平和,運(yùn)氣不是太差的話都能一次通關(guān)。在說有個(gè)再玩本級的功能,過一關(guān)的壓力是一點(diǎn)都沒有。這樣設(shè)計(jì)是為了給工作后的朋友緩解壓力。我們輕松的一點(diǎn)就爆炸一片。
看看截圖
大的那個(gè)灰色的(其實(shí)是半透明的)圓是鼠標(biāo)范圍。其他是彈來彈去的小球??梢钥吹剑∏蚴歉鞣N顏色的。
我們看看小球的定義
function Circle(x, y, xSpeed, ySpeed, radius, color, liveTime, state){
//圓心坐標(biāo)
this.x = x;
this.y = y;
//運(yùn)動速度
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
//半徑
this.radius = radius;
//顏色
this.color = color;
//生存計(jì)數(shù)器
this.liveTime = liveTime;
//狀態(tài):
this.state = state;
}
其中生存計(jì)數(shù)器是要和狀態(tài)結(jié)合使用的,狀態(tài)分為如下5個(gè)狀態(tài)
//小球狀態(tài)
var SMALL = 0;
var BIG = 1;
var EXPEND = 2;
var END = 3;
var DIS_VISIBLE = 4;
當(dāng)處于EXPEND狀態(tài)的時(shí)候,就說明小球進(jìn)入爆炸狀態(tài),這時(shí)候生存計(jì)數(shù)器就用上了。計(jì)數(shù)器是個(gè)倒計(jì)時(shí),計(jì)時(shí)到零,小球進(jìn)入DIS_VISIBLE狀態(tài)。這時(shí)候小球就不再渲染出來。
我們的小球有不同大小,不同顏色,看看小球的初始化就知道了,代碼在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負(fù)責(zé)我們關(guān)卡的管理,有這一關(guān)的小球數(shù),和通關(guān)需要爆破的小球數(shù)??傊?,這里按照本關(guān)需要的小球數(shù)初始化小球,可以看到里面有很多的隨機(jī)函數(shù)使用。小球的半徑處于如下兩個(gè)數(shù)之間
var SMALL_RADIUS1 = 3;
var SMALL_RADIUS2 = 10;
通過random_range來進(jìn)行這個(gè)范圍隨機(jī)。我們看到速度也是隨機(jī)的,范圍是
var SPEED_MIN = 10;
var SPEED_MAX = 50;
顏色中的color_rgba_str函數(shù)的第四個(gè)參數(shù)說明我們的每個(gè)小球的透明度是0.8,這樣我們就能在爆破的時(shí)候,或彈動的時(shí)候仍然看清其他小球。這段代碼就說到這。
我們講講主要流程,其實(shí)其中的詳細(xì)注釋,我覺得已經(jīng)可以教會很多初學(xué)者。不過還是講講好。主要流程就在Main.js中。負(fù)責(zé)初始化,渲染和循環(huán)。開始我們設(shè)置了一堆全局變量。
var g_ObjectMgr = null;
var g_MouseEventDispatch = new MouseEventDispatch();
var g_MouseMgr = null;//g_MouseMgr在g_ObjectMgr初始化后才初始化
//當(dāng)前關(guān)
var g_Steps = 1;//todo 顯示出來
//關(guān)卡數(shù)組
var g_StepsArr = [];
g_StepsArr = stepsInit();
//爆炸開始標(biāo)識
var _ExpendStart = false;
if(DEBUG){
_CircleLib.test();
}
var _Main = { 。。。
包括關(guān)卡數(shù)組,當(dāng)前關(guān)數(shù),爆炸標(biāo)識等。居然還設(shè)置了一個(gè)是否調(diào)試的狀態(tài)量,其實(shí)我也不知道js調(diào)試怎樣才好,基本就按自己的方式調(diào)。 _Main是個(gè)很大的結(jié)構(gòu)。我更寧愿把它當(dāng)做單例來思考。主要是,它包括了渲染,這個(gè)渲染不具通用性,僅此一例就夠。其他地方用了且不是畫出什么就難說了。當(dāng)然已經(jīng)設(shè)計(jì)其實(shí)我會更多考慮通用性的設(shè)計(jì),盡量不設(shè)計(jì)成這種單例。
這個(gè)_Main結(jié)構(gòu)中有我們的畫布canvas,我們的初始化函數(shù),每關(guān)調(diào)用一次,它來負(fù)責(zé)2d對象的初始化,游戲?qū)ο蟮某跏蓟J髽?biāo)監(jiān)聽初始化。然后就是進(jìn)入我們的循環(huán)。循環(huán)很簡單,就干四件事情
/*
* 循環(huán)繪圖
* 1.清空畫面
* 2.游戲?qū)ο箨P(guān)系處理
* 3.渲染出來
* 4.循環(huán)調(diào)用
*/
step: function(){
this.clear();
g_ObjectMgr.step();
this.render();
_this = this;
this._st = setTimeout(function(){
_this.step();
}, 50);
}
看看我們的下一關(guān)都干些什么
/*
* 下一關(guān)
*/
nextStep: function(){
clearTimeout(this._st);
if(this.canvas.getContext)
{
g_MouseEventDispatch.start();
this.initObjects();
this.step();
}
}
它就是清除計(jì)時(shí)器,重新分配事件,初始化對象。然后進(jìn)入循環(huán),為什么是這樣呢?清除計(jì)時(shí)器以使我們之前的循環(huán)停止。因?yàn)槲覀凂R上有新的循環(huán)了,其實(shí)事件可以看做有兩個(gè)狀態(tài),我們按下鼠標(biāo)的時(shí)候,這個(gè)事件就不可用了,下次使用必須初始化。小球數(shù)量變了,必須按照本關(guān)的需求來初始化。進(jìn)入循環(huán),新的循環(huán)開始。
看看再玩一次(本級)按鈕的調(diào)用:
/*
* 再玩一次(本級)
*/
again: function(){
this.nextStep()
}
為什么居然是調(diào)用下一關(guān)呢?只能說我設(shè)計(jì)的太懶惰,nextStep()本身根本不管關(guān)卡的變更。關(guān)卡的變更完全在爆炸檢查函數(shù)里,一旦發(fā)現(xiàn)小球爆炸,就會修改當(dāng)前關(guān)卡。而單純調(diào)用nextStep所使用的關(guān)卡是未改變過的,故而是在玩本級。
我們再看一下ObjectMgr.js中的爆炸檢查函數(shù)
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+"個(gè)小球,恭喜進(jìn)入第"+ next + "關(guān),\n\
下一關(guān)需要爆破" +g_StepsArr[g_Steps + 1].killNum + "個(gè)小球");
g_Steps++;
_Main.init();
}
}
}
還記得_ExpendStart這個(gè)變量的意思么?就是說鼠標(biāo)是否按下了,按下的話我們就要檢查是否有小球撞上鼠標(biāo)范圍或撞上爆炸中的小球。其實(shí)這里的調(diào)用_CircleLib.intersect這個(gè)函數(shù)是有些小問題的。它是通過引用修改的當(dāng)前小球的狀態(tài),至于為什么有很少量的小球未修改狀態(tài),這個(gè)我還沒弄明白??傊?,這個(gè)函數(shù)檢查了爆炸小球的數(shù)量,一旦爆炸小球的數(shù)量符合本關(guān)的要求,那么就可以進(jìn)入下一關(guān),可以看到我們進(jìn)入下一關(guān)的提示是一個(gè)對話框,不是很友好,可以設(shè)計(jì)為一個(gè)圖片較好,可惜我沒時(shí)間找美術(shù)。
點(diǎn)擊確定,我們玩下一關(guān)
小球多了很多,找個(gè)好點(diǎn)的位置,能捕捉很多小球。
看一下爆炸過程吧:
這是第十八關(guān)的一個(gè)爆炸情形,更具體的內(nèi)容歡迎看具體代碼吧,要不還講好長時(shí)間。
做完這個(gè)例子,發(fā)現(xiàn)其實(shí)非常多的小游戲很好設(shè)計(jì),可惜沒那么多時(shí)間,再說設(shè)計(jì)別人設(shè)計(jì)過的游戲也不是我的目標(biāo)。設(shè)計(jì)一些有趣的小游戲到手機(jī)里,這個(gè)倒是個(gè)不錯(cuò)的方向
一種更好的態(tài)度,更好的學(xué)習(xí)、思維方式。它會是網(wǎng)絡(luò)極佳的生存方式,你喜歡就對。
posted on 2011-12-18 23:03 yangyusong 閱讀(2141) 評論(2) 編輯 收藏