一、为什么选择 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 相关的资料。
下面我们给出一个上下移动方块的小动画,当遇到顶部或者底部时,会改变方向。代码如下:
< 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 类负责计算每一帧方块的位置,这样就可以解决上面两个问题了。代码如下:
< 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 来称呼游戏中的一个角色。
这里有一幅图像,他描绘了一个小精灵的飞行动作:
下面我们将实现一个 Sprite 类,让他在浏览器里面飞起来。
< 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 >
1.本站内容仅供参考,不作为任何法律依据。用户在使用本站内容时,应自行判断其真实性、准确性和完整性,并承担相应风险。
2.本站部分内容来源于互联网,仅用于交流学习研究知识,若侵犯了您的合法权益,请及时邮件或站内私信与本站联系,我们将尽快予以处理。
3.本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
4.根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
5.本站是非经营性个人站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途
暂无评论内容