您的位置:首页 > Web前端 > HTML5

HTML5游戏开发(十)

2018-01-14 16:27 183 查看

HTML5游戏开发(十)

一、动画

1、动画循环

  在Canvas中这病动画效果很简单:只需要在播放动画时持续更新并绘制就行了。这种持续更新与重绘叫就动画循环。

(1)通过requestAnimationFrame()方法让浏览器来自行决定帧速率:

  使用window.setInterval()或window.setTimeout()制作出的动画,其效果可能并不如预期般流畅,而且还可能会占用额外的资源。这是因为setInterval()与setTimeout方法具有如下特征:

==它们都是通过法,并不是专为制作动画而用的。

即使向其传递以毫秒为单位的参数值,它们也达不到毫秒级的精确性。

没有对调用动画循环的机制作优化。

不考虑绘制动画的最佳时机,而只会一味地以某个大致的时间时隔来调用动画循环。==

(2)Ployfill

window.requestNextAnimationFrame =
(function () {
var originalWebkitRequestAnimationFrame = undefined,
wrapper = undefined,
callback = undefined,
geckoVersion = 0,
userAgent = navigator.userAgent,
index = 0,
self = this;

//chrome浏览器
if (window.webkitRequestAnimationFrame) {
wrapper = function (time) {
if (time === undefined) {
time = +new Date();
}
self.callback(time);
};
//原生
originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;
window.webkitRequestAnimationFrame = function (callback, element) {
self.callback = callback;
originalWebkitRequestAnimationFrame(wrapper, element);
}
}
//火狐浏览器
if (window.mozRequestAnimationFrame) {
index = userAgent.indexOf('rv:');
if (userAgent.indexOf('Gecko') != -1) {
geckoVersion = userAgent.substr(index + 3, 3);
if (geckoVersion === '2.0') {
//原生
window.mozRequestAnimationFrame = undefined;
}
}
}

return window.requestAnimationFrame   ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame    ||
window.oRequestAnimationFrame      ||
window.msRequestAnimationFrame     ||
//回调函数
function (callback, element) {
var start,
finish;
window.setTimeout( function () {
start = +new Date();
callback(start);
finish = +new Date();
self.timeout = 1000 / 60 - (finish - start);
}, self.timeout);
};
}
)();


2.帧速率计算

 &ems;动画是由一系列叫做帧的图像组成的,这些图像的显示频率就叫做帧速率。通常来说,有必要计算一下帧速率。在基于时间的运动效果是地,可能会用到动画的帧速率,或是有时为了保证动画能够播放得足够流畅,我们也需要知道帧速率。 #### (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);
}

#controls {
margin-top: 10px;
margin-left: 15px;
}
</style>
</head>

<body>
<div id='controls'>
<input id='animateButton' type='button' value='开始' />
</div>
<canvas id='canvas' width='500' height='300'>
</canvas>
<script src='js/Frame.js'>
</script>
</body>

</html>


JS脚本

var canvas = document.querySelector('#canvas'),
context = canvas.getContext('2d'),
paused = true,
//定义三个小球数据
discs = [{
x: 150,
y: 50,
lastX: 150,
lastY: 50,
velocityX: 3.2,
velocityY: 3.5,
radius: 25,
innerColor: 'rgba(0,255,255,0.3)',
middleColor: 'rgba(0,255,255,0.9)',
outerColor: 'rgba(0,255,255,0.3)',
strokeStyle: 'slateblue',
}, {
x: 75,
y: 200,
lastX: 75,
lastY: 200,
velocityX: 2.2,
velocityY: 2.5,
radius: 25,
innerColor: 'rgba(225,225,225,0.1)',
middleColor: 'rgba(225,225,225,0.9)',
outerColor: 'rgba(225,225,225,0.3)',
strokeStyle: 'gray'
}, {
x: 100,
y: 150,
lastX: 150,
lastY: 50,
velocityX: 1.2,
velocityY: 1.5,
radius: 25,
innerColor: 'orange',
middleColor: 'yellow',
outerColor: 'gold',
shadowColor: 'rgba(255,0,0,0.7)',
strokeStyle: 'orange'
}, ],
//小球数量
numDiscs = discs.length,
//时间
lastTime = 0,
//
lastFpsUpdateTime = 0,
frameCount = 0,
animateButton = document.querySelector('#animateButton');

