和C++游戏开发相同,HTML5逐帧动画需要的图像元素也是一张绘制了每一帧图像效果的图片。通过循环绘制各帧的图像来实现动画的效果。
本示例中演示的是一个小人,默认状态下,小人朝右方站立;按下左/右方向键的时候,小人朝左/右方奔跑(在画布中没有位移);松开按键后保持奔跑的方向站立。
其中,向左或向右站立分别是一张6帧的图片,向左或向右奔跑分别是一张12帧的图片。
代码如下:
HTML代码:
<canvas id="canvas" width="600" height="400">
<p>Your browser does not support the canvas element!</p>
</canvas>
JavaScript代码如下:
以下这段代码已经在本人的博文中多次重用,所以就不解释了。
Array.prototype.remove = function(obj) {
for (i in this) {
if (this[i] === obj) {
this.splice(i, 1);
}
}
}
function BasicObject(x, y, order) {
this.x = x;
this.y = y;
this.order = isNaN(order) ? 0 : order;
this.addTo = function(list) {
list.push(this);
list.sort(function(a, b) {return a.order - b.order;});
}
this.removeFrom = function(list) {
list.remove(this);
}
}
逐帧动画的基础对象,继承自基础对象类,添加了图像、总帧数两个属性,以及绘制逐帧对象的方法
function FrameAnimationObject(x, y, order, image, frame) {
BasicObject.call(this, x, y, order);
this.image = image;
this.frame = frame;
this.currentFrame = 0;
this.draw = function(context) {
var sw = this.image.width / this.frame;
var sx = this.currentFrame * sw;
context.drawImage(this.image, sx, 0, sw, this.image.height, this.x, this.y, sw, this.image.height);
this.currentFrame++;
this.currentFrame = (this.currentFrame >= this.frame) ? 0 : this.currentFrame;
}
}
FrameAnimationObject.prototype = new BasicObject();
奔跑小人的类,继承自逐帧动画基础对象,指定了需求使用的图像资源,并添加了响应键盘事件的方法
function Person(x, y, order) {
FrameAnimationObject.call(this, x, y, order);
this.image = new Image();
this.image.src = "stop_right.png"
this.frame = 6;
this.onkeydown = function(event) {
if (event.keyCode == 37) {
this.image.src = "run_left.png";
this.frame = 12;
}
else if (event.keyCode == 39) {
this.image.src = "run_right.png";
this.frame = 12;
}
this.currentFrame = (this.currentFrame >= this.frame) ? 0 : this.currentFrame;
}
this.onkeyup = function(event) {
if (event.keyCode == 37) {
this.image.src = "stop_left.png";
}
else if (event.keyCode == 39) {
this.image.src = "stop_right.png";
}
this.frame = 6;
this.currentFrame = (this.currentFrame >= this.frame) ? 0 : this.currentFrame;
}
}
Person.prototype = new FrameAnimationObject();
动画引擎类以及程序入口
function Engin() {
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var buffer = document.createElement("canvas");
buffer.width = canvas.width;
buffer.height = canvas.height;
var bufferCtx = buffer.getContext("2d");
var objs = new Array();
const FPS = 20;
this.manage = function() {
bufferCtx.clearRect(0, 0, buffer.width, buffer.height);
context.clearRect(0, 0, canvas.width, canvas.height);
for (x in objs) {
if (objs[x].update) {
objs[x].update(objs);
}
}
for (x in objs) {
if (objs[x].draw) {
objs[x].draw(bufferCtx);
}
}
context.drawImage(buffer, 0, 0);
}
document.onkeydown = function(event) {
for (x in objs) {
if (objs[x].onkeydown) {
objs[x].onkeydown(event);
}
}
}
document.onkeyup = function(event) {
for (x in objs) {
if (objs[x].onkeyup) {
objs[x].onkeyup(event);
}
}
}
this.run = function() {
var p = new Person(canvas.width / 2, canvas.height / 2);
p.addTo(objs);
setInterval(this.manage, 1000 / FPS);
}
}
window.onload = function() {
new Engin().run();
}
需要说明的是,本次将键盘事件的响应放到了动画对象的类中来实现,并在引擎类中通过设定document的键盘事件来引用,这昂做事为了依照上一篇博文总所说的将动画对象的逻辑操作封装在最外层,同时避免了引擎类的过分膨胀。当动画对象逐渐增多时,效果更加明显。