站长资讯网
最全最丰富的资讯网站

HTML5边玩边学(七)-动画初步之飞舞的精灵

一、为什么选择 HTML5

HTML5 边玩边学算上这篇已经是第七篇了,在这篇开始之前,我想先说明一下为什么叫“HTML5” 边玩边学,因为有人对 HTML5 提出质疑,毕竟他是一个新生事物。我承认我用 HTML5 来吸引眼球了,如果看过边玩边学系列的每一篇,你会发现前六篇文章内容的和 HTML5 关系不是太大,真正的内容其实是 2D 图形图像编程的学习笔记。

如果我们想学习 2D 编程,其实可供选择的编程环境数不胜数:MFC、Delphi都有图形图像处理功能(即GDI),Java、.Net 更不用说了,如果你支持开源,GTK、QT、wxPython 也是不错的选择,Flash 更是拿手好戏,甚至几个流行的只能手机平台应该也有 2D 模块。

如果你选择了上面任何一款编程环境学习 2D 编程,你会发现他们的内容基本上是一样的:线型、填充、颜色、渐变、图像、组合、裁剪区、变形等等,甚至连函数名很多都是一摸一样,毕竟他们的理论基础都是图形学。

搞清楚我们真正想学习什么以后,其实编程环境只是个工具而已,我们根据个人喜好,选择最方便的一款来使用。其实我更青睐 Python 编程环境,只是如果我用了Python,估计跟我交流的人就不会太多了,大家机器里安装 Python 运行时的估计不会太多。

那么为什么选择 HTML5 而不是其他呢?首先,Javascript 语法简洁灵活,相应的函数库小巧但是够用, HTML5 Canvas 标签的 2D 表现能力也达到了要求,Chrome 浏览器的运行速度让人满意。除此之外,我们不用安装笨重的集成开发环境,不需要安装运行时,我们只需要一个加强功能的记事本、一个浏览器就可以去实践我们的想法,并且直接将效果呈现在网络上。我们只是发表文章同其他人分享自己的想法而已,至于平台、框架、语言特性,这些无关的东西当然牵扯的越少越好,这就是我选择 HTML5 的原因。

所以,请大家不要误解了标题的含义:这个系列并不是 HTML5 的学习笔记,而只是用 HTML5 来展现一些知识内容而已,你更多关注的应该是知识和内容本身,你可以在任何其他一款编程环境下再现他们。

二、动画初步

动画就是一系列连续的画面按顺序呈现出来而已,只是,在电影电视中,这些画面实现已经被准备好了,而在电脑程序中,我们见到每一瞬间的画面都是即时绘制的,大体流程可以表述如下:

a、轻微改变图形的数据(坐标、形状、颜色等等)

b、清空画布

c、绘制图形

d、回到步骤 a

当然,这里只是给出了一个最简单的流程框架,要实现复杂的动画可能还要考虑更多的问题,比如局部清除、碰撞检测之类的。

另外,绘制过程中有两个速度需要控制:

第一个是绘制速度,即每秒钟绘制多少次(帧),或者也可以这样说,每一帧暂停多少时间。如果你的动画每一帧都是一个样子,只是位置不同,这个速度影响不大。

第二个图形移动的速度。

所以,千万不要把这两个速度搞混了,绘制的越快,只能代表动画更流畅,但并不代表你的图像移动的更快。

使用 HTML5 绘制动画基本上就是上面这个流程,只是你还需要注意两点:

1、为了方便绘制的图形,我们经常会改变上下文对象的的状态,所以在绘制图形前后,千万别忘了保存和恢复状态,如果你不太了解状态是什么,请看前面的一篇文章《HTML5边玩边学(6):汽车人,变形……》

2、我们需要将整个绘制动作放到定时器里面,否则整个浏览器将失去响应。Javascript 有两个定时器方法,分别是:

setInterval(code,millisec) 和 setTimeout(code,millisec)

这两个方法我就介绍了,可以去 Google 相关的资料。

下面我们给出一个上下移动方块的小动画,当遇到顶部或者底部时,会改变方向。代码如下:

