Canvas做游戏实践分享(一)
近期看了几本老外的书,学习了一些大神们的博客,接触了一下火的不得了的canvas做游戏,把自己的学习过程分享出来。
1.游戏的基本内容1.1什么是动画????? 动画是通过连续播放一系列画面,给视觉造成连续变化的图画。它的基本原理与电影、电视一样,都是视觉原理。医学已证明,人类具有视觉暂留的特性,就是说人的眼睛看到一幅画或一个物体后,在1/24秒内不会消失。利用这一原理,在一幅画还没有消失前播放出下一幅画,就会给人造成一种流畅的视觉变化效果。因此,电影采用了每秒24幅画面的速度拍摄播放,电视采用了每秒25幅(PAL制,中央电视台的动画就是PAL制)或30幅(NSTC制)画面的速度拍摄播放。如果以每秒低于24幅画面的速度拍摄播放,就会出现停顿现象。
??????? 从播放效果上看,分为顺序动画(连续动作)和交互式动画(反复动作)。逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。
1.2使用HTML5来做动画??????? HTML5制作的动画主要是逐帧动画。目前主要有DOM, SVG与Canvas三种方式来实现。在此我们主要讨论Canvas方式来制作动画。
Canvas的支持
?????? 到目前为止,基本所有浏览器的最新版本均提供了对canvas的全面支持。基于“渐近增强,优雅降级”的移动互联网应用体验,如果用户使用的浏览器版本较低,可以在canvas标签中写入信息来提示用户升级浏览器。如:
<canvas>您的浏览器不支持此动画,请升级您的浏览器</canvas>
?如果要使用编程的方式来检测对于canvas的支持程度,可以使用如下代码:
if(document.createElement(“canvas”).getContext(“2d”)){ console.log(“当前浏览器支持canvas”);}
当然,也可以引入第三方的开源JavaScript库(http://code.google.com/p/explorercanvas/),在不支持canvas的浏览器中来模拟canvas的各种API。由于各个设备及浏览器版本的实现方式不同,为了保证用户得到一致的体验,我们需要在尽可能多的设备及浏览器上对我们的游戏进行测试及调优。
HTML5基本文档模板
???????? 我们的游戏需要在一个HTML文档中的canvas标签中渲染,在此我们创建一个最基本的HTML文档模板。如下
<!doctype html><html><head><meta charset="utf-8"><title>base canvas</title><link rel="stylesheet" type=”text/css” href="style.css"></head><body><canvas id="canvas" width="400" height="400"></canvas><script type=”text/javascript”>window.onload = function () {};</script></body></html>
简单浏览一下,刚开始我们定义了HTML5文档类型,之后定义了header标签,引用了外部的CSS文件。然后在body中定义了一个canvas标签来做为我们的游戏渲染容器。在body标签结束前,我们创建了一个script标签,在其中来使用JavaScript来实现我们的游戏(之所以在header中使用link标签来加载外部css文件,或在body结束标签前再定义script标签、引入外部JavaScript文件,是为了保证页面的逐步呈现速度,保证浏览器更高效地渲染HTML页面,提高脚本下载的并行度,参见Steve Souders大神的《高性能网站建设指南》).
????? 可以看到,在脚本中我们为window对象的onload事件指定了回调函数,即在所有的文档元素加载完成后,再执行回调函数中的程序。这可以保证canvas标签在我们使用之前已经创建成功。当然,如果当前的HTML文档中有大量的资源(如图片,音乐等)需要加载,那我们的window.onload事件会需要等待很长的时间才能执行,在这种情况下最好是使用脚本来动态加载相应的资源,之后会介绍到相关的知识。
其它
?
随着开发游戏的复杂度增加,我们的代码量及程序复杂度会随之增长,这就需要我们对游戏进行建模,将代码按一定的规则放置在外部脚本文件中引入。
同时,游戏开发中一个好的编辑器与调试工具也会得到事半功倍的效果。在此我们使用sublime text2来做为编辑器,使用chrome中的开发人员工具来做为调试工具。
?
1.3 逐帧动画?
因为canvas动画是逐帧动画,所以我们需要有一个循环来控制动画中的每一帧的渲染。而对于每一帧动画的渲染,会按如下序列进行:
a. 执行计算当前帧动作的所有代码,将其结果调用canvas API放入内存
b. 将内存中当前帧渲染到canvas容器中
c. 进入下一帧的处理流程(通常会在此时擦除画布)
在JavaScript中,有两个函数setTimeout与setInterval通常用来进行相关的计时调用。setTimeout 只会执行回调函数一次,setInterval会每隔X 毫秒执行函数一次。但这两个函数均不是为开发游戏而生,都存在着一些问题。
基于 JavaScript 引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。因此没法确保函数会在setTimeout 指定的时刻被调用。而当回调函数的执行被阻塞时,setInterval 仍然会发布更多的毁掉指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。所以通常我们不建议使用setInterval,而是采用setTimeout来替代,最简单的处理回调函数被阻塞的情况就是在回调函数中再调用setTimeout.(具体参见JavaScript Gardenhttp://bonsaiden.github.com/JavaScript-Garden/zh/).
但是,随着对动画的流畅度要求提高,如果有大量定时器在同时运行,这些定时器之间的调度会对性能有轻微的影响。一种解决方案就是实现一个采用帧管理的动画框架,通过一个定时器触发动画帧,不同的动画来注册这些帧,在每一帧上处理多个动画的属性变化。这样的好处是减少了定时器调度的开销,但是对于动画框架的开发者来说,统一帧管理、提供监听帧的API等,都是需要开发和维护的。浏览器厂商目前提供一个专门做动画的方法,即requestAnimationFrame(),从浏览器的层面对帧动画进行了优化,如下代码示例(参见http://paulirish.com/2011/requestanimationframe-for-smart-animating/)。
window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })();(function animloop(){ requestAnimFrame(animloop); render();})();?
?
?
?1.4JavaScript对象知识??????? JavaScript是一个基于对象的语言,它的对象就是一种包含属性的特殊的数据结构,这此属性可以是变量,函数或其它对象。同时JavaScript对象可以随时被动态改变。如:
?
var objectA={};objectA.name=”objectA”;
?如果我们需要同时创建同一类型的对象,可以使用构造函数的形式。JavaScript的构造函数是一种特殊的函数,它可以基于设置给自身的属性来生成对象。在创建了构造函数后,我们就可以使用new关键字来生成对象了。如:
function Animal(name){ this.name=name || “animal”; this.say=function(){ console.log(this.name);}var dog=new Animal(“dog”); dog.say();var cat=new Animal(“cat”); cat.say();
JavaScript是基于原型的一种语言。在我们创建了一个新的对象实例时,实际是我们是创建了一个对象,这个对象继承了构造函数的原型链(具体参见《JavaScript面向对象编程》及http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html 或http://7685941014.blog.163.com/blog/static/124615480201061782347164/ )
function Person(name){ this.name=name; console.log(this.name); }?function Student(name,school){ Person.call(this,name); this.school=school; }Student.prototype=new Person();Student.prototype.constructer=Student;
由于JavaScript的函数也是一种对象,所以,可以给函数设置属性及方法。比如很多时候我们使用的回调方法等。如:
var person=[“Jim”,”James”,”Kimmy”]; person.forEach(function(o,i){ console.log(i+”,”+o); }
直接构造for循环或者使用forEach方法来遍历Array的所有元素,在Array对象的元素数目很大的情况下,使用forEach方法会有效率上的问题。与此类似,在使用for…in 循环来遍历对象的所有属性时,如果对象的原型链嵌套层次比较深,也会导致效率上有问题。(for … in不能遍历出enumerable为false的属性,如Array的length属性)。
?
1.5用户交互???? 游戏的核心在于用户交互性,用户交互是基于事件的。事件包括监听器及事件处理。在canvas上绘制的图形自身不支持DOM事件检测,只有canvas标签自身支持DOM整件监听。因此需要对canvas容器的事件进行处理,实现相对事件监听及处理。
????? 按W3C的规范http://www.w3.org/TR/DOM-Level-3-Events ,我们可以为对象注册事件,如下:
element.addEventListener(type, handler [, useCapture]);
?一个典型监听鼠标点击的事件如下:
canvas.addEventListener('mousedown', function (event) { console.log("Mouse pressed on element!");}, false);
换句话说,每一个对象都订阅了指定的事件触发事件,在该事件触发时,会将此信息广播给所有订阅其信息的对象,此对象再调用对应的事件触发回调函数来进行处理。如果我们想注销某一个事件,则使用
element.removeEventListener(type, handler [, useCapture]);?
鼠标事件
?
????? 鼠标事件包括mousedown,mouseup,mouseover等。如果需要获取鼠标位置时,在监听到mousemove事件响应后,可做如下处理:
var utils={};utils.cpatureMousePosition=function(element){ var mouse={x:0,y:0}; element.addEventListener("mousemove",function(event){ var x,y; if(event.pageX||event.pageY){ x=event.pageX; y=event.pageY; }else{ x=event.clientX+document.body.scrollLeft+document.documentElement.scrollLeft; y=event.clientY+document.body.scrollTop+document.documentElement.scrollTop; } x-=element.offsetLeft; y-=element.offsetTop; mouse.x=x; mouse.y=y; },false); return mouse;};
具体的原因在于各个浏览器对于鼠标位置的处理实现方式与提供的接口不同:参见http://www.seobye.com/div-css/87/及http://javascript.about.com/library/blmousepos.htm,可以在页面中使用上面定义的方法:
<!doctype html><html><head><meta charset="utf-8"><title>Get Mouse Position</title><link rel="stylesheet" type=”text/css” href="style.css"></head><body><canvas id="canvas" width="400" height="400"></canvas><script type=”text/javascript”>window.onload = function () { var canvas=document.getElementById(“canvas”); var mouse=utils.captureMousePosition(canvas); (function drawFrame(){ window.requestAnimFrame(drawFrame,canvas); console.log(mouse.x+”,”+mouse.y); })();};</script></body></html>
键盘事件
?????? 有两个键盘事件——keydown与keyup。可以通过event.keyCode (https://github.com/lamberta/html5-animation/blob/master/xtras/keycode.js )来获取当前键盘值的ASCII值。然后使用switch来区分处理具体的按键并加以处理。如下:
element.addEventListener(“keyup”,function(event){ switch(event.keyCode){ case: break; }},false);
?
?
1 楼 feiyang404 2012-03-09 对你的文章非常感兴趣,想问问你看的什么书,能否介绍一下给我?详细点,谢谢了,或者blog的地址 2 楼 blessdyb 2012-03-15 feiyang404 写道对你的文章非常感兴趣,想问问你看的什么书,能否介绍一下给我?详细点,谢谢了,或者blog的地址