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);
显示效果:
相关文章推荐
- HTML5 Canvas游戏开发(一)基础知识
- Html5 Egret游戏开发 成语大挑战(九)设置界面和声音管理
- 40个很棒的由html5开发的网络游戏案例
- HTML5游戏开发进阶 6 :加入单位
- [HTML5游戏开发]简单的《找没有同汉字版〗爆去考考您狄综力吧
- HTML5开发 页游/手游动画及游戏系列教程(Game Tutorial):(一)物体动起来吧
- 25 个超棒的 HTML5 & JavaScript 游戏引擎开发库
- 分享微信开发Html5轻游戏中的几个坑
- 25 个超棒的 HTML5 & JavaScript 游戏引擎开发库
- html5游戏开发笔记
- HTML5游戏开发开源库件lufylegend1.4.1发布
- 【转】html5游戏开发引擎
- html5游戏开发-零基础开发《圣诞老人送礼物》小游戏
- HTML5游戏开发技术基础整理
- 2014年25个基于HTML5开发的最佳游戏
- HTML5游戏开发-Box2dWeb应用(一)-创建各种各样的刚体
- Html5游戏开发之乒乓Ping Pong游戏示例(三)
- 磊友黄何:HTML5游戏开发成本低 盈利模式清晰
- HTML5游戏开发(四)
- 学习HTML5开发RPG游戏第五步>游戏界面设计<二>(结束)