自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用JavaScript和Canvas寫一個游戲框架

開發(fā) 前端
下面我們要介紹的JavaScript代碼使用面向?qū)ο蟮姆绞絹砭帉?。對于沒有編寫過多少JavaScript代碼的人來說,恐怕第一眼看到它們會覺得有點奇怪。

4、寫一個游戲框架(一)

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

在知道了如何使用畫布元素之后,接下來我教大家寫一個框架,有了這個框架,我們就可以把它作為基礎(chǔ)來創(chuàng)建游戲。在這第一部分,我們會介紹前兩個文件/類。

編寫代碼之前,我們先來看一看隨后幾篇文章將致力于創(chuàng)建的示例Demo。表面上看起來,這個Demo跟第二篇文章里的那個沒啥區(qū)別,但如果你看看后臺(查看網(wǎng)頁源代碼)就會發(fā)現(xiàn),為了更方便地創(chuàng)建這個最終效果,一個凝聚不少心血的基礎(chǔ)框架已經(jīng)寫好了。

 

 

下面我們要介紹的JavaScript代碼使用面向?qū)ο蟮姆绞絹砭帉?。對于沒有編寫過多少JavaScript代碼的人來說,恐怕第一眼看到它們會覺得有點奇怪。如果你真的不太熟悉JavaScript的面向?qū)ο缶幊蹋ㄗh通過Mozilla Developer Network的這個教程https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript來補補課。這篇教程里解釋了我們稍后會用到的一些編程技術(shù)。

從設(shè)計思想上來看,這個框架可以分成兩部分:與底層的2D引擎交互的類(用于操作畫布、控制渲染循環(huán)、處理輸入等的代碼)和用來創(chuàng)建對象以便構(gòu)成游戲的類。前者可以歸為引擎類,后者可以歸為應用類。由于應用類要構(gòu)建于引擎類之上,所以我們需要先來創(chuàng)建引擎類。

Main.js

如果你研究了前面例子中的代碼,就會發(fā)現(xiàn)Main.js文件中包含了不少代碼。

  1.  
  2. /** 每秒多少幀  
  3.     @type Number  
  4. */  
  5. var FPS = 30;  
  6. /** 兩幀間間隔的秒數(shù)  
  7.     @type Number  
  8. */  
  9. var SECONDS_BETWEEN_FRAMES = 1 / FPS;  
  10. /** GameObjectManager 實例的全局引用  
  11.     @type GameObjectManager  
  12. */  
  13. var g_GameObjectManager = null;  
  14. /** 應用中用到的圖像  
  15.     @type Image  
  16. */  
  17. var g_image = new Image();  
  18. g_image.src = "jsplatformer3-smiley.jpg";  
  19.  
  20. // 將應用的入口設(shè)置為init函數(shù)  
  21. window.onload = init;  
  22.  
  23. /**  
  24.     應用的入口  
  25. */  
  26. function init()  
  27. {  
  28.     new GameObjectManager().startupGameObjectManager();  
  29. }  

首先是定義全局變量的代碼。然后,跟以前一樣,當頁面加載完畢后立即運行init函數(shù)。在init函數(shù)里,創(chuàng)建GameObjectManager類的實例。

這里在GameObjectManager類的實例上調(diào)用了startupGameObjectManager函數(shù)。這篇文章以及后面的幾篇文章還將多次提到幾個命名上具有startupClassName形式的函數(shù)。這些函數(shù)實際上充當了各自類的構(gòu)造函數(shù),這樣做有兩個原因。

首先,JavaScript不支持函數(shù)重載(至少不容易實現(xiàn))。如果你想讓一個類有多個構(gòu)造函數(shù),那么這就成了問題。而通過把構(gòu)造工作分配給另一組函數(shù)(如startupClassName1、startupClassName2),就可以比較容易地定義構(gòu)造類的不同方式了。

第二個原因(很大程度上也是個人的問題)是我經(jīng)常會在構(gòu)造函數(shù)中引用尚未定義的變量。這可能是我使用C++、Java和C#這些語言落下的毛病,在這些語言里,類變量在源代碼中的位置對其在構(gòu)造函數(shù)中的可見性沒有影響。拿下面這個C#類為例:

  1. class Test  
  2. {  
  3.     public void Test() {this.a = 5;}  
  4.     public int a;  
  5. }  

