HTML5游戏开发(十一)
一、精灵
精灵是一种可以集成入动画之中的图形对象。并赋予它们各种行为。
精灵对象有两个方法:
paint()与update()。update()方法用于执行每个精灵的行为,执行顺序就是这些行为被加入精灵之中的顺序。paint()方法则将精灵绘制代理给绘制器来做,不过仅仅在精灵确实有绘制器并且可见是时,此方法才会生效。
Sprite构造器接受三个参数:精灵的名称,绘制器及行为数组。
1.精灵对象
/**
* 创建精灵对象
* @param {Object} name 名称
* @param {Object} painter 绘制器
* @param {Object} behaviors 对象行为
*/
var Sprite = function (name, painter, behaviors) {
if (name !== undefined) this.name = name;
if (painter !== undefined) this.painter = painter;
if (behaviors !== undefined) this.behaviors = behaviors;
return this;
};
//精灵绘制器
Sprite.prototype = {
left: 0,//
top: 0,//
width: 10,//宽
height: 10,//高
velocityX: 0,//X速度
velocityY: 0,//Y速度
visible: true,//是否可见
animating: false,//动画
painter: undefined, //绘制器 paint(sprite, context)
behaviors: [], //对象行为 execute(sprite, context, time)
//绘制精灵
paint: function (context) {
if (this.painter !== undefined && this.visible) {
this.painter.paint(this, context);
}
},
//执行行为
update: function (context, time) {
//执行所有的行为方法
for (var i = this.behaviors.length; i > 0; --i) {
//调用对象的execute方法
this.behaviors[i-1].execute(this, context, time);
}
}
};
2.精灵绘制器
(1)描边与填充绘制器
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>精灵示例</title>
<style>
body {
background: #dddddd;
}
#canvas {
position: absolute;
left: 0px;
top: 20px;
margin: 20px;
background: #ffffff;
border: thin inset rgba(100, 150, 230, 0.5);
}
</style>
</head>
<body>
<canvas id='canvas' width='300' height='300'>
</canvas>
<script type="text/javascript" src="js/Sprite.js"></script>
<script type="text/javascript" src="js/clock.js"></script>
</body>
</html>
JS脚本
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
//时钟半径
CLOCK_RADIUS = canvas.width / 2 - 15;
context.lineWidth = 0.5;
context.strokeStyle = 'rgba(0,0,0,0.2)';
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.shadowBlur = 4;
context.stroke();
//--------------------------1、编写绘制器
//绘制器
var ballPainter = {
//绘制方法
paint: function(sprite, context) {
//精灵参数
var x = sprite.left + sprite.width / 2,
y = sprite.top + sprite.height / 2,
width = sprite.width,
height = sprite.height,
radius = sprite.width / 2;
context.save();
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2, false);
context.clip();
//阴影
context.shadowColor = 'rgb(0,0,0)';
context.shadowOffsetX = -4;
context.shadowOffsetY = -4;
context.shadowBlur = 8;
//填充
context.fillStyle = 'rgba(218, 165, 32, 0.1)';
context.fill();
//路径
context.lineWidth = 2;
context.strokeStyle = 'rgb(100,100,195)';
context.stroke();
context.restore();
}
};
//---------------------------------2、使用精灵创建钟表
//创建精灵对象
var ball = new Sprite('ball', ballPainter);
//制表面
function drawClockFace() {
context.beginPath();
context.arc(canvas.width / 2, canvas.height / 2,
CLOCK_RADIUS, 0, Math.PI * 2, false);
context.save();
context.strokeStyle = 'rgba(0,0,0,0.2)';
context.stroke();
context.restore();
}
//绘制小球
function drawHand(loc, isHour) {
//计算角度
var angle = (Math.PI * 2) * (loc / 60) - Math.PI / 2,
//半径
handRadius = CLOCK_RADIUS,
//结束点
lineEnd = {
x: canvas.width / 2 +
Math.cos(angle) * (handRadius - ball.width / 2),
y: canvas.height / 2 +
Math.sin(angle) * (handRadius - ball.width / 2)
};
context.beginPath();
//起始点,中心点
context.moveTo(canvas.width / 2, canvas.height / 2);
//圆周长上
context.lineTo(lineEnd.x, lineEnd.y);
context.stroke();
//设置小球参数
ball.left = canvas.width / 2 +
Math.cos(angle) * handRadius - ball.width / 2;
ball.top = canvas.height / 2 +
Math.sin(angle) * handRadius - ball.height / 2;
//调用绘制器方法
ball.paint(context);
}
//小球处理
function drawHands() {
//秒球
var date = new Date(),
hour = date.getHours();
ball.width = 10;
ball.height = 10;
//大小为20的 指向秒的小球
drawHand(date.getSeconds(), false);
//分钟球
hour = hour > 12 ? hour - 12 : hour;
ball.width = 20;
ball.height = 20;
drawHand(date.getMinutes(), false);
//小时球
ball.width = 30;
ball.height = 30;
drawHand(hour * 5 + (date.getMinutes() / 60) * 5);
//中心点
ball.width = 10;
ball.height = 10;
ball.left = canvas.width / 2 - ball.width / 2;
ball.top = canvas.height / 2 - ball.height / 2;
ballPainter.paint(ball, context);
}
//绘制时钟
function drawClock() {
//绘制时钟表面
drawClockFace();
//绘制时分秒
drawHands();
}
//---------------------------------------3、创建动画
//动画
function animate() {
//清除画布
context.clearRect(0,0,canvas.width,canvas.height);
//时钟绘制
drawClock();
//动画
window.requestAnimationFrame(animate);
}
window.requestAnimationFrame(animate);
显示效果:
(2)图像绘制器
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图像绘制器</title>
<style>
body {
background: #eeeeee;
}
#canvas {
position: absolute;
left: 0px;
top: 20px;
margin-left: 10px;
background: lightskyblue;
border: thin solid rbga(0, 0, 0, 1.0);
box-shadow: rgba(0, 0, 0, 0.5) 4px 4px 6px;
}
</style>
</head>
<body>
<canvas id='canvas' width='200' height='160'>
</canvas>
<script type="text/javascript" src="js/Sprite.js"></script>
<script type="text/javascript" src="js/tree.js"></script>
</body>
</html>
//图像绘制器
var ImagePainter = function (imageUrl) {
this.image = new Image;
this.image.src = imageUrl;
};
ImagePainter.prototype = {
image: undefined,
paint: function (sprite, context) {
if (this.image !== undefined) {
//complete 属性可返回浏览器是否已完成对图像的加载。
if ( ! this.image.complete) {
//加载图像
this.image.onload = function (e) {
sprite.width = this.width;
sprite.height = this.height;
//图像绘制
context.drawImage(this,
sprite.left, sprite.top,
sprite.width, sprite.height);
};
}
else {
context.drawImage(this.image, sprite.left, sprite.top,
sprite.width, sprite.height);
}
}
}
};
//图像加载
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
tree = new Sprite('tree', new ImagePainter('img/smalltree.png')),
TREE_LEFT = 10,
TREE_TOP = 10,
TREE_WIDTH = 180,
TREE_HEIGHT = 130;
//绘制图像
function paint() {
tree.paint(context);
}
//动画
function animate(now) {
context.clearRect(0,0,canvas.width,canvas.height);
paint();
window.requestAnimationFrame(animate);
}
tree.left = TREE_LEFT;
tree.top = TREE_TOP;
tree.width = TREE_WIDTH;
tree.height = TREE_HEIGHT;
window.requestAnimationFrame(animate);
显示效果:
(3)精灵表绘制器
为了节省磁盘空间,减收下载次数,如果用于制作动画的精灵其每帧所用的图像都比较小,那么就可以把它们都放在一张图中,包含动画每一帧图像的图片,就叫做精灵表。
精灵表创建:
//精灵表绘制器
SpriteSheetPainter = function (cells) {
this.cells = cells;
};
SpriteSheetPainter.prototype = {
cells: [],//存储表
cellIndex: 0,
//获取不同的精灵对象
advance: function () {
if (this.cellIndex == this.cells.length-1) {
this.cellIndex = 0;
}
else {
this.cellIndex++;
}
},
//绘制精灵
paint: function (sprite, context) {
var cell = this.cells[this.cellIndex];
context.drawImage(spritesheet, cell.left, cell.top,
cell.width, cell.height,
sprite.left, sprite.top,
cell.width, cell.height);
}
};
精灵表使用:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>精灵表绘制器</title>
<style>
body {
background: #eeeeee;
}
#canvas {
position: absolute;
left: 0px;
top: 35px;
margin-left: 10px;
background: "#99CC99";
border: thin solid rbga(0, 0, 0, 1.0);
box-shadow: rgba(0, 0, 0, 0.5) 4px 4px 6px;
}
</style>
</head>
<body>
<input id='animateButton' type='button' value='开始' />
<canvas id='canvas' width='256' height='256'>
</canvas>
<script type="text/javascript" src="js/Sprite.js"></script>
<script type="text/javascript" src="js/spriteTable.js"></script>
</body>
</html>
JS脚本:
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
animateButton = document.getElementById('animateButton'),
spritesheet = new Image(),
//精灵表
runnerCells = [{
left: 0,
top: 0,
width: 256,
height: 256
}, {
left: 256,
top: 0,
width: 256,
height: 256
}, {
left: 512,
top: 0,
width: 256,
height: 256
}, {
left: 768,
top: 0,
width: 256,
height: 256
}, {
left: 1024,
top: 0,
width: 256,
height: 256
}, {
left: 1280,
top: 0,
width: 256,
height: 256
}, {
left: 1536,
top: 0,
width: 256,
height: 256
}, {
left: 1792,
top: 0,
width: 256,
height: 256
}],
//创建精灵对象
sprite = new Sprite('runner', new SpriteSheetPainter(runnerCells)),
lastAdvance = 0,
paused = false,
PAGEFLIP_INTERVAL = 100; //动画间的时间间隔
//暂停动画
function pauseAnimation() {
animateButton.value = '开始';
paused = true;
}
//开始动画
function startAnimation() {
animateButton.value = '暂停';
paused = false;
lastAdvance = 0;
window.requestAnimationFrame(animate);
}
//------------------------------事件处理
//动画开始按钮
animateButton.onclick = function(e) {
if(animateButton.value === '开始') startAnimation();
else pauseAnimation();
};
//动画
function animate(time) {
if(!paused) {
//清除画布
context.clearRect(0, 0, canvas.width, canvas.height);
drawBackground();
//保存画布
context.save();
//绘制
sprite.paint(context);
if(time - lastAdvance > PAGEFLIP_INTERVAL) {
//不断累加获取对象
sprite.painter.advance();
lastAdvance = time;
}
//恢复画布
context.restore();
window.requestAnimationFrame(animate);
}
}
//-------------------------初始设置
function drawBackground() {
//重绘背景
context.fillStyle = "#99CC99";
context.fillRect(0, 0, canvas.width, canvas.height);
}
spritesheet.src = 'img/sprite.png';
spritesheet.onload = function(e) {
//加载图像
context.drawImage(spritesheet, 0, 0);
};
drawBackground();
sprite.left = 0;
sprite.top = 0;
显示效果:
3.精灵对象的行为
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>精灵对象行为</title>
<style>
body {
background: #eeeeee;
}
#canvas {
position: absolute;
left: 0px;
top: 35px;
margin-left: 10px;
background: "#99CC99";
border: thin solid rbga(0, 0, 0, 1.0);
box-shadow: rgba(0, 0, 0, 0.5) 4px 4px 6px;
}
</style>
</head>
<body>
<canvas id='canvas' width='256' height='256'>
</canvas>
<script type="text/javascript" src="js/Sprite.js"></script>
<script type="text/javascript" src="js/behaviors.js"></script>
</body>
</html>
JS脚本
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
animateButton = document.getElementById('animateButton'),
spritesheet = new Image(),
//精灵表
runnerCells = [{left: 0,top: 0, width: 256, height: 256 },
{left: 256, top: 0, width: 256, height: 256 },
{left: 512, top: 0, width: 256, height: 256 },
{left: 768, top: 0, width: 256, height: 256 },
{left: 1024,top: 0, width: 256, height: 256 },
{left: 1280,top: 0, width: 256, height: 256 },
{left: 1536,top: 0, width: 256, height: 256 },
{left: 1792,top: 0, width: 256, height: 256 }];
//行为对象,原地行走
var runInPlace = {
lastAdvance: 0,
PAGEFLIP_INTERVAL: 100,//运行速度
//执行器
execute: function (sprite, context, time) {
if (time - this.lastAdvance > this.PAGEFLIP_INTERVAL) {
//精灵绘制
sprite.painter.advance();
this.lastAdvance = time;
}
}
};
//从右向左移动
moveLeftToRight = {
lastMove: 0,
//执行器
execute: function (sprite, context, time) {
if (this.lastMove !== 0) {
//精灵的左坐标按像素减去
sprite.left -= sprite.velocityX *
((time - this.lastMove) / 1000);
//如果精灵left小于0时
if (sprite.left < -(canvas.width/2).toFixed()) {
//精灵出现位置
sprite.left = canvas.width/2;
}
}
this.lastMove = time;
}
};
//创建精灵对象,并添加行为对象
var sprite = new Sprite('runner',
new SpriteSheetPainter(runnerCells),
//一个方法为移动,一个方法为从右向左,组合而行成的动作
[ runInPlace,moveLeftToRight]);
function animate(time) {
context.clearRect(0,0,canvas.width,canvas.height);
drawBackground();
//调用更新方法,用来运动
sprite.update(context, time);
//绘制精灵
sprite.paint(context);
window.requestAnimationFrame(animate);
}
//---------------------------------初始设置
//绘制背景
function drawBackground() {
//重绘背景
context.fillStyle = "#99CC99";
context.fillRect(0, 0, canvas.width, canvas.height);
}
spritesheet.src = 'img/sprite.png';
spritesheet.onload = function(e) {
context.drawImage(spritesheet, 100, 0);
};
sprite.velocityX = 50; //每秒移动像素为50
sprite.left = canvas.width/2; //精灵出现位置
sprite.top = 0;
window.requestAnimationFrame(animate);
显示效果:
4.动画制作器
//精灵动画器
var SpriteAnimator = function (painters, elapsedCallback) {
this.painters = painters;
if (elapsedCallback) {
this.elapsedCallback = elapsedCallback;
}
};
//精灵动画器
SpriteAnimator.prototype = {
painters: [],
duration: 1000, //持续时间
startTime: 0, //开始时间
index: 0,
elapsedCallback: undefined, //间隔回调
//动画终止
end: function (sprite, originalPainter) {
sprite.animating = false;
if (this.elapsedCallback) {
this.elapsedCallback(sprite);
}
else {
sprite.painter = originalPainter;
}
},
//动画开始
start: function (sprite, duration) {
var endTime = +new Date() + duration,
period = duration / (this.painters.length),
interval = undefined,
animator = this, // for setInterval() function
originalPainter = sprite.painter;
this.index = 0;
sprite.animating = true;
sprite.painter = this.painters[this.index];
//周期执行
interval = setInterval(function() {
if (+new Date() < endTime) {
sprite.painter = animator.painters[++animator.index];
}
else {
animator.end(sprite, originalPainter);
clearInterval(interval);
}
}, period);
},
};