欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

HTML5 Canvas编写五彩连珠(4):动画

程序员文章站 2022-06-16 14:28:29
上一节中,我们留下了一个flyin的方法没有介绍,这里想单独写一篇html5的动画实现。 在第二节中我们实现了画一个泡泡,并且成功的擦除了泡泡,但当时也说了别把棋盘的线给擦掉了,所以做了偏移量。所以...

上一节中,我们留下了一个flyin的方法没有介绍,这里想单独写一篇html5的动画实现。
在第二节中我们实现了画一个泡泡,并且成功的擦除了泡泡,但当时也说了别把棋盘的线给擦掉了,所以做了偏移量。所以说html5 canvas还是低级, 没有图层的概念,擦掉再想补回来,怎么补?  答案就是重绘。  没错,整个canvas重绘,这样就能不用担心补哪里了。虽然带来了性能的损失,但绝对减少的编码难度。而且计算机的能力也不差这点损失。那么重绘的话,我们在canvas是上所有的需要绘制的对象都应该有draw方法。这是必须的。另外,所有的元素都有个上下的概念,所以要先绘制下面的,再绘制上面的。 而这个上下就得靠 子元素的概念,这样在父元素绘制完毕后遍历其子元素绘制,就不用担心掩盖的问题。

  如果想把ready区的3个泡“飞入”棋盘,就需要让canvas在泡移动的时候进行重绘,泡泡不动时不需要重绘。泡泡的移动很容易实现,只要改变他的x,y坐标即可。如果想达到动画的效果,就得在改变坐标期间,定时重绘,可以使用setinterval来实现。
  另外,我们不光飞入的动作需要重绘,游戏开始后玩家还要点击移动一个泡泡到另外一个格子,所以这里也要重绘。那绘制的信息这么多,整个重绘工作都要交给game来进行,game控制所有的父元素。

[javascript]
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); }, 

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自然不必说,只是这里没有写具体代码,结束时应该无法继续操作泡泡。
game.draw 绘制所有的父元素
game.play 就是重绘方法,需要重绘时掉用此方法。接收2个参数,第一个是重绘时需要做的动作,interval是绘制的间隔时间。不同的动作可能间隔不一样。
可能这种实现是野路子,真正的重绘应该是游戏开始后就不听的调用重绘方法,而不是具体哪里调用,只是在具体的精灵(每个元素)update自己状态就像我这里的action。  这里我们暂且这样实现,后面如果达不到需求再重构这个重绘的代码,毕竟核心的代码不变,只是改改机制 不是大问题。
game.stop 停止重绘,又调用了一次draw,是为了保证最后的绘制没问题。

  接下来考虑下flyin飞入的实现。知道起始和结束的xy坐标,飞入的路径不是问题,无非是x y的加加减减,那么动画方面,我们的game.play的action就是来加减ready.bubbles的xy坐标。飞入的逻辑并非这么简单,首先需要3个没染色空格,如果不足3个,那就gameover了,所以map需要提供一个返回3个空格子的方法,另外,飞入之后ready区要重新生成新的泡泡,而这几个被飞的泡泡需要删除,并且map要对3个空格子进行染色。 这就完成了整个飞入效果。
其实还有一个逻辑就是 飞入完毕后检查是否有哪些泡泡可以被消除,这个我们后面再讲。
  先看获取3个空格的方法:

[javascript]
getemptybubbles: function () { var emptybubbles = []; this.bubbles.foreach(function (row) { row.foreach(function (bubble) { if (!bubble.color) { emptybubbles.push(new bubble(bubble.x, bubble.y)); } }); }); if (emptybubbles.length <= 3) { game.over(); return []; } var result = []; var usedrandom = []; for (var i = 0; i < emptybubbles.length; i++) { if (result.length == 3) { break; } var ra = game.getrandom(emptybubbles.length); var isused = false; usedrandom.foreach(function (r1) { if (r1 === ra) { isused = true; } else { usedrandom.push(ra); } }); if (!isused) { result.push(emptybubbles[ra]); } } return result; }, 

getemptybubbles: function () { var emptybubbles = []; this.bubbles.foreach(function (row) { row.foreach(function (bubble) { if (!bubble.color) { emptybubbles.push(new bubble(bubble.x, bubble.y)); } }); }); if (emptybubbles.length <= 3) { game.over(); return []; } var result = []; var usedrandom = []; for (var i = 0; i < emptybubbles.length; i++) { if (result.length == 3) { break; } var ra = game.getrandom(emptybubbles.length); var isused = false; usedrandom.foreach(function (r1) { if (r1 === ra) { isused = true; } else { usedrandom.push(ra); } }); if (!isused) { result.push(emptybubbles[ra]); } } return result; },
获取3个随机的空格还是挺多代码的。。。然后就是flyin的实现了。

首先定一个一个status,来存飞入的状态。3个都飞完毕才能做后面的逻辑。bubble对象也为此增加了px和py俩个成员(即bubble的实际坐标),这样才能控制每个像素的移动。 其实在计算飞入路径时我写了很久的代码,别看现在就这么几行,开发过程中还是颇费力。各种诡异的飞行。。。开始是按x++ y++递增飞行的,这样就是45°角飞行,但显然飞行线路(起始到结束的线)的倾斜度不是45°,那就会出现先飞完x或y,再走直线,很傻的。所以要用斜率来计算当前的y坐标。而x的坐标可以固定常熟移动。我画了一个斜率的公式,忘记的同学可以看看下。根据长宽的比率,就能计算当前的y值。

  HTML5 Canvas编写五彩连珠(4):动画


 

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); } } }); } 

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); } } }); }
  既然我们已经实现了动画效果,那么接下来顺便再实现一个动画效果,就是点击泡泡时,泡泡要做出响应(就是忽闪忽闪的),要不然用户都不知道点了没有。所以bubble也要增加一个闪动的action。

代码的意思是让间隔50毫秒,重绘一次光照的亮度,亮度值(外圆的半径)10和30之间来回荡。这样就亮了暗,暗了再亮。很有意思:)

[javascript]
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); }; 

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); };
细心的朋友可能会发现,在调用game.stop的方法的参数上增加了一个参数。 这里我要说明下。如果没有参数的情况,game.play和stop会造成问题,因为用的都是一个interval,clear的话会打断其他的动画,所以我们把每个action都要传递一个name,这样就能让game粒度更细的控制每个action了。game的播放代码也做了响应的调整,如下:

[javascript]
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(); }, 

 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/

 

摘自  君之蘭