這些代碼是合乎語法的,可以正常工作。下面再看看JavaScript中一個相同的例子:

  1. function Test()  
  2. {  
  3.     this.a = 5;  
  4.     var a;  
  5. }  

這段代碼的問題在于,局部變量a在我們把數(shù)值5賦給它的時候還不存在。只有運行到var a;這一行,變量a才存在。盡管這個例子有點故意編排的意味,但的確能夠說明我所遇到的問題。通過把類的創(chuàng)建放到一個類似startupClassName這樣的函數(shù)中完成,并且在構(gòu)造函數(shù)中定義(但不初始化)局部變量,然后當我在這些構(gòu)建函數(shù)中引用相應的局部變量時,就能夠確保它們一定是存在的。

#p#

GameObjectManager.js

  1.  
  2. /**  
  3.     管理游戲中所有對象的管理器  
  4.     @class  
  5. */  
  6. function GameObjectManager()  
  7. {  
  8.     /** 保存游戲中對象的數(shù)組  
  9.         @type Arary  
  10.     */  
  11.     this.gameObjects = new Array();  
  12.     /** 上一次幀被渲染的時間  
  13.         @type Date  
  14.     */  
  15.     this.lastFrame = new Date().getTime();  
  16.     /** x軸的全局滾動值  
  17.         @type Number  
  18.     */  
  19.     this.xScroll = 0;  
  20.     /** y軸的全局滾動值  
  21.         @type Number  
  22.     */  
  23.     this.yScroll = 0;  
  24.     /** 對ApplicationManager實例的引用  
  25.         @type ApplicationManager  
  26.     */  
  27.     this.applicationManager = null;  
  28.     /** 對畫布元素的引用  
  29.         @type HTMLCanvasElement  
  30.     */  
  31.     this.canvas = null;  
  32.     /** 對畫布元素2D上下文的引用  
  33.         @type CanvasRenderingContext2D  
  34.     */  
  35.     this.context2D = null;  
  36.     /** 對內(nèi)存中用作后臺緩沖區(qū)的畫布的引用  
  37.         @type HTMLCanvasElement  
  38.     */  
  39.     this.backBuffer = null;  
  40.     /** 對后臺緩沖畫布的2D上下文的引用  
  41.         @type CanvasRenderingContext2D  
  42.     */  
  43.     this.backBufferContext2D = null;  
  44.  
  45.     /**  
  46.         初始化這個對象  
  47.         @return A reference to the initialised object  
  48.     */  
  49.     this.startupGameObjectManager = function()  
  50.     {  
  51.         // 設(shè)置引用this對象的全局指針  
  52.         g_GameObjectManager = this;  
  53.  
  54.         // 取得畫布元素及其2D上下文的引用  
  55.         this.canvas = document.getElementById('canvas');  
  56.         thisthis.context2D = this.canvas.getContext('2d');  
  57.         this.backBuffer = document.createElement('canvas');  
  58.         thisthis.backBuffer.width = this.canvas.width;  
  59.         thisthis.backBuffer.height = this.canvas.height;  
  60.         thisthis.backBufferContext2D = this.backBuffer.getContext('2d');  
  61.  
  62.         // 創(chuàng)建一個新的ApplicationManager  
  63.         this.applicationManager = new ApplicationManager().startupApplicationManager();  
  64.  
  65.         // 使用setInterval來調(diào)用draw函數(shù)  
  66.         setInterval(function(){g_GameObjectManager.draw();}, SECONDS_BETWEEN_FRAMES);  
  67.  
  68.         return this;  
  69.     }  
  70.  
  71.     /**  
  72.         渲染循環(huán)  
  73.     */  
  74.     this.draw = function ()  
  75.     {  
  76.         // 計算從上一幀到現(xiàn)在的時間  
  77.         var thisFrame = new Date().getTime();  
  78.         var dt = (thisFrame - this.lastFrame)/1000;  
  79.         this.lastFrame = thisFrame;  
  80.  
  81.         // 清理繪制上下文  
  82.         this.backBufferContext2D.clearRect(0, 0, this.backBuffer.width, this.backBuffer.height);  
  83.         this.context2D.clearRect(0, 0, this.canvas.width, this.canvas.height);  
  84.  
  85.         // 首先更新所有游戲?qū)ο? 
  86.         for (x in this.gameObjects)  
  87.         {  
  88.             if (this.gameObjects[x].update)  
  89.             {  
  90.                 this.gameObjects[x].update(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  91.             }  
  92.         }  
  93.  
  94.         // 然后繪制所有游戲?qū)ο? 
  95.         for (x in this.gameObjects)  
  96.         {  
  97.             if (this.gameObjects[x].draw)  
  98.             {  
  99.                 this.gameObjects[x].draw(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  100.             }  
  101.         }  
  102.  
  103.         // 將后臺緩沖復制到當前顯示的畫布  
  104.         this.context2D.drawImage(this.backBuffer, 0, 0);  
  105.     };  
  106.  
  107.     /**  
  108.         向gameObjects集合中添加一個GameObject  
  109.         @param gameObject The object to add  
  110.     */  
  111.     this.addGameObject = function(gameObject)  
  112.     {  
  113.         this.gameObjects.push(gameObject);  
  114.         this.gameObjects.sort(function(a,b){return a.zOrder - b.zOrder;})  
  115.     };  
  116.  
  117.     /**  
  118.         從gameObjects集合中刪除一個GameObject  
  119.         @param gameObject The object to remove  
  120.     */  
  121.     this.removeGameObject = function(gameObject)  
  122.     {  
  123.         this.gameObjects.removeObject(gameObject);  
  124.     }  
  125. }  

首先看一看GameObjectManager類。GameObjectManager是一個引擎類,用于管理畫布的繪制操作,還負責分派GameObject類(下一篇文章里介紹)的事件。

GameObjectManager類的startupGameObjectManager函數(shù)的代碼如下:

  1.  
  2. /**  
  3.     初始化這個對象  
  4.     @return A reference to the initialised object  
  5. */  
  6. this.startupGameObjectManager = function()  
  7. {  
  8.     // 設(shè)置引用this對象的全局指針  
  9.     g_GameObjectManager = this;  
  10.  
  11.     // 取得畫布元素及其2D上下文的引用  
  12.     this.canvas = document.getElementById('canvas');  
  13.     thisthis.context2D = this.canvas.getContext('2d');  
  14.     this.backBuffer = document.createElement('canvas');  
  15.     thisthis.backBuffer.width = this.canvas.width;  
  16.     thisthis.backBuffer.height = this.canvas.height;  
  17.     thisthis.backBufferContext2D = this.backBuffer.getContext('2d');  
  18.  
  19.     // 創(chuàng)建一個新的ApplicationManager  
  20.     this.applicationManager = new ApplicationManager().startupApplicationManager();  
  21.  
  22.     // 使用setInterval來調(diào)用draw函數(shù)  
  23.     setInterval(function(){g_GameObjectManager.draw();}, SECONDS_BETWEEN_FRAMES);  
  24.  
  25.     return this;  
  26. }  

前面已經(jīng)說過,我們會把每個類的初始化工作放在startupClassName函數(shù)中來做。因此,GameObjectManager類將由startupGameObjectManager函數(shù)進行初始化。

而引用這個GameObjectManager實例的全局變量g_GameObjectManager經(jīng)過重新賦值,指向了這個新實例。

  1. // 設(shè)置引用this對象的全局指針  
  2. g_GameObjectManager = this;  

對畫布元素及其繪圖上下文的引用也同樣保存起來:

  1. // 取得畫布元素及其2D上下文的引用  
  2. this.canvas = document.getElementById('canvas');  
  3. thisthis.context2D = this.canvas.getContext('2d');  

在前面的例子中,所有繪圖操作都是直接在畫布元素上完成的。這種風格的渲染一般稱為單緩沖渲染。在此,我們要使用一種叫做雙緩沖渲染的技術(shù):任意游戲?qū)ο蟮乃欣L制操作,都將在一個內(nèi)存中的附加畫布元素(后臺緩沖)上完成,完成后再通過一次操作把它復制到網(wǎng)頁上的畫布元素(前臺緩沖)。

雙緩沖技術(shù)(http://www.brighthub.com/internet/web-development/articles/11012.aspx)通常用于減少畫面抖動。我自己在測試的時候從沒發(fā)現(xiàn)直接向畫布元素上繪制有抖動現(xiàn)象,但我在網(wǎng)上的確聽別人念叨過,使用單緩沖渲染會導致某些瀏覽器在渲染時發(fā)生抖動。

不管怎么說,雙緩沖還是能夠避免最終用戶看到每個游戲?qū)ο笤诶L制過程中最后一幀的組合過程。在通過JavaScript執(zhí)行某些復雜繪制操作時(例如透明度、反鋸齒及可編程紋理),這種情況是完全可能發(fā)生的。

使用附加緩沖技術(shù)占用的內(nèi)存非常少,多執(zhí)行一次圖像復制操作(把后臺緩沖繪制到前臺緩沖)導致的性能損失也可以忽略不計,可以說實現(xiàn)雙緩沖系統(tǒng)沒有什么缺點。

如果將在HTML頁面中定義的畫布元素作為前臺緩沖,那就需要再創(chuàng)建一個畫布來充當后臺緩沖。為此,我們使用了document.createElement函數(shù)在內(nèi)存里創(chuàng)建了一個畫布元素,把它用作后臺緩沖。

  1. this.backBuffer = document.createElement('canvas');  
  2. thisthis.backBuffer.width = this.canvas.width;  
  3. thisthis.backBuffer.height = this.canvas.height;  
  4. thisthis.backBufferContext2D = this.backBuffer.getContext('2d');  

接下來,我們創(chuàng)建了ApplicationManager類的一個新實例,并調(diào)用startupApplicationManager來初始化它。這個ApplicationManager類將在下一篇文章中介紹。

  1. // 創(chuàng)建一個新的ApplicationManager  
  2. this.applicationManager = new ApplicationManager().startupApplicationManager();  

最后,使用setInterval函數(shù)重復調(diào)用draw函數(shù),這個函數(shù)是渲染循環(huán)的核心所在。

  1. // 使用setInterval來調(diào)用draw函數(shù)  
  2. setInterval(function(){g_GameObjectManager.draw();}, SECONDS_BETWEEN_FRAMES);  

#p#

下面來看一看draw函數(shù)。

  1. /**  
  2.     渲染循環(huán)  
  3. */  
  4. this.draw = function ()  
  5. {  
  6.     // 計算從上一幀到現(xiàn)在的時間  
  7.     var thisFrame = new Date().getTime();  
  8.     var dt = (thisFrame - this.lastFrame)/1000;  
  9.     this.lastFrame = thisFrame;  
  10.  
  11.     // 清理繪制上下文  
  12.     this.backBufferContext2D.clearRect(0, 0, this.backBuffer.width, this.backBuffer.height);  
  13.     this.context2D.clearRect(0, 0, this.canvas.width, this.canvas.height);  
  14.  
  15.     // 首先更新所有游戲?qū)ο? 
  16.     for (x in this.gameObjects)  
  17.     {  
  18.         if (this.gameObjects[x].update)  
  19.         {  
  20.             this.gameObjects[x].update(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  21.         }  
  22.     }  
  23.  
  24.     // 然后繪制所有游戲?qū)ο? 
  25.     for (x in this.gameObjects)  
  26.     {  
  27.         if (this.gameObjects[x].draw)  
  28.         {  
  29.             this.gameObjects[x].draw(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  30.         }  
  31.     }  
  32.  
  33.     // 將后臺緩沖復制到當前顯示的畫布  
  34.     this.context2D.drawImage(this.backBuffer, 0, 0);  
  35. };  

這個draw函數(shù)就是所有渲染循環(huán)的核心。在前面的例子中,渲染循環(huán)的函數(shù)會直接修改要繪制到屏幕上的對象(笑臉)。如果只需繪制一個對象,這樣做沒有問題。但是,一個游戲要由幾十個單獨的對象組成,所以這個draw函數(shù)并沒有直接在渲染循環(huán)中直接處理要繪制的對象,而是維護了一個保存著這些對象的數(shù)組,讓這些對象自己來更新和繪制自己。

首先,計算自上一幀渲染所經(jīng)過的時間。即便我們在代碼里寫了每秒鐘調(diào)用30次draw函數(shù),但誰也無法保證事實如此。通過計算自上一幀渲染所經(jīng)過的時間,可以做到盡可能讓游戲的執(zhí)行與幀速率無關(guān)。

  1. // 計算從上一幀到現(xiàn)在的時間  
  2. var thisFrame = new Date().getTime();  
  3. var dt = (thisFrame - this.lastFrame)/1000;  
  4. this.lastFrame = thisFrame;  

接著清理繪制上下文。

  1. // 清理繪制上下文  
  2. this.backBufferContext2D.clearRect(0, 0, this.backBuffer.width, this.backBuffer.height);  
  3. this.context2D.clearRect(0, 0, this.canvas.width, this.canvas.height);  

然后,就是調(diào)用游戲?qū)ο?這些對象是由GameObject類定義的,下一篇文章將介紹該類)自己的更新(update)和繪制(draw)方法。注意,這兩個方法是可選的(這也是我們在調(diào)用它們之前先檢查它們是否存在的原因),但差不多每一個對象都需要更新和繪制自已。

  1. // 首先更新所有游戲?qū)ο? 
  2. for (x in this.gameObjects)  
  3. {  
  4.     if (this.gameObjects[x].update)  
  5.     {  
  6.         this.gameObjects[x].update(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  7.     }  
  8. }  
  9.  
  10. // 然后繪制所有游戲?qū)ο? 
  11. for (x in this.gameObjects)  
  12. {  
  13.     if (this.gameObjects[x].draw)  
  14.     {  
  15.         this.gameObjects[x].draw(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  16.     }  
  17. }  

最后,把后臺緩沖復制到前臺緩沖,最終用戶就可以看到下一幀了。

  1. // 將后臺緩沖復制到當前顯示的畫布  
  2. this.context2D.drawImage(this.backBuffer, 0, 0);  

理解了draw函數(shù),下面再分別講一講addGameObject和removeGameObject函數(shù)。

  1. /**  
  2.     向gameObjects集合中添加一個GameObject  
  3.     @param gameObject The object to add  
  4. */  
  5. this.addGameObject = function(gameObject)  
  6. {  
  7.     this.gameObjects.push(gameObject);  
  8.     this.gameObjects.sort(function(a,b){return a.zOrder - b.zOrder;})  
  9. };  
  10.  
  11. /**  
  12.     從gameObjects集合中刪除一個GameObject  
  13.     @param gameObject The object to remove  
  14. */  
  15. this.removeGameObject = function(gameObject)  
  16. {  
  17.     this.gameObjects.removeObject(gameObject);  
  18. }  

利用addGameObject和removeGameObject(在Utils.js文件里通過擴展Array.prototype添加)函數(shù),可以在GameObjectManager所維護的GameObject集合(即gameObjects變量)中添加和刪除游戲?qū)ο蟆?/p>

GameObjectManager類是我們這個游戲框架中最復雜的一個類。在下一篇文章中,我們會講解游戲框架的另外幾個類:GameObject、VisualGameObject、Bounce和ApplicationManager。

好了,現(xiàn)在放松一下,看一看Demo吧。

原文:http://www.brighthub.com/content/matthewcaspersonshubfoliohasmoved.aspx

譯文:http://www.cn-cuckoo.com/2011/08/14/game-development-with-javascript-and-the-canvas-element-3-2604.html

【編輯推薦】

  1. 使用JavaScript和Canvas開發(fā)游戲之認識Canvas
  2. HTML 5 Canvas(畫布)教程之圖像處理
  3. HTML 5新特性Canvas入門秘籍
  4. 15個不可思議的HTML 5 Canvas應用欣賞
  5. 使用JavaScript和Canvas開發(fā)游戲之使用Canvas
責任編輯:陳貽新 來源: 李松峰博客
相關(guān)推薦

2011-08-12 08:56:31

JavaScript

2011-08-11 09:16:50

JavaScript

2015-06-29 11:30:07

JavaScript小烏龜推箱子

2022-08-10 18:14:49

國際象棋游戲位字段C語言

2019-05-14 12:30:07

PythonPygame游戲框架

2011-11-03 09:13:27

JavaScript

2016-11-29 13:31:52

JavaScriptsetTimeout定時執(zhí)行

2011-10-21 09:10:12

JavaScript

2021-03-30 05:58:01

JavascriptCss3轉(zhuǎn)盤小游戲

2014-02-14 09:37:01

JavascriptDOM

2013-01-14 09:44:58

JavaScriptJSJS框架

2021-02-05 16:03:48

JavaScript游戲?qū)W習前端

2020-11-30 06:20:13

javascript

2017-06-08 15:53:38

PythonWeb框架

2015-06-02 04:13:23

Python乒乓球類游戲

2021-09-08 08:36:50

ncursesLinux猜謎游戲

2023-03-01 10:19:23

2022-09-01 11:48:45

JavaScript框架

2024-01-15 00:35:23

JavaScript框架HTML

2021-04-13 06:35:13

Elixir語言編程語言軟件開發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號