audio元素
使用HTML5 audio元素是新的在网页中嵌入音频文件的标准方法。在此元素出现前,大部分播放音频的网页都采用嵌入式插件(如Flash)。
我们既可以在HTML中使用<audio>
标签,也可以在JavaScript中使用audio对象。
<audio src="music.mp3" controls> Your broswer does not support HTML5 Audio. </audio>
注意 不支持audio元素的浏览器会忽视
<audio>
标签,并将标签内的其他内容渲染出来。
controls属性会使浏览器显示一个简单的“浏览器指定”的音频文件播放界面(包括如播放/暂停按钮和音量控制条)。
效果如下:
audio元素还有如下一些属性:
preload:指定音频是否会被预加载
autoplay:指定音频被加载后是否自动播放
loop:指定音频播放结束后是否循环播放
不同的浏览器对音频文件格式的支持
为避开一些限制,可以准备多个不同格式的音频文件,以供浏览器选择并播放。<audio>
标签中允许出现多个<source>
标签,浏览器会自动使用第一个支持的格式。
<audio controls> <source src="music.ogg" type="audio/ogg"> <source src="music.mp3" type="audiio/mpegs"> Your broswer does not support HTML5 Audio. </audio>
也可以在JavaScript中使用audio对象来动态加载音频。根据自己的需要,通过audio对象控制音频的加载、播放、暂停,这也是我们在游戏中将采取的方法。
<script> // 创建一个audio对象 var sound = new Audio(); // 选择音频源 sound.src = "music.mp3"; // 播放音频 sound.play(); </script>
audio对象与HTML中的<audio>
标签一样,需要先探查浏览器支持哪种音频格式,然后加载合适的文件。audio对象提供canPlayType()方法以探查浏览器是否支持某种音频格式,它返回""、"maybe"或"probably"。利用该方法,首先做一个简单的检查,然后根据检查的结果加载合适格式的文件,如下所示:
<script> var audio = document.createElement('audio'); var mp3Support, oggSupport; if (audio.canPlayType){ // 当前canPlayType()方法返回"","maybe"或"probably" mp3Support = "" != audio.canPlayType('audio/mpeg'); oggSupport = "" != audio.canPlayType('audio/ogg'); }else{ // 不支持audio标签 mp3Support = false; oggSupport = false; } // 检查是否支持ogg,mp3,如果不支持,就将soundFileExtn设置为undefined var soundFileExtn = mp3Support?".mp3": oggSupport?".ogg":undefined; if (soundFileExtn){ var sound = new Audio(); // 加载具有探查到扩展名的音频文件 sound.src = "music" + soundFileExtn; sound.play(); } </script>
音频文件加载完成后,audio对象触发canplaythrough事件。可以利用该事件追踪音频文件何时加载完成,如下所示:
<script> if (soundFileExtn) { var sound = new Audio(); sound.addEventListener('canplaythrough', function () { alert("loaded"); sound.play(); }); sound.src = "music" + soundFileExtn; } </script>
通过这种方式,我们可以设计一个预加载对象,它负责在游戏开始之前,把游戏资源全部加载进来。
image元素
image元素允许HTML显示图片。最简单的方法就是使用<img>
标签并指定src属性,也可以在JavaScript中建立image对象,并设定src属性来动态加载一张图片。如下所示:
var image = new Image(); image.src = 'p1_jump.png';
上述两种方式获得的图像都能被绘制在canvas上。
图像加载
游戏通常被设计成当素有的图像加载完成后,再开始运行。通常,我们会显示一个进度条或状态栏告诉用户多少图像已经加载完成了。image对象的onload事件将在浏览器完成对图像的加载之后立即触发。使用这个事件可以追踪图像何时加载完成,如下所示:
image.onload = function(){ alert('Image finished loading'); };
使用onload事件能创建一个简单的图像加载器,用来追踪目前已经加载了多少图像,如下所示:
var imageLoader = { loaded:true, loadedImages:0, totalImages:0, load:function(url){ this.totalImages++; this.loaded = false; var image = new Image(); image.src = url; image.onload = function(){ imageLoader.loadedImages++; if (imageLoader.loadedImages === imageLoader.totalImages){ imageLoader.loaded = true; } } return image; } }
这个图片加载器可用来加载大量的图片(如在一个循环中)。imageLoader.loaded属性表示是否所有的图片都被加载了,计算loadedImages/totalImages的值,就可以绘制一个百分比条或进度条。
子画面页(精灵图)
当游戏包含了太多的图像时,需要考虑的另一个问题是,如何优化图片服务器的负载。从数十张到数百张图像,游戏可能需求任何东西。即使一个最简单的即时战略(RTS)游戏也会需要不同单位、建筑、地图、背景和效果的图像。对于单位和建筑,你可能需要用不同的图像来表示它们不同的方向和状态;对于动画,你可能在每一帧都需要一张图像。
在早前的一个RTS游戏项目中,我曾经对动画的每一帧和单位的每一个状态都用了单独的图片,结果图片的数目超过了1000张。大部分浏览器同时只能发出几个请求,因此,下载所有的这些图片不仅耗费了大量时间,也使服务器收到的HTTP请求过载了。在本地计算机上测试代码的时候,这允许还不算问题,但如果代码在服务器上,那就糟糕了——在真正玩游戏前,用户需要等待5~10分钟(有时甚至更长)。这个问题就需要采取子画面页(sprite sheet,也称精灵图)来解决。
子画面页是一张单独的较大的图片文件,其中存储了某个物体的所有图像(不同方向、位置、某一帧等)。显示某个图像的时候,先计算出该图像在子画面中的位置偏移量,然后利用drawImage()方法绘制图片局部的功能,画出子画面页的“那个部分”(也就是子画面)。
绘制单独的图像
// 首先:单独载入每张照片并存储在一个长数组中 // 三个参数:图片(数组元素)和待绘制坐标(x,y) var image = imageArray[imageNumber]; context.drawImage(image,x,y);
利用子画面页绘制图像
// 9个参数:图片(子画面页)、图像在子画面页上的坐标(x,y) // 图像在子画面页上的宽度和高度(用来切割) // 待绘制坐标(x,y)和绘制宽度与高度(调整大小) context.drawImage(this.spriteImage,this.imageWidth*(imageNumber),0, this.imageWidth, this.imageHeight,x,y,this.imageWidth,this.imageHeight);
使用子画面页有以下一些优点:
更少的HTTP请求
更好的压缩性能
更快的加载时间
动画:计时器和游戏循环
动画实际上就是绘制物体、擦掉,然后再新的位置上重新画出来。我们通常会频繁调用某个绘图函数(每秒数次)来产生动画。某些游戏还具有单独的控制/动画方法来更新游戏中物体的位置(以控制物体移动),这个方法调用的频率比绘图函数小一些。
典型的动画和绘制循环
function animationLoop(){ // 遍历游戏中的所有物体并更新它们的位置}function drawingLoop(){ // 1. 清除canvas // 2. 遍历所有的物体 // 3. 绘制每个物体}
现在的问题是,如何每隔一小段时间就重复调用drawingLoop()
方法呢?最简单的方式是:使用时间函数setInterval()
方法与setTimeout()
方法。setInterval(functionName,timeInterval)
告诉浏览器,每隔一个固定的时间间隔就调用一次给定的函数,直到函数clearInterval()
被调用。当需要停止动画的时候(游戏暂停或结束),我们就调用·clearInterval()`
使用setInterval调用绘图循环
// 每20ms调用一次drawingLoop()var gameLoop = setInterval(drawingLoop, 20);// 停止调用drawingLoop(),并清除游戏循环变量clearInterval(gameLoop);
setTimeout(functionName,timeInterval)
告诉浏览器在一个给定的时间间隔后,调用一次给定的函数。
使用setTimeout调用绘图循环
function drawingLoop(){ // 1. 20ms后调用drawingLoop方法一次 var gameLoop = setTimeout(drawingLoop,20); // 2. 清空canvas // 3. 遍历所有的物体 // 4. 绘制所有的物体}
当我们需要停止动画时(游戏暂停或结束),调用clearTimeout()
。
// 停止调用drawingLoop(),并清除游戏循环变量clearTimeout(gameLoop);
requestAnimationFrame
使用setInterval()
或setTimeout()
来实现动画确实行得通,但是浏览器厂商已经提出了一个新的API专门用来处理动画。用这个API代替setInterval()
的优势是,浏览器可以:
将动画代码优化到一个回流-重绘的循环中去,使动画更流畅
标签页不可见时,暂停动画,减少对CPU和GPU的占用
在不支持更高帧频的机器上自动限制帧频,在有能力比较高帧频运行动画的机器上提高帧频
不同的浏览器厂商对API中的这个方法有各自的命名(如在微软的IE浏览器中被命名为msRequestAnimationFrame,而在Mozilla的Firefox浏览器中被命名为mozRequestAnimationFrame)。但是,只需向你的代码中加入以下这么一小段代码就可以使用两个跨平台的方法:requestAnimationFrame()
和cancelAnimationFrame()
。
简单的requestAnimationFrame方法
(function(){ var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', '0']; for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x){ window.requestAnimationFrame = window[vendors[x] + 'RequestAnimatioinFrame']; window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element){ var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function(){ callback(currTime+timeToCall); },timeToCall); lastTime = currTime + timeToCall; return id; }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id){ this.clearTimeout(id); }; }());
注意 现在我们不能确定帧的刷新频率(浏览器决定调用绘图循环的速度),但我们需要确保运动物体在屏幕上移动的速度与实际帧频无关。方法是:计算距离上一次调用绘图循环的时间,以此决定物体移动的距离。
有了上述的代码,我们就可以从drawingLoop()
方法内部(如同调用setTimeout()
方法一样)调用requestAnimationFrame()
方法。
使用requestAnimationFrame调用绘图循环
function drawingLoop(nowTime){ // 1. 当浏览器准备再次开始绘制时,调用drawingLoop()方法 var gameLoop = requestAnimationFrame(drawingLoop); // 2. 清空canvas // 3. 遍历所有的物体 // 4. 可选,使用当前时间和上一次当前时间修改画面 // 5. 画出它们}
当我们需要停止动画(游戏暂停或结束)时,可调用cancelAnimationFrame()
。
// 停止调用drawingLoop(),并清除游戏循环变量cancelAnimationFrame(gameLoop);
以上。
作者:Viljw
链接:https://www.jianshu.com/p/296ebcf8ab8f