在游戲中發(fā)揮HTML5 Canvas的潛能
在游戲中,fluidity(流暢性)這一概念非常重要,因為好的流暢性能帶給玩家更好的體驗。
這篇文章的主旨在于教你一些技巧,讓你可以最大程度地發(fā)揮HTML5 canvas的潛能。
我將通過一個例子來表達我要講的內(nèi)容。這個例子是2D tunnel effect(2D隧道效應),它是我為Coding4Fun 會議寫的(我參加了在法國舉辦的TechDays 2012)。
(http://video.fr.msn.com/watch/video/techdays-2012-session-technique-coding4fun/zqy7cm8l).
早在80年代,當我還是一個年輕的demomaker的時候曾受到一些Commodore AMIGA代碼的啟發(fā),從而寫了這個2D tunnel effect。
經(jīng)過不斷地改進,現(xiàn)在它僅使用了canvas和Javascript(最初的代碼是基于68000匯編的):
完整的代碼可以從這里獲得:http://www.catuhe.com/msdn/canvas/tunnel.zip
這篇文章的目的不是講解該程序的開發(fā)過程,而是讓你通過優(yōu)化已有的代碼來達到一個實時性的效果。
使用off-screen canvas(離屏畫布)來讀取圖片數(shù)據(jù)
我想講的第一點是怎樣使用canvas來讀取圖片數(shù)據(jù)。實際上,任何一個游戲都需要圖形來顯示游戲界面和背景。canvas有一個非常有用的畫圖方法:drawImage 。這個功能可以用來繪制游戲界面,通過它你可以定義起始和目的區(qū)域。
但是有時候光使用它是不夠的,比如你想要在源圖像上實現(xiàn)一些特效,或者源圖像不是一個簡單的位圖而是一個復雜的資源(如地圖)。
在這些情況下,你需要訪問到圖片的內(nèi)部數(shù)據(jù)。但是Image標簽無法讀到這些數(shù)據(jù),這時候就該canvas上場了。
事實上,每當你需要從圖片中讀取內(nèi)容的時候,你都可以使用off-screen canvas。也就是說,當你導入一張圖片的時候,你只需要將它渲染到canvas中(而不是DOM里),然后你就可以通過讀取canvas的像素點來獲得源圖片的內(nèi)容了(這個過程非常簡單)。
有關(guān)部分的代碼如下(2D tunnel effect中用來讀取隧道的紋理數(shù)據(jù)的):
- var loadTexture = function (name, then) {
- var texture = new Image();
- var textureData;
- var textureWidth;
- var textureHeight;
- var result = {};
- // on load
- texture.addEventListener(‘load’, function () {
- var textureCanvas = document.createElement(‘canvas’); // off-screen canvas
- // Setting the canvas to right size
- textureCanvas.width = this.width; //<– “this” is the image
- textureCanvas.height = this.height;
- result.width = this.width;
- result.height = this.height;
- var textureContext = textureCanvas.getContext(’2d’);
- textureContext.drawImage(this, 0, 0);
- result.data = textureContext.getImageData(0, 0, this.width, this.height).data;
- then();
- }, false);
- // Loading
- texture.src = name;
- return result;
- };
為了使用這些代碼,你還要保證隧道紋理圖片的導入是異步的,因此你需要傳遞then參數(shù),代碼如下:
- // Texture
- var texture = loadTexture(“soft.png”, function () {
- // Launching the render
- QueueNewFrame();
- });
使用硬件縮放功能
現(xiàn)代瀏覽器和Windows8都支持硬件加速的canvas,這意味著,你可以使用GPU來調(diào)整canvas里面內(nèi)容的尺寸。
在2D tunnel effect里,該算法要求處理canvas的每一個像素點,因此一個1024×768的canvas就得處理786432個像素點,并且為了達到流暢性的要求,每分鐘得處理60次,也就是說每分鐘要處理47185920個像素點!
很顯然,任何可以減少像素處理總數(shù)的方法都能帶來極大的性能提升。
又一次輪到canvas上場了!下面的代碼展示了怎樣使用硬件加速來調(diào)整canvas的內(nèi)部有效區(qū)域使之等于DOM對象的外部尺寸:
- // Setting hardware scaling
- canvas.width = 300;
- canvas.style.width = window.innerWidth + ‘px’;
- canvas.height = 200;
- canvas.style.height = window.innerHeight + ‘px’;
請注意DOM對象的尺寸(canvas.style.width、canvas.style.height)和canvas有效區(qū)域的尺寸(canvas.width、canvas.height)之間的差別。
當這兩個尺寸不同的時候,硬件會自動調(diào)整有效區(qū)域的大小,這是一件很棒的事:我們可以繪制低分辨率的圖形,然后通過GPU的調(diào)整使之符合DOM對象的大?。▽崿F(xiàn)一個漂亮免費的模糊濾鏡效果)。
在這種情況下,本來只有300×200 的圖像會被GPU擴展到跟你的窗口一樣大。
所有的現(xiàn)代瀏覽器都支持該功能,因此你可以放心的使用。
優(yōu)化rendering loop
制作游戲的時候,需要一個rendering loop用來繪制所有的組件(如背景,界面,分數(shù)等等)。這個loop是代碼的核心,因此必須充分優(yōu)化從而保證游戲的快速和流暢。
RequestAnimationFrame
HTML5一個有趣的功能是使用window.requestAnimationFrame. 代替window.setInterval 來創(chuàng)建定時器,從而實現(xiàn)每(1000/16) 毫秒渲染一次(以達到60fps),你可以通過requestAnimationFrame將該任務交給瀏覽器。調(diào)用這個方法表明你想要盡快的更新有關(guān)的圖形。
瀏覽器會將你的請求放入內(nèi)部渲染計劃,并使之與其本身的渲染及動畫代碼(CSS, transitions等等)同步。這個方法另一個有趣的地方在于如果窗口不顯示(minimized, fully occluded等等),你的代碼就不會被調(diào)用。
這能改善性能,因為瀏覽器可以優(yōu)化并發(fā)渲染從而提高動畫的流暢性(如你的渲染周期太長的話瀏覽器會將它與其本身的渲染及動畫周期同步)。
代碼很清晰(別忘了window前綴):
- var intervalID = -1;
- var QueueNewFrame = function () {
- if (window.requestAnimationFrame)
- window.requestAnimationFrame(renderingLoop);
- else if (window.msRequestAnimationFrame)
- window.msRequestAnimationFrame(renderingLoop);
- else if (window.webkitRequestAnimationFrame)
- window.webkitRequestAnimationFrame(renderingLoop);
- else if (window.mozRequestAnimationFrame)
- window.mozRequestAnimationFrame(renderingLoop);
- else if (window.oRequestAnimationFrame)
- window.oRequestAnimationFrame(renderingLoop);
- else {
- QueueNewFrame = function () {
- };
- intervalID = window.setInterval(renderingLoop, 16.7);
- }
- };
你只需要在rendering loop的結(jié)尾調(diào)用這個函數(shù)并在接下來的代碼段里進行注冊即可:
- var renderingLoop = function () {
- …
- QueueNewFrame();
- };
訪問DOM(Document Object Model)
為了優(yōu)化rendering loop,你必須遵循這條黃金準則:DO NOT ACCESS THE DOM(不要訪問DOM)。即使現(xiàn)代瀏覽器在這方面做了優(yōu)化,讀取DOM對象屬性還是太慢了。
例如,在我的代碼里,我使用了Internet Explorer 10 profiler(IE10的提供的分析器,按F12快捷鍵打開),顯示的結(jié)果如下:
如圖所示,訪問canvas的寬度和高度會花費大量的時間!
原始代碼如下:
- var renderingLoop = function () {
- for (var y = -canvas.height / 2; y < canvas.height / 2; y++) {
- for (var x = -canvas.width / 2; x < canvas.width / 2; x++) {
- …
- }
- }
- };
你可以通過兩個變量來預先獲取canvas.width 和 canvas.height,然后在后面用變量來代替這些屬性值:
- var renderingLoop = function () {
- var index = 0;
- for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) {
- for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) {
- …
- }
- }
- };
是不是非常簡單?雖然有時候很難注意到這些細節(jié),但是請相信我這絕對是件值得的事。
預先計算
通過分析器得知,Math.atan2函數(shù)比較慢。事實上,該操作并不需要在運行時計算,你可以在JavaScript里面加一些代碼預先計算出結(jié)果。
一般來說,預先計算一些較為費時的代碼是一種好方法。這里,在運行rendering loop之前,我已經(jīng)計算好了Math.atan2:
- // precompute arctangent
- var atans = [];
- var index = 0;
- for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) {
- for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) {
- atans[index++] = Math.atan2(y, x) / Math.PI;
- }
- }
atans數(shù)組的使用明顯的提高了性能。
避免使用Math.round, Math.floor 以及 parseInt
最后一點是parseInt的使用:
當你使用canvas時,你需要使用一些整數(shù)坐標。實際上,所有的計算都采用的浮點數(shù),你需要將它們轉(zhuǎn)換成整形。
JavaScript 提供了 Math.round, Math.floor 甚至 parseInt 來轉(zhuǎn)換數(shù)值。但是這個方法做了一些額外的工作(比如檢測數(shù)據(jù)是不是有效的數(shù)值,parseInt 甚至先將參數(shù)轉(zhuǎn)換成了字符串!)。在我的rendering loop里面,我需要一個更快的轉(zhuǎn)換方法。
在我的舊的匯編代碼里面,我使用了一個小技巧:將數(shù)據(jù)右移0位。這會將浮點數(shù)從浮點寄存器移到整數(shù)寄存器,并且是通過硬件轉(zhuǎn)換的。右移0位不會改變數(shù)據(jù)的值,但是會以整數(shù)形式返回。
原始代碼如下:
u = parseInt((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u);
以下是改進后的代碼:
u = ((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u)>> 0;
當然該方法要求你的數(shù)據(jù)是合法的數(shù)值。
最終結(jié)果
實現(xiàn)了上述優(yōu)化之后得到的結(jié)果如下:
你可以看到做了這些基本的功能優(yōu)化之后的表現(xiàn)。
原始的隧道渲染(沒做任何優(yōu)化):
做完上述優(yōu)化之后:
下表展示了每項優(yōu)化對幀速率的影響(在我的機器上):
更進一步
記住這些關(guān)鍵技巧,你就可以為現(xiàn)代瀏覽器或Windows8制作實時、快速、流暢的游戲了。