使用JavaScript和Canvas開發遊戲(二)

李鬆峰發表於2011-08-26

原文作者:Matthew Casperson • 編輯:Michele McDonough
原文連結: Game Development with JavaScript and the Canvas element

1、認識一下Canvas
2、在Canvas上繪圖
3、通過Canvas元素實現高階影像操作
4、通過Canvas實現視差滾動
5、寫一個遊戲框架(一)
6、寫一個遊戲框架(二)
7、動畫
8、JavaScript鍵盤輸入
9、綜合運用
10、定義級別
11、跳躍與墜落
12、新增道具
13、載入資源
14、新增主選單

3、通過Canvas元素實現高階影像操作

http://www.brighthub.com/internet/web-development/articles/39509.aspx

這篇文章將帶領大家學習使用JavaScript和Canvas元素操作影像了幾種不同的方式,這些方式在Canvas元素出現之前是不可能的事兒。

上一篇文章演示瞭如何利用Canvas實現一個基本的影像動畫。那個例子很簡單,同樣的效果通過修改IMG或DIV等標準HTML元素的一些屬性,照樣也可以輕易實現。下面我們就來演示一下畫布元素的高階應用,展示一下它的真正威力。

首先,還是準備一個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頁面相比,唯一的區別就是新增了一些按鈕。單擊這些按鈕,就會設定currentFunction變數(稍後介紹)的值,用以改變在渲染迴圈中執行的函式。

以下是 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:兩幀之間間隔的秒數(FPS的倒數)
  • HALFIMAGEDIMENSION:要繪製影像的寬度/高度的一半,用於把影像定位到畫布的中心點
  • HALFCANVASWIDTH:畫布寬度的一半,用於配合HALFIMAGEDIMENSION使用,以便在畫布上居中影像
  • HALFCANVASHEIGHT:畫布高度的一半,用於配合HALFIMAGEDIMENSION使用,以便在畫布上居中影像
  • currentFunction:渲染迴圈(參見上一篇文章)中執行的函式
  • currentTime:應用已經執行了多少秒
  • sineWave:0到1之間的一個值,用於控制影像的運動
  • image:要在畫布上繪製的影像
  • canvas:畫布元素的引用
  • context2D:畫布元素的2D上下文的引用

然後,跟前面一樣,要設定在window的onload事件發生時立即呼叫init函式(關於init函式的介紹,請參見上一篇文章)。

draw函式

下面來看一看draw函式:

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值(透明度),以及縮放、旋轉和切變影像。為了展示這些效果,需要基於某一範圍內的值來應用變化。變數sineWave就用來定義這個範圍值的基準。

標準的正弦函式能夠在-1到1之間產生非常完美的波形圖。首先,我們通過遞增currentTime變數來反映動畫已經執行了多長時間,然後再利用這個值在正弦曲線上找到一個點。給正弦函式返回的值(從-1到1)先加1再除以2,就可以把它們轉換成0到1這個範圍內的值。

currentTime += SECONDSBETWEENFRAMES;
sineWave = (Math.sin(currentTime) + 1) / 2;

然後,呼叫clearRect方法清空畫布,以便為後面的繪圖準備一個乾淨的版面。

context2D.clearRect(0, 0, canvas.width, canvas.height);

應用到畫布上面的效果是可以累積的,因而就可以利用幾個簡單的函式來“組合”出效果來。例如,在向螢幕上繪製之前,可能會有一艘飛船需要旋轉、變換和縮放。因為所有效果都對畫布起作用,所以這些效果會應用到將被繪製在螢幕上的所有物件,而不僅僅是某一幅影像或某一個形狀(比如一艘飛船)。

其中,save和restore函式為應用這些累積的效果提供了一種簡單的機制,可以將應用了這些效果的影像或圖形繪製到畫布上,然後“撤銷”這些改變。後臺的操作是什麼呢?save函式把當前的繪製狀態推進棧裡,而restore函式則把最後一個狀態彈出棧。還拿前面提到的飛船為例,需要執行下列操作:

  • 呼叫save函式(儲存當前的繪製狀態)
  • 旋轉、變換和縮放上下文
  • 繪製飛船
  • 呼叫restore函式,移除自上一次呼叫save方法以來所新增的任何效果,也就是撤銷之前的變化

在這裡,我們就是要組合起來使用這兩個方法。首先,在把任何效果應用到畫布之前,先儲存繪製狀態。

context2D.save();

儲存了繪製狀態之後,就該應用目標效果了。為此,首先呼叫translate函式,從而將隨後要繪製的影像在畫布上居中。

context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);

接下來,呼叫由變數currentFunction引用的函式。正是這些被引用的函式,是讓影像發生alpha(透明度)變化以及縮放、旋轉和切變的關鍵。這些函式我們稍後再介紹。

currentFunction();

為影像應用完效果之後,就可以把它繪製到畫布上面了。所以,接下來就是呼叫drawImage來繪圖。

context2D.drawImage(image, 0, 0);

最後,再呼叫restore函式,把自呼叫save函式以來應用的所有效果從畫布上移除。

context2D.restore();

alpha函式

function alpha()
{
    context2D.globalAlpha = sineWave;
}

通過修改上下文物件的globalAlpha屬性,所有後續繪製操作的透明度都會被修改。將globalAlpha設定為0,意味著被繪製的任何物件都將完全透明,而將這個屬性設定為1,則意味著任何繪製操作都會保持原有的透明度級別。在此,我們通過修改這個globalAlpha屬性,可以實現笑臉的淡入和淡出效果。

shear函式

function shear()
{
    context2D.transform(1, 0, (sineWave - 0.5), 1, 0, 0);
}

[/code] 切變操作是通過transform函式向畫布應用一個矩陣來實現的。變換矩陣本身就是一個值得研究的主題,但對我們來說,如果不想理解背後的數學原理,可以在網上找到很多標準的2D變換矩陣(http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics),直接使用transform函式來應用它們即可。所謂切變,其實就是把影像的頂部或底部推到一邊。

scale函式

 function scale()
 {
     context2D.translate(HALFIMAGEDIMENSION * (1 - sineWave), HALFIMAGEDIMENSION * (1 - sineWave));
    context2D.scale(sineWave, sineWave);
 }

顧名思義,scale(縮放)函式修改的是影像的大小。但在此之前,我們還呼叫了一次transalte函式。這是為了讓縮放後的影像在畫布上居中。如果你把這行程式碼註釋掉,就會發現影像會從左上角向右下角膨脹。呼叫translate函式就是為抵消其圓心的位移,讓影像始終居中。

rotate函式

function rotate()
{
 context2D.translate(HALFIMAGEDIMENSION, HALFIMAGEDIMENSION);
 context2D.rotate(sineWave * Math.PI * 2);
 context2D.translate(-HALFIMAGEDIMENSION, -HALFIMAGEDIMENSION);
}

與scale函式類似,rotate(旋轉)函式的作用也正如其名:旋轉影像。與scale函式同樣類似的是,這裡也額外呼叫了translate函式以確保影像圍繞中心點而不是左上角旋轉。建議大家把對translate函式的呼叫註釋掉,自己看一看結果有什麼不同。

剛剛我們看到了使用畫布元素實現的4種也還算簡單的效果,這些效果使用標準的HTML元素幾乎是不可能做到的。其中,有的效果可以使用scale和rotate等內建函式來實現,而使用transform函式則可以完成大量的影像操作(切變只是其中之一)。

看看Demo吧。http://webdemos.sourceforge.net/jsplatformer2/jsplatformer2.html

為之漫筆 最後編輯於: 2011/08/12 @ 06:29

相關文章