//擦除背景
function eraseBackground() {
context.clearRect(0, 0, canvas.width, canvas.height);
}

//更新小球位置,进行坐标计算
function update() {
var i = numDiscs,
disc = null;
while(i--) {
disc = discs[i];
//      坐标计算
if(disc.x + disc.velocityX + disc.radius > context.canvas.width ||
disc.x + disc.velocityX - disc.radius < 0)
//横坐标
disc.velocityX = -disc.velocityX;

if(disc.y + disc.velocityY + disc.radius > context.canvas.height ||
disc.y + disc.velocityY - disc.radius < 0)
//纵坐标
disc.velocityY = -disc.velocityY;
//      默认为相加 每次移动距离
disc.x += disc.velocityX;
disc.y += disc.velocityY;
}
}
//绘制小球
function drawDisc(disc) {
var gradient = context.createRadialGradient(disc.x, disc.y, 0,
disc.x, disc.y, disc.radius);

gradient.addColorStop(0.3, disc.innerColor);
gradient.addColorStop(0.7, disc.middleColor);
gradient.addColorStop(1.0, disc.outerColor);

context.save();
context.beginPath();
context.arc(disc.x, disc.y, disc.radius, 0, Math.PI * 2, false);
context.clip();

context.fillStyle = gradient;
context.strokeStyle = disc.strokeStyle;
context.lineWidth = 2;
context.fill();
context.stroke();

context.restore();
}
//绘制小球
function draw() {
var i = numDiscs,
disc;
i = numDiscs;
while(i--) {
disc = discs[i];
//小球绘制
drawDisc(disc);
disc.lastX = disc.x;
disc.lastY = disc.y;
}

if(frameCount === 100) {
frameCount = -1;
}

if(frameCount !== -1 && frameCount < 100) {
frameCount++;
}
}
//计算帧率
function calculateFps() {
//每秒的播发帧数
var now = (+new Date),
fps = 1000 / (now - lastTime);
lastTime = now;
return fps;
}
//动画
function animate() {
//获取当前时间
var now = (+new Date),
fps = 0;
//启动动画
if(!paused) {
//擦除重绘
eraseBackground();
//更新
update();
//绘制
draw();
//计算帧率
fps = calculateFps();
//如果当前时间与最后更新时间大于1000
if(now - lastFpsUpdateTime > 1000) {
lastFpsUpdateTime = now;
//最后帧率
lastFpsUpdate = fps;
}
//设置帧率显示样式
context.fillStyle = 'cornflowerblue';
//四舍五入取值
context.fillText(lastFpsUpdate.toFixed() + ' fps', 45, 50);
}
//chrome可以使用标准的requestAnimationFrame
window.requestAnimationFrame(animate);
}

//加载就调用
window.requestAnimationFrame(animate);


(2)事件处理

//-------------------------------事件处理
canvas.onclick = function(e) {
paused = paused ? false : true;
};

animateButton.onclick = function(e) {
paused = paused ? false : true;
if(paused) {
animateButton.value = '开始';
} else {
animateButton.value = '停止';
}
};

context.canvas.width = canvas.width;
context.canvas.height = canvas.height;
//设定字体
context.font = '48px Helvetica';


显示效果:



3.恢复动画背景

从来质上来看,恢复动画背景无外乎三种方法:

1、将所有内容擦除,并重绘制;

2、仅绘内容发生变化的区域;

3、从离屏缓冲区中将内容发生变化的那部分背景图像恢复到屏幕上。

4.利用剪辑区域来重绘背景

//重绘背景
function drawBackground(){
context.fillStyle="#99CC99";
context.fillRect(0,0,canvas.width,canvas.height);
}
//重绘小球区域
function drawDiscBackground(disc){
//保存原来
context.save();
context.beginPath();
context.arc(disc.lastX,disc.lastY,disc.radius+1,0,Math.PI*2,false);
//剪辑
context.clip();
//擦除背景
eraseBackground();
//重绘背景
drawBackground();
//恢复
context.restore();
}

