成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開發 前端
下面我們要介紹的JavaScript代碼使用面向對象的方式來編寫。對于沒有編寫過多少JavaScript代碼的人來說,恐怕第一眼看到它們會覺得有點奇怪。

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

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

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

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

 

 

下面我們要介紹的JavaScript代碼使用面向對象的方式來編寫。對于沒有編寫過多少JavaScript代碼的人來說,恐怕第一眼看到它們會覺得有點奇怪。如果你真的不太熟悉JavaScript的面向對象編程,建議通過Mozilla Developer Network的這個教程https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript來補補課。這篇教程里解釋了我們稍后會用到的一些編程技術。

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

Main.js

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

  1.  
  2. /** 每秒多少幀  
  3.     @type Number  
  4. */  
  5. var FPS = 30;  
  6. /** 兩幀間間隔的秒數  
  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. // 將應用的入口設置為init函數  
  21. window.onload = init;  
  22.  
  23. /**  
  24.     應用的入口  
  25. */  
  26. function init()  
  27. {  
  28.     new GameObjectManager().startupGameObjectManager();  
  29. }  

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

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

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

第二個原因(很大程度上也是個人的問題)是我經常會在構造函數中引用尚未定義的變量。這可能是我使用C++、Java和C#這些語言落下的毛病,在這些語言里,類變量在源代碼中的位置對其在構造函數中的可見性沒有影響。拿下面這個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在我們把數值5賦給它的時候還不存在。只有運行到var a;這一行,變量a才存在。盡管這個例子有點故意編排的意味,但的確能夠說明我所遇到的問題。通過把類的創建放到一個類似startupClassName這樣的函數中完成,并且在構造函數中定義(但不初始化)局部變量,然后當我在這些構建函數中引用相應的局部變量時,就能夠確保它們一定是存在的。

#p#

GameObjectManager.js

  1.  
  2. /**  
  3.     管理游戲中所有對象的管理器  
  4.     @class  
  5. */  
  6. function GameObjectManager()  
  7. {  
  8.     /** 保存游戲中對象的數組  
  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.     /** 對內存中用作后臺緩沖區的畫布的引用  
  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.         // 設置引用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.         // 創建一個新的ApplicationManager  
  63.         this.applicationManager = new ApplicationManager().startupApplicationManager();  
  64.  
  65.         // 使用setInterval來調用draw函數  
  66.         setInterval(function(){g_GameObjectManager.draw();}, SECONDS_BETWEEN_FRAMES);  
  67.  
  68.         return this;  
  69.     }  
  70.  
  71.     /**  
  72.         渲染循環  
  73.     */  
  74.     this.draw = function ()  
  75.     {  
  76.         // 計算從上一幀到現在的時間  
  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.         // 首先更新所有游戲對象  
  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.         // 然后繪制所有游戲對象  
  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函數的代碼如下:

  1.  
  2. /**  
  3.     初始化這個對象  
  4.     @return A reference to the initialised object  
  5. */  
  6. this.startupGameObjectManager = function()  
  7. {  
  8.     // 設置引用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.     // 創建一個新的ApplicationManager  
  20.     this.applicationManager = new ApplicationManager().startupApplicationManager();  
  21.  
  22.     // 使用setInterval來調用draw函數  
  23.     setInterval(function(){g_GameObjectManager.draw();}, SECONDS_BETWEEN_FRAMES);  
  24.  
  25.     return this;  
  26. }  

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

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

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

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

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

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

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

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

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

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

  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');  

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

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

最后,使用setInterval函數重復調用draw函數,這個函數是渲染循環的核心所在。

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

#p#

下面來看一看draw函數。

  1. /**  
  2.     渲染循環  
  3. */  
  4. this.draw = function ()  
  5. {  
  6.     // 計算從上一幀到現在的時間  
  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.     // 首先更新所有游戲對象  
  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.     // 然后繪制所有游戲對象  
  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函數就是所有渲染循環的核心。在前面的例子中,渲染循環的函數會直接修改要繪制到屏幕上的對象(笑臉)。如果只需繪制一個對象,這樣做沒有問題。但是,一個游戲要由幾十個單獨的對象組成,所以這個draw函數并沒有直接在渲染循環中直接處理要繪制的對象,而是維護了一個保存著這些對象的數組,讓這些對象自己來更新和繪制自己。

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

  1. // 計算從上一幀到現在的時間  
  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);  

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

  1. // 首先更新所有游戲對象  
  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. // 然后繪制所有游戲對象  
  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函數,下面再分別講一講addGameObject和removeGameObject函數。

  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添加)函數,可以在GameObjectManager所維護的GameObject集合(即gameObjects變量)中添加和刪除游戲對象。

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

好了,現在放松一下,看一看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開發游戲之認識Canvas
  2. HTML 5 Canvas(畫布)教程之圖像處理
  3. HTML 5新特性Canvas入門秘籍
  4. 15個不可思議的HTML 5 Canvas應用欣賞
  5. 使用JavaScript和Canvas開發游戲之使用Canvas
責任編輯:陳貽新 來源: 李松峰博客
相關推薦

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定時執行

2011-10-21 09:10:12

JavaScript

2021-03-30 05:58:01

JavascriptCss3轉盤小游戲

2014-02-14 09:37:01

JavascriptDOM

2013-01-14 09:44:58

JavaScriptJSJS框架

2021-02-05 16:03:48

JavaScript游戲學習前端

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猜謎游戲

2022-09-01 11:48:45

JavaScript框架

2024-01-15 00:35:23

JavaScript框架HTML

2023-03-01 10:19:23

2021-04-13 06:35:13

Elixir語言編程語言軟件開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人精品一区二区户外勾搭野战 | 亚洲精品久久久久久下一站 | 91五月天| 亚洲成人一区二区三区 | 欧美一区二区三区在线视频 | 久久久久久黄 | av在线伊人 | 日韩精品一区二区三区中文在线 | 黄a在线观看 | 黄网免费 | 亚洲天堂av网 | 女同久久 | 国产精品久久久久久久久久久免费看 | 成人免费在线视频 | 在线观看欧美日韩视频 | 国产精品国产成人国产三级 | 欧美成人免费电影 | 免费观看毛片 | 国产情侣啪啪 | www.日韩| 成人精品一区 | 理论片午午伦夜理片影院 | 一区在线播放 | 久久久久久久一区 | 精品综合久久 | 日韩精品成人网 | 国产蜜臀| 国产成人精品一区二三区在线观看 | 综合久久亚洲 | 久久人人爽人人爽人人片av免费 | 亚洲区一区二区 | 久久大全 | 成人亚洲精品 | 一区二区三区视频在线免费观看 | 91在线精品视频 | 天堂网avav | 欧美日韩亚洲国产综合 | 羞羞在线观看视频 | 一二三四在线视频观看社区 | 999久久久免费精品国产 | 99久久精品视频免费 |