基本动画        Code highlighting produced by Actipro CodeHighlighter (freeware)  http://www.CodeHighlighter.com/    --><canvas id="canvas1" width="250" height="300" style="background-color:black">        你的浏览器不支持 Canvas 标签,请使用 Chrome 浏览器 或者 FireFox 浏览器    </canvas><br/>    帧数:<input  id="txt1" type="text" value="25"/><br/>    每次移动距离:<input type="text" id="txt2" value="10"/><br/>    <input type="button" value="开始" onclick="move_box()"/>    <input type="button" value="暂停" onclick="stop()"/>            <script type="text/javascript">        //定时器        var interval=null;                //停止动画        function stop(){            clearInterval(interval);        }            //===================================================================        //基本动画        //====================================================================        function move_box(){            //停止动画            stop();            //移动速度            var delta=parseInt(document.getElementById('txt1').value);            //每秒绘制多少次            var fps=parseInt(document.getElementById('txt2').value);                //画布对象            var canvas=document.getElementById("canvas1")            //获取上下文对象            var ctx = canvas.getContext("2d");            //设置颜色            ctx.fillStyle="red";                        //方块的初始位置            var x=100;var y=50;            //方块的长度和宽度            var w=30;var h=30;                        //开始动画            interval = setInterval(function(){                //改变 y 坐标                y=y+delta;                //上边缘检测                if(y<0){                    y=0;                    delta=-delta;                }                //下边缘检测                if((y+h)>canvas.getAttribute("height")){                    y=canvas.getAttribute("height")-h;                    delta=-delta;                }                 //清空画布                ctx.clearRect(0,0,canvas.getAttribute("width"),canvas.getAttribute("height"));                //保存状态                ctx.save();                //移动坐标                ctx.translate(x,y);                //重新绘制                ctx.fillRect(0,0,w,h);                //恢复状态                ctx.restore();            },1000/fps);        }        </script>

三、重新组织代码

上面的代码能正常工作了,但是存在很多问题,主要有以下几点:

1、计算方块位置的代码和绘制方块的代码混杂一起,即逻辑和视图混杂,基本上不能扩展了

2、代码没办法复用,比如我们需要绘制多个不同的方块对象:起始位置、大小、颜色、速度各不相同,每一种情况都需要重写一遍。

下面我们重新组织一下代码,把方块的共同属性抽象出来,组成一个 Box 类,由这个 Box 类负责计算每一帧方块的位置,这样就可以解决上面两个问题了。代码如下:

重新组织代码        Code highlighting produced by Actipro CodeHighlighter (freeware)  http://www.CodeHighlighter.com/    --><canvas id="canvas2" width="250" height="300" style="background-color:black">        你的浏览器不支持 Canvas 标签,请使用 Chrome 浏览器 或者 FireFox 浏览器    </canvas><br/>    <input type="button" value="开始" onclick="move_box2()"/>    <input type="button" value="暂停" onclick="stop()"/>        <script type="text/javascript">        //定时器        var interval=null;                //停止动画        function stop(){            clearInterval(interval);        }            //===================================================================        //重新组织代码        //====================================================================        //方块的构造函数        function Box(color,x,y,w,h,delta){            this.color=color;            this.x=x;            this.y=y;            this.w=w;            this.h=h;            this.delta=delta;            //三十帧            this.fps=30;            //每一帧的延迟时间            this.delay=1000/this.fps;            //上一次重绘的时间            this.last_update=0;        }                //方块更新        Box.prototype.update=function(canvas){            //获取当前时间            var now=(new Date()).getTime();            //如果达到了延迟时间,则更新数据            if((now-this.last_update)>this.delay){                            //改变 y 坐标                this.y=this.y+this.delta;                //上边缘检测                if(this.y<0){                    this.y=0;                    this.delta=-this.delta;                }                //下边缘检测                if((this.y+this.h)>canvas.getAttribute("height")){                    this.y=canvas.getAttribute("height")-this.h;                    this.delta=-this.delta;                }                 //记下最新一次绘制时间                this.last_update=now;            }                    }                        function move_box2(){            //停止动画            stop();            //画布对象            var canvas=document.getElementById("canvas2")            //获取上下文对象            var ctx = canvas.getContext("2d");            //清空画布            ctx.clearRect(0,0,canvas.getAttribute("width"),canvas.getAttribute("height"));                        //创建多个方块对象            var boxes=[];            boxes[0]= new Box("red",3,2,10,35,2,10);//速度10            boxes[1]= new Box("blue",60,28,44,15,5);//速度20            boxes[2]= new Box("green",130,200,23,18,10);//速度30            boxes[3]= new Box("pink",200,150,35,10,20);//速度40                        //开始动画绘制            interval = setInterval(function(){                for(var i=0;i<boxes.length;i++){                    //取出一个方块                    var box=boxes[i];                    //清空这个方块                    ctx.clearRect(box.x,box.y,box.w,box.h);                    //更新数据                    box.update(canvas);                    //保存状态                    ctx.save();                    //设置颜色                    ctx.fillStyle=box.color;                    //移动坐标                    ctx.translate(box.x,box.y);                    //重新绘制                    ctx.fillRect(0,0,box.w,box.h);                    //恢复状态                    ctx.restore();                }            },1);//尽可能快的循环        }        </script>

四、精灵登场

据说在很久远的年代,有多远我也不知道,可能是任天堂红白机是哪个年代吧,由于游戏机处理器的计算速度有限,所以专门设置了一个硬件用来处理角色图像的相关数据,这些数据可能包括:

1、计算当前的角色应该绘制哪一帧。上面我们的方块虽然在移动,但是始终都是一个样子;可是在游戏中,一个跑动的精灵,跑动动作是由很多幅连续的图像组成,我们需要知道现在应该绘制其中的哪一幅图像;

2、表现精灵动作的很多幅连续的图像通常是集中放置在一个大图中,我们需要计算当前绘制的那一幅,在大图中处于什么位置,并把它截取出来

上面说到这个硬件,曾经被叫做 Sprite 精灵。现如今,我们的处理器已经十分强大,不再需要 Sprite 这样的辅助硬件,但是这样的功能仍然需要,只不过用软件来实现罢了,所以,我们依然用 Sprite 来称呼游戏中的一个角色。

这里有一幅图像,他描绘了一个小精灵的飞行动作

HTML5边玩边学(七)-动画初步之飞舞的精灵

下面我们将实现一个 Sprite 类,让他在浏览器里面飞起来。

精灵登场        Code highlighting produced by Actipro CodeHighlighter (freeware)  http://www.CodeHighlighter.com/    --><canvas id="canvas3" width="250" height="300" style="background-color:black">        你的浏览器不支持 &lt;canvas&gt;标签,请使用 Chrome 浏览器 或者 FireFox 浏览器    </canvas><br/>    帧数:<input  id="txt4" type="text" value="10"/><br/>    速度:<input type="text" id="txt5" value="5"/><br/>    比例:<input type="text" id="txt6" value="2"/><br/>    <input type="button" value="开始" onclick="animate()"/>    <input type="button" value="暂停" onclick="stop()"/>        <script type="text/javascript">        //定时器        var interval=null;                //停止动画        function stop(){            clearInterval(interval);        }                //===================================================================        //精灵登场        //====================================================================        //每一帧在大图中的位置        var frames=[];        frames[0]=[0,4,19,19];        frames[1]=[22,1,24,19];        frames[2]=[49,0,18,17];        frames[3]=[1,32,18,17];        frames[4]=[22,33,24,19];        frames[5]=[49,36,19,19];                //精灵类        function Sprite(dx,dy,delta,fps){            this.dx=dx;            this.dy=dy;            this.fps=fps;            this.delay=1000/fps;            this.last_update=0;            //移动速度            this.delta=-delta;            //帧编号            this.index=0;            //方向            this.dir_left=true;        }                Sprite.prototype.update=function(canvas){            //获取当前时间            var now=(new Date()).getTime();            if((now-this.last_update)>this.delay){                if(this.dir_left){                    //方向朝左,只绘制0 1 2帧                    if(this.index>2)                        this.index=0;                }                else{                    //方向朝右,只绘制 3 4 5 帧                    if(this.index>5)                        this.index=3;                }                //取出当前帧的坐标                this.frame=frames[this.index];                                //当前帧在大图中的位置                this.sx=this.frame[0];                this.sy=this.frame[1];                this.sw=this.frame[2];                this.sh=this.frame[3];                                //当前帧大小                this.dw=this.frame[2];                this.dh=this.frame[3];                                //改变 x 坐标                this.dx=this.dx+this.delta;                //左边缘检测                if(this.dx<0){                    this.dx=0;                    //转向                    this.delta=-this.delta;                    this.dir_left=false;                    this.index=3;                }                //右边缘检测                if((this.dx+this.dw)>canvas.getAttribute("width")){                    this.dx=canvas.getAttribute("width")-this.dw;                    //转向                    this.delta=-this.delta;                    this.dir_left=true;                    this.index=0;                }                         this.dy=this.dy;//y 不移动                                    this.index++;                this.last_update=now;            }        }                function animate(){            //停止动画            stop();            //移动速度            var delta=parseInt(document.getElementById('txt4').value);            //每秒绘制多少次            var fps=parseInt(document.getElementById('txt5').value);            //比例            var scale=parseInt(document.getElementById('txt6').value);                        //画布对象            var canvas=document.getElementById("canvas3")            //获取上下文对象            var ctx = canvas.getContext("2d");            //清空画布            ctx.clearRect(0,0,canvas.getAttribute("width"),canvas.getAttribute("height"));                        var img=new Image();            img.src="http://images.cnblogs.com/cnblogs_com/myqiao/html5/sprite.gif";                var sprite=new Sprite(120,150,delta,fps);            interval = setInterval(function(){                //清空画布                ctx.clearRect(0,0,canvas.getAttribute("width"),canvas.getAttribute("height"));                //更新数据                sprite.update(canvas);                //保存状态                ctx.save();                //移动坐标                ctx.translate(sprite.dx,sprite.dy);                ctx.scale(scale,scale);                ctx.drawImage(img,sprite.sx,sprite.sy,sprite.sw,sprite.sh,0,0,sprite.dw,sprite.dh);                //恢复状态                ctx.restore();            },1);                    }            </script>

赞(0)
分享到: 更多 (0)
网站地图   沪ICP备18035694号-2    沪公网安备31011702889846号