//绘制小球
function draw() {
var i = numDiscs,
disc;
i = numDiscs;
while(i--) {
drawDiscBackground(discs[i]);
disc = discs[i];
//小球绘制
drawDisc(disc);
disc.lastX = disc.x;
disc.lastY = disc.y;
}

if(frameCount === 100) {
frameCount = -1;
}

if(frameCount !== -1 && frameCount < 100) {
frameCount++;
}
}


5.利用图块复制技术重绘背景

将整个背景一次性地绘制到离屏canvas中,稍后从离屏canvas中只将修复动画背景所需的那一块图像复制到屏幕上即可。

//创建离屏对象
var offscreenCanvas = document.createElement('canvas');
offscreenContext = offscreenCanvas.getContext('2d');
//离屏的大小设定
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
//将原画布复制给离屏
function drawBackground() {
//将当前画布给离屏
offscreenContext.drawImage(canvas, 0, 0,
canvas.width, canvas.height);
}
//重绘小球区域
function drawDiscBackground(context, disc) {
var x = disc.lastX,
y = disc.lastY,
r = disc.radius,
w = r * 2,
h = r * 2;
//保存原来
context.save();
context.beginPath();
context.arc(x, y, r + 1, 0, Math.PI * 2, false);
//剪辑
context.clip();
//擦除背景
eraseBackground();
//重绘背景--使用离屏技术
context.drawImage(offscreenCanvas,x-r,y-r,w,h,x-r,y-r,w,h);
//恢复
context.restore();
}


默认情况下:浏览器使用了双缓冲技术绘制动画

6.基于时间的运动

例:每秒移动100个像素,那么每毫秒移动的像素为:100/1000=0.1px,计算每一帧移动多少像素:假设这一帧对于上一帧过去的时间为200ms,那么这一帧移动的像素即为:200ms*0.1px=20px

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

#controls {
margin-top: 10px;
margin-left: 15px;
}
</style>
</head>

<body>
<div id='controls'>
<input id='animateButton' type='button' value='开始' />
</div>
<canvas id='canvas' width='500' height='300'>
</canvas>
<script src='js/TimeFrame.js'>
</script>
</body>

</html>


JS脚本

