HTML 5游戲制作之五彩連珠(動畫)
上一節(jié)中,我們留下了一個flyin的方法沒有介紹,這里想單獨(dú)寫一篇html5的動畫實現(xiàn)。
在第二節(jié)中我們實現(xiàn)了畫一個泡泡,并且成功的擦除了泡泡,但當(dāng)時也說了別把棋盤的線給擦掉了,所以做了偏移量。所以說html5 Canvas還是低級, 沒有圖層的概念,擦掉再想補(bǔ)回來,怎么補(bǔ)? 答案就是重繪。 沒錯,整個Canvas重繪,這樣就能不用擔(dān)心補(bǔ)哪里了。雖然帶來了性能的損失,但絕對減少的編碼難度。而且計算機(jī)的能力也不差這點(diǎn)損失。那么重繪的話,我們在Canvas是上所有的需要繪制的對象都應(yīng)該有draw方法。這是必須的。另外,所有的元素都有個上下的概念,所以要先繪制下面的,再繪制上面的。 而這個上下就得靠 子元素的概念,這樣在父元素繪制完畢后遍歷其子元素繪制,就不用擔(dān)心掩蓋的問題。
如果想把ready區(qū)的3個泡“飛入”棋盤,就需要讓Canvas在泡移動的時候進(jìn)行重繪,泡泡不動時不需要重繪。泡泡的移動很容易實現(xiàn),只要改變他的x,y坐標(biāo)即可。如果想達(dá)到動畫的效果,就得在改變坐標(biāo)期間,定時重繪,可以使用setInterval來實現(xiàn)。
另外,我們不光飛入的動作需要重繪,游戲開始后玩家還要點(diǎn)擊移動一個泡泡到另外一個格子,所以這里也要重繪。那繪制的信息這么多,整個重繪工作都要交給game來進(jìn)行,game控制所有的父元素。
- start: function () {
- this.map.init();
- this.ready.init();
- this.draw();
- this.canvas.onclick = this.onclick;
- },
- over: function () {
- alert("GAME OVER");
- },
- draw: function () {
- this.ctx.clearRect(0, 0, 400, 600);
- this.ctx.save();
- this.map.draw();
- this.ready.draw();
- this.ctx.restore();
- },
- play: function (action, interval) {
- var me = this;
- play = setInterval(function () {
- action();
- me.draw();
- }, interval || 1);
- },
- stop: function () {
- clearInterval(play);
- this.draw();
- //console.log(this.ready.bubbles.length);
- },
game.start就是初始化所有的父元素,
game.over自然不必說,只是這里沒有寫具體代碼,結(jié)束時應(yīng)該無法繼續(xù)操作泡泡。
game.draw 繪制所有的父元素。
game.play 就是重繪方法,需要重繪時掉用此方法。接收2個參數(shù),***個是重繪時需要做的動作,interval是繪制的間隔時間。不同的動作可能間隔不一樣。
可能這種實現(xiàn)是野路子,真正的重繪應(yīng)該是游戲開始后就不聽的調(diào)用重繪方法,而不是具體哪里調(diào)用,只是在具體的精靈(每個元素)Update自己狀態(tài)就像我這里的action。 這里我們暫且這樣實現(xiàn),后面如果達(dá)不到需求再重構(gòu)這個重繪的代碼,畢竟核心的代碼不變,只是改改機(jī)制 不是大問題。
game.stop 停止重繪,又調(diào)用了一次draw,是為了保證***的繪制沒問題。
接下來考慮下flyin飛入的實現(xiàn)。知道起始和結(jié)束的xy坐標(biāo),飛入的路徑不是問題,無非是x y的加加減減,那么動畫方面,我們的game.play的action就是來加減ready.bubbles的xy坐標(biāo)。飛入的邏輯并非這么簡單,首先需要3個沒染色空格,如果不足3個,那就GameOver了,所以map需要提供一個返回3個空格子的方法,另外,飛入之后ready區(qū)要重新生成新的泡泡,而這幾個被飛的泡泡需要刪除,并且map要對3個空格子進(jìn)行染色。 這就完成了整個飛入效果。
其實還有一個邏輯就是 飛入完畢后檢查是否有哪些泡泡可以被消除,這個我們后面再講。
先看獲取3個空格的方法:
- getEmptyBubbles: function () {
- var empties = [];
- this.bubbles.forEach(function (row) {
- row.forEach(function (bubble) {
- if (!bubble.color) {
- empties.push(new Bubble(bubble.x, bubble.y));
- }
- });
- });
- if (empties.length <= 3) {
- game.over();
- return [];
- }
- var result = [];
- var useds = [];
- for (var i = 0; i < empties.length; i++) {
- if (result.length == 3) {
- break;
- }
- var isUsed = false;
- var ra = game.getRandom(empties.length);
- for (var m = 0; m < useds.length; m++) {
- isUsed = ra === useds[m];
- if (isUsed) break;
- }
- if (!isUsed) {
- result.push(empties[ra]);
- useds.push(ra);
- }
- }
- console.log(useds);
- return result;
- },
獲取3個隨機(jī)的空格還是挺多代碼的。。。然后就是flyin的實現(xiàn)了。
首先定一個一個status,來存飛入的狀態(tài)。3個都飛完畢才能做后面的邏輯。Bubble對象也為此增加了px和py倆個成員(即Bubble的實際坐標(biāo)),這樣才能控制每個像素的移動。 其實在計算飛入路徑時我寫了很久的代碼,別看現(xiàn)在就這么幾行,開發(fā)過程中還是頗費(fèi)力。各種詭異的飛行。。。開始是按x++ y++遞增飛行的,這樣就是45°角飛行,但顯然飛行線路(起始到結(jié)束的線)的傾斜度不是45°,那就會出現(xiàn)先飛完x或y,再走直線,很傻的。所以要用斜率來計算當(dāng)前的y坐標(biāo)。而x的坐標(biāo)可以固定常熟移動。我畫了一個斜率的公式,忘記的同學(xué)可以看看下。根據(jù)長寬的比率,就能計算當(dāng)前的y值。
- flyin: function () {
- var emptys = game.map.getEmptyBubbles();
- if (emptys.length < 3) {
- //GAME OVER
- game.over();
- return;
- }
- var me = this;
- var status = [0, 0, 0];
- game.play(function () {
- if (status[0] && status[1] && status[2]) {
- game.stop();
- status = [0, 0, 0];
- me.bubbles = [];
- me.genrate();
- return;
- }
- for (var i = 0; i < me.bubbles.length; i++) {
- if (status[i]) {
- continue;
- }
- var target = emptys[i];
- var x2 = target.px + game.map.startX - me.startX;
- var y2 = target.py + game.map.startY - me.startY;
- var current = me.bubbles[i];
- var tmpWidth = 2;
- if (current.px < x2) {
- current.py = ((y2 - current.py) / (x2 - current.px)) * tmpWidth + current.py;
- current.px += tmpWidth;
- }
- else if (current.px > x2) {
- current.py = ((y2 - current.py) / (current.px - x2)) * tmpWidth + current.py;
- current.px -= tmpWidth;
- }
- else {
- current.py += tmpWidth;
- }
- if (current.py > y2) {
- current.py = y2;
- }
- if (current.px > x2) {
- current.px = x2;
- }
- if (current.px == x2 && current.py == y2) {
- status[i] = 1;
- current.x = target.x;
- current.y = target.y;
- game.map.addBubble(current);
- console.log(1);
- }
- }
- });
- }
既然我們已經(jīng)實現(xiàn)了動畫效果,那么接下來順便再實現(xiàn)一個動畫效果,就是點(diǎn)擊泡泡時,泡泡要做出響應(yīng)(就是忽閃忽閃的),要不然用戶都不知道點(diǎn)了沒有。所以Bubble也要增加一個閃動的action。
代碼的意思是讓間隔50毫秒,重繪一次光照的亮度,亮度值(外圓的半徑)10和30之間來回蕩。這樣就亮了暗,暗了再亮。很有意思:)
- Bubble.prototype.play = function () {
- var me = this;
- var isUp = true;
- game.play("light_" + this.x + "_" + this.y, function () {
- if (isUp) {
- me.light++;
- }
- if (!isUp) {
- me.light--;
- }
- if (me.light == 30) {
- isUp = false;
- }
- if (me.light == 10) {
- isUp = true;
- }
- }, 50);
- };
- Bubble.prototype.stop = function () {
- //this.light = 10;
- var me = this;
- game.stop("light_" + this.x + "_" + this.y);
- game.play("restore_" + this.x + "_" + this.y, function () {
- if (me.light > 10) {
- me.light--;
- }
- else {
- me.light = 10;
- game.stop("restore_" + me.x + "_" + me.y);
- }
- }, 50);
- };
細(xì)心的朋友可能會發(fā)現(xiàn),在調(diào)用Game.stop的方法的參數(shù)上增加了一個參數(shù)。 這里我要說明下。如果沒有參數(shù)的情況,game.play和stop會造成問題,因為用的都是一個interval,clear的話會打斷其他的動畫,所以我們把每個action都要傳遞一個name,這樣就能讓game粒度更細(xì)的控制每個action了。game的播放代碼也做了響應(yīng)的調(diào)整,如下:
- play: function (name, action, interval) {
- var me = this;
- this.actions[name] = setInterval(function () {
- action();
- me.draw();
- }, interval || 1);
- },
- stop: function (name) {
- clearInterval(this.actions[name]);
- this.draw();
- },
效果演示地址:http://jsfiddle.net/maddemon/VtMSU/embedded/result/
原文鏈接:http://www.cnblogs.com/mad/archive/2012/03/18/2403404.html
【編輯推薦】