使用JavaScript和Canvas開發(fā)游戲之使用Canvas
3、通過Canvas元素實現(xiàn)高級圖像操作
http://www.brighthub.com/internet/web-development/articles/39509.aspx
這篇文章將帶領(lǐng)大家學(xué)習(xí)使用JavaScript和Canvas元素操作圖像了幾種不同的方式,這些方式在Canvas元素出現(xiàn)之前是不可能的事兒。
上一篇文章演示了如何利用Canvas實現(xiàn)一個基本的圖像動畫。那個例子很簡單,同樣的效果通過修改IMG或DIV等標(biāo)準(zhǔn)HTML元素的一些屬性,照樣也可以輕易實現(xiàn)。下面我們就來演示一下畫布元素的高級應(yīng)用,展示一下它的真正威力。
首先,還是準(zhǔn)備一個HTML頁面。
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
- <html lang="en">
- <head>
- <title>JavaScript Platformer 2</title>
- <script type="text/javascript" src="jsplatformer2.js"></script>
- <style type="text/css">
- body { font-family: Arial,Helvetica,sans-serif;}
- </style>
- </head>
- <body>
- <p>
- <a href="http://www.brighthub.com/internet/web-development/articles/38364.aspx">
- Game Development with Javascript and the canvas element
- </a>
- </p>
- <canvas id="canvas" width="600" height="400">
- <p>Your browser does not support the canvas element.</p>
- </canvas>
- <br />
- <button onclick="currentFunction=alpha;">Change Alpha</button>
- <button onclick="currentFunction=shear;">Shear</button>
- <button onclick="currentFunction=scale;">Scale</button>
- <button onclick="currentFunction=rotate;">Rotate</button>
- </body>
- </html>
與上個一例子的HTML頁面相比,唯一的區(qū)別就是添加了一些按鈕。單擊這些按鈕,就會設(shè)置currentFunction變量(稍后介紹)的值,用以改變在渲染循環(huán)中運行的函數(shù)。
以下是 jsplatformer2.js 的代碼。
- // 每秒多少幀
- const FPS = 30;
- const SECONDSBETWEENFRAMES = 1 / FPS;
- const HALFIMAGEDIMENSION = 75;
- const HALFCANVASWIDTH = 300;
- const HALFCANVASHEIGHT = 200;
- var image = new Image();
- image.src = "jsplatformer2-smiley.jpg"; //還是第一個例子中的圖像
- var canvas = null;
- var context2D = null;
- var currentFunction = null;
- var currentTime = 0;
- var sineWave = 0;
- window.onload = init;
- function init()
- {
- canvas = document.getElementById('canvas');
- context2D = canvas.getContext('2d');
- setInterval(draw, SECONDSBETWEENFRAMES * 1000);
- currentFunction = scale;
- }
- function draw()
- {
- currentTime += SECONDSBETWEENFRAMES;
- sineWave = (Math.sin(currentTime) + 1) / 2;
- context2D.clearRect(0, 0, canvas.width, canvas.height);
- context2D.save();
- context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);
- currentFunction();
- context2D.drawImage(image, 0, 0);
- context2D.restore();
- }
- function alpha()
- {
- context2D.globalAlpha = sineWave;
- }
- function shear()
- {
- context2D.transform(1, 0, (sineWave - 0.5), 1, 0, 0);
- }
- function scale()
- {
- context2D.translate(HALFIMAGEDIMENSION * (1 - sineWave), HALFIMAGEDIMENSION * (1 - sineWave));
- context2D.scale(sineWave, sineWave);
- }
- function rotate()
- {
- context2D.translate(HALFIMAGEDIMENSION, HALFIMAGEDIMENSION);
- context2D.rotate(sineWave * Math.PI * 2);
- context2D.translate(-HALFIMAGEDIMENSION, -HALFIMAGEDIMENSION);
- }
跟前面一樣,這個JavaScript文件先定義了一些全局變量。
◆ FPS:每秒多少幀
◆ SECONDSBETWEENFRAMES:兩幀之間間隔的秒數(shù)(FPS的倒數(shù))
◆ HALFIMAGEDIMENSION:要繪制圖像的寬度/高度的一半,用于把圖像定位到畫布的中心點
◆ HALFCANVASWIDTH:畫布寬度的一半,用于配合HALFIMAGEDIMENSION使用,以便在畫布上居中圖像
◆ HALFCANVASHEIGHT:畫布高度的一半,用于配合HALFIMAGEDIMENSION使用,以便在畫布上居中圖像
◆ currentFunction:渲染循環(huán)(參見上一篇文章)中運行的函數(shù)
◆ currentTime:應(yīng)用已經(jīng)運行了多少秒
◆ sineWave:0到1之間的一個值,用于控制圖像的運動
◆ image:要在畫布上繪制的圖像
◆ canvas:畫布元素的引用
◆ context2D:畫布元素的2D上下文的引用
然后,跟前面一樣,要設(shè)置在window的onload事件發(fā)生時立即調(diào)用init函數(shù)(關(guān)于init函數(shù)的介紹,請參見上一篇文章)。
draw函數(shù)
下面來看一看draw函數(shù):
- function draw()
- {
- currentTime += SECONDSBETWEENFRAMES;
- sineWave = (Math.sin(currentTime) + 1) / 2;
- context2D.clearRect(0, 0, canvas.width, canvas.height);
- context2D.save();
- context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);
- currentFunction();
- context2D.drawImage(image, 0, 0);
- context2D.restore();
- }
這個例子要演示4種效果:修改alpha值(透明度),以及縮放、旋轉(zhuǎn)和切變圖像。為了展示這些效果,需要基于某一范圍內(nèi)的值來應(yīng)用變化。變量sineWave就用來定義這個范圍值的基準(zhǔn)。
標(biāo)準(zhǔn)的正弦函數(shù)能夠在-1到1之間產(chǎn)生非常完美的波形圖。首先,我們通過遞增currentTime變量來反映動畫已經(jīng)運行了多長時間,然后再利用這個值在正弦曲線上找到一個點。給正弦函數(shù)返回的值(從-1到1)先加1再除以2,就可以把它們轉(zhuǎn)換成0到1這個范圍內(nèi)的值。
- currentTime += SECONDSBETWEENFRAMES;
- sineWave = (Math.sin(currentTime) + 1) / 2;
然后,調(diào)用clearRect方法清空畫布,以便為后面的繪圖準(zhǔn)備一個干凈的版面。
- context2D.clearRect(0, 0, canvas.width, canvas.height);
應(yīng)用到畫布上面的效果是可以累積的,因而就可以利用幾個簡單的函數(shù)來“組合”出效果來。例如,在向屏幕上繪制之前,可能會有一艘飛船需要旋轉(zhuǎn)、變換和縮放。因為所有效果都對畫布起作用,所以這些效果會應(yīng)用到將被繪制在屏幕上的所有對象,而不僅僅是某一幅圖像或某一個形狀(比如一艘飛船)。
其中,save和restore函數(shù)為應(yīng)用這些累積的效果提供了一種簡單的機制,可以將應(yīng)用了這些效果的圖像或圖形繪制到畫布上,然后“撤銷”這些改變。后臺的操作是什么呢?save函數(shù)把當(dāng)前的繪制狀態(tài)推進(jìn)棧里,而restore函數(shù)則把最后一個狀態(tài)彈出棧。還拿前面提到的飛船為例,需要執(zhí)行下列操作:
◆ 調(diào)用save函數(shù)(保存當(dāng)前的繪制狀態(tài))
◆ 旋轉(zhuǎn)、變換和縮放上下文
◆ 繪制飛船
調(diào)用restore函數(shù),移除自上一次調(diào)用save方法以來所添加的任何效果,也就是撤銷之前的變化
在這里,我們就是要組合起來使用這兩個方法。首先,在把任何效果應(yīng)用到畫布之前,先保存繪制狀態(tài)。
- context2D.save();
保存了繪制狀態(tài)之后,就該應(yīng)用目標(biāo)效果了。為此,首先調(diào)用translate函數(shù),從而將隨后要繪制的圖像在畫布上居中。
- context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);
接下來,調(diào)用由變量currentFunction引用的函數(shù)。正是這些被引用的函數(shù),是讓圖像發(fā)生alpha(透明度)變化以及縮放、旋轉(zhuǎn)和切變的關(guān)鍵。這些函數(shù)我們稍后再介紹。
- currentFunction();
為圖像應(yīng)用完效果之后,就可以把它繪制到畫布上面了。所以,接下來就是調(diào)用drawImage來繪圖。
- context2D.drawImage(image, 0, 0);
最后,再調(diào)用restore函數(shù),把自調(diào)用save函數(shù)以來應(yīng)用的所有效果從畫布上移除。
- context2D.restore();
alpha函數(shù)
- function alpha()
- {
- context2D.globalAlpha = sineWave;
- }
通過修改上下文對象的globalAlpha屬性,所有后續(xù)繪制操作的透明度都會被修改。將globalAlpha設(shè)置為0,意味著被繪制的任何對象都將完全透明,而將這個屬性設(shè)置為1,則意味著任何繪制操作都會保持原有的透明度級別。在此,我們通過修改這個globalAlpha屬性,可以實現(xiàn)笑臉的淡入和淡出效果。
shear函數(shù)
- function shear()
- {
- context2D.transform(1, 0, (sineWave - 0.5), 1, 0, 0);
- }
切變操作是通過transform函數(shù)向畫布應(yīng)用一個矩陣來實現(xiàn)的。變換矩陣本身就是一個值得研究的主題,但對我們來說,如果不想理解背后的數(shù)學(xué)原理,可以在網(wǎng)上找到很多標(biāo)準(zhǔn)的2D變換矩陣(http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics),直接使用transform函數(shù)來應(yīng)用它們即可。所謂切變,其實就是把圖像的頂部或底部推到一邊。
scale函數(shù)
- function scale()
- {
- context2D.translate(HALFIMAGEDIMENSION * (1 - sineWave), HALFIMAGEDIMENSION * (1 - sineWave));
- 56
- context2D.scale(sineWave, sineWave);
- }
顧名思義,scale(縮放)函數(shù)修改的是圖像的大小。但在此之前,我們還調(diào)用了一次transalte函數(shù)。這是為了讓縮放后的圖像在畫布上居中。如果你把這行代碼注釋掉,就會發(fā)現(xiàn)圖像會從左上角向右下角膨脹。調(diào)用translate函數(shù)就是為抵消其圓心的位移,讓圖像始終居中。
rotate函數(shù)
- function rotate()
- {
- context2D.translate(HALFIMAGEDIMENSION, HALFIMAGEDIMENSION);
- context2D.rotate(sineWave * Math.PI * 2);
- context2D.translate(-HALFIMAGEDIMENSION, -HALFIMAGEDIMENSION);
- }
與scale函數(shù)類似,rotate(旋轉(zhuǎn))函數(shù)的作用也正如其名:旋轉(zhuǎn)圖像。與scale函數(shù)同樣類似的是,這里也額外調(diào)用了translate函數(shù)以確保圖像圍繞中心點而不是左上角旋轉(zhuǎn)。建議大家把對translate函數(shù)的調(diào)用注釋掉,自己看一看結(jié)果有什么不同。
剛剛我們看到了使用畫布元素實現(xiàn)的4種也還算簡單的效果,這些效果使用標(biāo)準(zhǔn)的HTML元素幾乎是不可能做到的。其中,有的效果可以使用scale和rotate等內(nèi)置函數(shù)來實現(xiàn),而使用transform函數(shù)則可以完成大量的圖像操作(切變只是其中之一)。
看看Demo吧。http://webdemos.sourceforge.net/jsplatformer2/jsplatformer2.html
原文作者:Matthew Casperson 原文鏈接: Game Development with JavaScript and the Canvas element
譯文作者:李松峰 譯文鏈接:http://www.cn-cuckoo.com/2011/08/10/game-development-with-javascript-and-the-canvas-element-2554.html
【編輯推薦】