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);
},
};