var canvas = document.querySelector('#canvas'),
context = canvas.getContext('2d'),
paused = true,
discs = [{
x: 215,
y: 175,
lastX: 150,
lastY: 75,
velocityX: -3.2,
velocityY: 4.5,
radius: 25,
innerColor: 'rgba(255,0,0,1.0)',
middleColor: 'rgba(255,0,0,0.7)',
outerColor: 'rgba(255,0,0,0.5)',
shadowColor: 'rgba(255,0,0,0.7)',
strokeStyle: 'orange'
}, {
x: 250,
y: 150,
lastX: 150,
lastY: 80,
velocityX: 2.2,
velocityY: -4.5,
radius: 25,
innerColor: 'rgba(255,255,0,1)',
middleColor: 'rgba(255,255,0,0.7)',
outerColor: 'rgba(255,255,0,0.5)',
shadowColor: 'rgba(175,175,175,0.7)',
strokeStyle: 'gray',
},

{
x: 150,
y: 75,
lastX: 50,
lastY: 150,
velocityX: 2.2,
velocityY: -1.5,
radius: 25,
innerColor: 'rgba(100,145,230,1.0)',
middleColor: 'rgba(100,145,230,0.7)',
outerColor: 'rgba(100,145,230,0.5)',
shadowColor: 'rgba(100,145,230,0.8)',
strokeStyle: 'blue'
},

{
x: 100,
y: 100,
lastX: 150,
lastY: 75,
velocityX: -5.9,//X轴速度
velocityY: -0.2,//Y轴速度
radius: 25,
innerColor: 'rgba(255,0,0,1.0)',
middleColor: 'rgba(255,0,0,0.7)',
outerColor: 'rgba(255,0,0,0.5)',
shadowColor: 'rgba(255,0,0,0.7)',
strokeStyle: 'orange'
},
],
numDiscs = discs.length,
startTime = 0, //开始时间
lastTime = 0, //结束时间
elapsedTime = 0, //运行时间,每帧时间差
fps = 0, //帧率
lastFpsUpdate = {
time: 0,
value: 0
},
animateButton = document.querySelector('#animateButton');
//擦除背影
function eraseBackground() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
//更新小球
function update() {
var i = numDiscs,
disc = null;

while(i--) {
disc = discs[i];

if(disc.x + disc.velocityX + disc.radius > context.canvas.width ||
disc.x + disc.velocityX - disc.radius < 0)
disc.velocityX = -disc.velocityX;

if(disc.y + disc.velocityY + disc.radius > context.canvas.height ||
disc.y + disc.velocityY - disc.radius < 0)
disc.velocityY = -disc.velocityY;

disc.x += disc.velocityX;
disc.y += disc.velocityY;
}
}
//基于时间更新
function updateTimeBased(time) {
var i = numDiscs,
disc = null;

if(fps == 0)
return;

while(i--) {
disc = discs[i];
deltaX = disc.velocityX
//elapsedTime为每帧时间差,速度(像素)X每帧秒数,移动坐标像素
deltaX = disc.velocityX * (elapsedTime / 1000);
deltaY = disc.velocityY * (elapsedTime / 1000);

if(disc.x + deltaX + disc.radius > context.canvas.width ||
disc.x + deltaX - disc.radius < 0) {
disc.velocityX = -disc.velocityX;
deltaX = -deltaX;
}

if(disc.y + deltaY + disc.radius > context.canvas.height ||
disc.y + deltaY - disc.radius < 0) {
disc.velocityY = -disc.velocityY;
deltaY = -deltaY;
}

disc.x = disc.x + deltaX;
disc.y = disc.y + deltaY;
}
}
//绘制小球
function draw() {
var i = numDiscs,
disc = discs[i];

while(i--) {
disc = discs[i];
//
gradient = context.createRadialGradient(disc.x, disc.y, 0,
disc.x, disc.y, disc.radius);

gradient.addColorStop(0.3, disc.innerColor);
gradient.addColorStop(0.5, disc.middleColor);
gradient.addColorStop(1.0, disc.outerColor);

context.beginPath();
context.arc(disc.x, disc.y, disc.radius, 0, Math.PI * 2, false);

context.save();

context.fillStyle = gradient;
context.strokeStyle = disc.strokeStyle;
context.fill();
context.stroke();
context.restore();

}
}
//计算帧率
function calculateFps(now) {
//第一帧所用时间单位为毫秒
elapsedTime = now - lastTime;
//计算帧率,每一秒多少帧
fps = 1000 / elapsedTime;
lastTime = now;
}
//更新帧率
function updateFps() {
var now = (+new Date);
//计算帧率
calculateFps(now);

if(now - startTime < 2000) {
return;
}

if(now - lastFpsUpdate.time > 1000) {
lastFpsUpdate.time = now;
lastFpsUpdate.value = fps;
}
if(!paused) {
context.fillStyle = 'cornflowerblue';
context.fillText(lastFpsUpdate.value.toFixed() + ' fps', 50, 48);
}
}
//基于时间运行动画
function animate(time) {
if(time === undefined) {
time = +new Date;
}
//console.log(time)
if(!paused) {
//擦除重绘背景
eraseBackground();
//drawBackground();
//基于时间
updateTimeBased(time);
//非基于时间
//update();
draw();
}
//更新帧率
updateFps();
}
//按钮事件
animateButton.addEventListener('click', function(e) {
paused = paused ? false : true;
if(paused) {
animateButton.value = '开始';
} else {
for(var i = 0; i < discs.length; ++i) {
discs[i].velocityX *= 20;
discs[i].velocityY *= 50;
}
animateButton.value = '停止';
}
});

context.font = '36px Helvetica';
//获取开始是境
startTime = +new Date;
//计算每一帧所用时长为多少毫秒,以下为每秒60帧动画
setInterval(animate, 1000 / 60);


6.视差动画

视差动画,就是动画制作者让各个动画图层以不同的速度滚动,这样就实现了视差效果。近的移动快,远的移动慢。

(1)初始设置

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>视差动画</title>
<style>
body {
background: #dddddd;
}
#canvas {
position: absolute;
left: 20px;
top: 30px;
background: #ffffff;
margin-left: 10px;
margin-top: 10px;
box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
}
margin-left: 15px;
}

</style>
</head>

<body>
<input id='animateButton' type='button' value='动画'/>
<canvas id='canvas' width='1000' height='440'>
</canvas>
<script src='js/requestNextAnimationFrame.js'></script>
<script src='js/Parallax.js'></script>
</body>
</html>


JS脚本

var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
animateButton = document.getElementById('animateButton'),
//树
tree = new Image(),
//大数
nearTree = new Image(),
//草
grass = new Image(),
grass2 = new Image(),
//天空
sky = new Image(),

paused = true,
lastTime = 0,
lastFpsUpdate = { time: 0, value: 0 },
fps=60,
//天空偏移
skyOffset = 0,
//草地偏移
grassOffset = 0,
//树偏移
treeOffset = 0,
//大树偏移
nearTreeOffset = 0,
//速度
TREE_VELOCITY = 20,
FAST_TREE_VELOCITY = 40,
SKY_VELOCITY = 8,
GRASS_VELOCITY = 75;

//----------------------初始设置
context.font = '48px Helvetica';

tree.src = 'img/smalltree.png';
nearTree.src = 'img/tree-twotrunks.png';
grass.src = 'img/grass.png';
grass2.src = 'img/grass2.png';
sky.src = 'img/sky.png';

//擦除背景
function erase() {
context.clearRect(0,0,canvas.width,canvas.height);
}
//绘制图像
function draw() {
//天空
skyOffset = skyOffset < canvas.width ?
skyOffset + SKY_VELOCITY/fps : 0;
//草地
grassOffset = grassOffset < canvas.width ?
grassOffset +  GRASS_VELOCITY/fps : 0;
//树
treeOffset = treeOffset < canvas.width ?
treeOffset + TREE_VELOCITY/fps : 0;
//大树
nearTreeOffset = nearTreeOffset < canvas.width ?
nearTreeOffset + FAST_TREE_VELOCITY/fps : 0;
//保存画布
context.save();
//平移
context.translate(-skyOffset, 0);
//绘制两个天空,实现背景滚动
context.drawImage(sky, 0, 0);
context.drawImage(sky, sky.width-2, 0);
//恢复画布
context.restore();

//保存画布
context.save();
context.translate(-treeOffset, 0);
context.drawImage(tree, 100, 240);
context.drawImage(tree, 1100, 240);
context.drawImage(tree, 400, 240);
context.drawImage(tree, 1400, 240);
context.drawImage(tree, 700, 240);
context.drawImage(tree, 1700, 240);
//恢复画布
context.restore();

context.save();
context.translate(-nearTreeOffset, 0);
context.drawImage(nearTree, 250, 220);
context.drawImage(nearTree, 1250, 220);
context.drawImage(nearTree, 800, 220);
context.drawImage(nearTree, 1800, 220);
context.restore();

context.save();
context.translate(-grassOffset, 0);
context.drawImage(grass, 0, canvas.height-grass.height);
context.drawImage(grass, grass.width-5,
canvas.height-grass.height);
context.drawImage(grass2, 0, canvas.height-grass2.height);
context.drawImage(grass2, grass2.width,
canvas.height-grass2.height);
context.restore();

}


(2)动画实现

//--------------------帧率
//帧率
function calculateFps(now) {
var fps = 1000 / (now - lastTime);
lastTime = now;
return fps;
}
//动画
function animate(now) {
if (now === undefined) {
now = +new Date;
}
fps = calculateFps(now);
if (!paused) {
erase();
draw();
}
requestNextAnimationFrame(animate);
}

animateButton.onclick = function (e) {
paused = paused ? false : true;
if (paused) {
animateButton.value = '动画';
}
else {
animateButton.value = '暂停';
}
};

//加载绘制
sky.onload = function (e) {
draw();
};
//动画加载
requestNextAnimationFrame(animate);


显示效果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: