遊戲開發並不需要侷限於使用 Unity 或 Unreal Engine4 的用戶。JavaScript 遊戲開發已經有一段時間了。實際上,最流行的瀏覽器(例如Chrome,Firefox和Edge)的最新版本提供了對高級圖形渲染(例如WebGL【https://get.webgl.org/】)的支持,從而帶來了非常有趣的遊戲開發機會。
不過用 WebGL 進行遊戲開發沒有辦法在一篇文章中涵蓋其所有內容(有專門爲此編寫的完整書籍),並且出於個人喜好,在深入研究特定技術之前,我更傾向於依賴框架的幫助。
這就是爲什麼經過研究後,我決定用 MelonJS【http://www.melonjs.org/】 編寫此快速教程的原因。
什麼是 MelonJS?
你可能已經猜到了,MelonJS 是一個 JavaScript 遊戲引擎,與所有主流瀏覽器完全兼容(從 Chrome 到 Opera,一直到移動版 Chrome 和 iOS Safari)。
它具有一系列功能,在我的研究過程中非常引人注目:
-
對於初學者來說,它是完全獨立的,不需要外部依賴就可以使它工作。
-
但是,它可以與多個第三方工具集成在一起,使你的工作更加輕鬆,例如Tiled(可幫助你創建地圖和遊戲關卡【https://github.com/bjorn/tiled/wiki】),TexturePacker(幫助你創建所需的紋理圖集並簡化和優化精靈管理【https://github.com/melonjs/melonJS/wiki/How-to-use-Texture-Atlas-with-TexturePacker】)。
-
集成了 2D 物理引擎。這意味着你可以使用開箱即用的逼真的 2D 運動和碰撞檢測。這很關鍵,因爲必須解決所有這些問題,這需要大量的工作(更不用說數學運算了,這並不是我的菜)。
- 支持聲音 API,使你能夠以出色的簡便性添加聲音效果和背景音樂。
該引擎還有其他令人讚歎的功能,你可以在其網站上進行查看,不過以上是本文中我們最關注的功能。
提示:使用 Bit【https://github.com/teambit/bit】(Github【https://github.com/teambit/bit】)可以輕鬆共享和重用 JS 模塊,項目中的 UI 組件,建議更新。
Bit 組件:能夠輕鬆地在團隊中跨項目共享
設計我們的遊戲
打字遊戲的目的是通過打字(或敲擊隨機鍵)爲玩家提供移動或執行某種動作的能力。
我記得小時候曾經學過如何打字(是的,很久以前)了,當時在“Mario Teaches Typing” 這個遊戲中,必須鍵入單個字母才能前進,要麼跳到烏龜上,要麼從下面打一個方塊。下圖爲你提供了遊戲外觀以及怎樣與之進行互動的想法。
儘管這是一個有趣的小遊戲,但它並不是一個真正的平臺遊戲,Mario 所執行的動作始終對應一個按鍵,並且永遠不會失效。
不過,對於本文,我想讓事情變得更有趣,並不是創建一個簡單的打字遊戲,例如上面的遊戲:
遊戲不會通過單個字母來決定下一步的行動,而是提供了五個選擇,並且每個選擇都必須寫一個完整的單詞:
-
前進
-
向前跳
-
跳起來
-
向後跳
- 向後移動
換句話說,你可以通過輸入的單詞來移動角色,而不是經典的基於箭頭進行控制。
除此之外,該遊戲將是一個經典平臺遊戲,玩家可以通過走動收集金幣。爲了簡潔起見,我們會將敵人和其他類型的實體排除在本教程之外(儘管你應該能夠推斷出所使用的代碼,並能基於該代碼創建自己的實體)。
爲了使本文保持合理的長度,我將只關注一個階段,全方位的動作(換句話說,你將能夠執行所有 5 個動作)、幾個敵人、一種收藏品,還有數量可觀的臺階供你跳來跳去。
你需要的工具
儘管 melonJS 是完全獨立的,但在此過程中有一些工具可以幫助大家,我建議你使用它們:
-
Texture Packer:有了這個,你將能夠自動生成紋理圖集,這是另一種表達 JSON 文件的方式,其中打包了所有圖像,以便引擎以後可以檢索並根據需要使用它們。如果你沒有這個工具,那麼手動維護地圖集可能會消耗太多的時間。【https://www.codeandweb.com/texturepacker】
- Tiled:這將是我們的關卡編輯器。儘管你可以免費下載它(你需要找到顯示“No thanks, just take me to the downloads” 的鏈接),但是你可以向該神奇工具的作者捐獻最低 1 美元。如果你有可用的 PayPal 帳戶或借記卡,建議你這樣做,這樣的軟件需要維護,並且需要付出時間和精力。
使用這些工具,你將可以繼續學習並完成本教程,所以讓我們開始編碼吧。
基本的平臺遊戲
爲了開始這個項目,我們可以使用一些示例代碼。下載引擎時,它將默認附帶一組示例項目,你可以檢出這些項目(它們位於 example 文件夾中)。
這些示例代碼是我們用來快速啓動項目的代碼。在其中,你會發現:
-
data 文件夾,包含與代碼無關的所有內容。在這裏你可以找到聲音、音樂、圖像、地圖定義甚至字體。
-
js文件夾,你將在這裏保存所有與遊戲相關的代碼。
- index.html 和 index.css文件。這些是你的應用與外界互動所需的聯繫點。
瞭解現有代碼
現在暫時將資源留在 data 文件夾中,我們需要了解該示例爲我們提供了什麼。
執行遊戲
要執行遊戲,你需要做一些事情:
-
一份 melonJS。如果已下載,請確保獲得 dist 文件夾的內容。將其複製到任意文件夾中,並確保像其他 JS 文件一樣,將其添加到 index.html 文件中。
- 安裝(如果尚未安裝)npm 中提供的 http-server【https://www.npmjs.com/package/http-server】 模塊,該模塊可以快速爲相關文件夾提供 HTTP 服務。如果尚未安裝,只需執行以下操作:
1$ npm install -g http-server
安裝完成後,從項目文件夾中運行:
1$ http-server
這時你可以通過訪問 http://localhost:8080 來測試遊戲。
查看代碼
在遊戲中你會發現這是一個能夠進行基本(非常尷尬)動作的平臺遊戲,幾個不同的敵人和一個收藏品。基本上這與我們的目標差不多,但控制方案略有不同。
這裏要檢查的關鍵文件是:
-
game.js:該文件包含所有初始化代碼,有趣的是如何實例化遊戲圖形和主控件。
-
screens/play.js:包含設置關卡所需的所有代碼。你會注意到它內容並不多。由於級別定義是使用其他工具(即 Tiled)完成的,所以此代碼只是啓用了該功能。
-
entities/player.js:顯然這是你的主要目標。該文件包含你角色的移動代碼,碰撞反應和控制鍵綁定。雖然規模並不大,卻是你想花費最多時間的地方。
- entities/enemies.js:僅次於 player 代碼,這很重要,因爲你將看到如何基於預定義的座標來設置自動行爲。
其餘文件也很有用,但並不是那麼重要,我們會在需要時使用它們。
瞭解一切從何而來
如果你提前做好了了功課,可能已經注意到了,沒有一行實例化玩家或敵人的代碼。他們的座標無處可尋。那麼,遊戲該如何理解呢?
這是關卡編輯器所起到的作用。如果你下載了Tiled,則可以在 data/map 文件夾中打開名爲 map1.tmx 的文件,然後會看到類似下面的內容:
屏幕的中心部分向你顯示正在設計的關卡。如果仔細觀察,你會看到圖像和矩形形狀,其中一些具有不同的顏色和名稱。這些對象代表遊戲中的 東西,具體取決於它們的名稱和所屬的層。
在屏幕的右側,你會在其中看到圖層列表(在右上方)。有不同類型的層:
-
圖像層:用於背景或前景圖像
-
對象層:用於碰撞對象、實體以及你想在地圖中實例化的任何對象。
- Tile 層:你將在其中放置 Tile 以創建實際關卡的位置。
右下角包含此地圖的圖塊。tileet 也可以由 Tiled 創建,並且可以在同一文件夾中以 tsx 擴展名找到該 tileet。
最後,在屏幕左側,你會看到“屬性”部分,在這裏你將看到有關所選對象或單擊的圖層的詳細信息。你將能夠更改通用屬性(例如圖層的顏色,以便更好地瞭解其對象的位置)並添加自定義屬性(稍後將其作爲參數傳遞給遊戲中實體的構造函數)。
更改運動方案
現在我們已經準備好進行編碼了,讓我們專注於本文的主要目的,我們將以示例的工作版本爲例,嘗試對其進行修改,使其可以用作打字遊戲。
這意味着,需要更改的第一件事是運動方案,或者換句話說:更改控制。
轉到 entities/player.js 並檢查 init 方法。你會注意到很多 bindKey 和 bindGamepad 調用。這些代碼本質上是將特定按鍵與邏輯操作綁定在一起。簡而言之,它可以確保無論你是按向右箭頭鍵,D 鍵還是向右移動模擬搖桿,都會在代碼中觸發相同的“向右”動作。
所有這些都需要將其刪除,這對我們沒什麼用。同時創建一個新文件,將其命名爲 wordServices.js,並在此文件中創建一個對象,該對象將在每個回合中返回單詞,這能夠幫助我們瞭解玩家到底選擇了哪個動作。
1/**
2 * Shuffles array in place.
3 * @param {Array} a items An array containing the items.
4 */
5function shuffle(a) {
6 var j, x, i;
7 for (i = a.length - 1; i > 0; i--) {
8 j = Math.floor(Math.random() * (i + 1));
9 x = a[i];
10 a[i] = a[j];
11 a[j] = x;
12 }
13 return a;
14}
15
16
17ActionWordsService = {
18
19 init: function(totalActions) {
20 //load words...
21 this.words = [
22 "test", "hello", "auto", "bye", "mother", "son", "yellow", "perfect", "game"
23 ]
24 this.totalActions = totalActions
25 this.currentWordSet = []
26 },
27
28 reshuffle: function() {
29 this.words = shuffle(this.words)
30 },
31
32 getRegionPostfix: function(word) {
33 let ws = this.currentWordSet.find( ws => {
34 return ws.word == word
35 })
36 if(ws) return ws.regionPostfix
37 return false
38 },
39
40 getAction: function(word) {
41 let match = this.getWords().find( am => {
42 return am.word == word
43 })
44 if(match) return match.action
45 return false
46 },
47
48 getWords: function() {
49 let actions = [ { action: "right", coords: [1, 0], regionPostfix: "right"},
50 { action: "left", coords: [-1, 0], regionPostfix: "left"},
51 { action: "jump-ahead", coords: [1,-0.5], regionPostfix: "upper-right"},
52 { action: "jump-back", coords:[-1, -0.5], regionPostfix: "upper-left"},
53 { action: "up", coords: [0, -1], regionPostfix: "up"}
54 ]
55
56 this.currentWordSet = this.words.slice(0, this.totalActions).map( w => {
57 let obj = actions.shift()
58 obj.word = w
59 return obj
60 })
61 return this.currentWordSet
62 }
63}
本質上,該服務包含一個單詞列表,然後將其隨機排列,並且每次請求該列表時(使用 getWords 方法),都會隨機獲取一組單詞,並將它們分配給上面提到的一種操作。還有與每個操作相關的其他屬性:
-
基於動作 HUD,coords 屬性用於將文本放置在正確的座標中(稍後會詳細介紹)
- regionPostfix 屬性用於爲 HUD 操作選擇正確的框架。
現在,讓我們看看如何在遊戲過程中請求用戶輸入。
注意:繼續前進之前,請記住,爲了使新服務可用於其餘代碼,你必須將其包含在 index.html 文件中,就像其他 JS 庫一樣:
1<script type="text/javascript" src="js/wordServices.js"></script>
如何捕獲用戶輸入
你可以潛在地使用鍵綁定的組合來模仿使用遊戲元素的輸入字段的行爲,但是請考慮輸入字段默認提供的所有可能的組合和行爲(例如,粘貼文本、選擇、移動而不刪除字符等) ),必須對所有程序進行編程以使其可用。
相反,我們可以簡單地在 HTML 主頁面中添加一個文本字段,並使用 CSS 對其進行樣式設置,使其位於 Canvas 元素之上,它將成爲遊戲的一部分。
你只需要在 <body> 內的這段代碼即可:
1<input type="text" id="current-word" />
儘管這完全取決於你,但我還是建議你使用 jQuery 來簡化將回調附加到 keypress 事件上所需的代碼。當然可以使用原生 JS 完成此操作,但我更喜歡這個庫提供的語法糖。
以下代碼位於 game.js 文件的 load 方法中,負責捕獲用戶的輸入:
1me.$input = $("#current-word")
2
3let lastWord = ''
4me.$input.keydown( (evnt) => {
5
6 if(evnt.which == 13) {
7 console.log("Last word: ", lastWord)
8 StateManager.set("lastWord", lastWord)
9 lastWord = ''
10 me.$input.val("")
11 } else {
12 if(evnt.which > 20) {
13 let validChars = /[a-z0-9]+/gi
14 if(!String.fromCharCode(evnt.which).match(validChars)) return false
15 }
16
17 setTimeout(_ => {
18 lastWord = me.$input.val() //String.fromCharCode(evnt.which)
19 console.log("Partial: ", lastWord)
20 }, 1)
21 }
22 setTimeout(() => {
23 StateManager.set("partialWord", me.$input.val())
24 }, 1);
25})
本質上是我們捕獲輸入元素並將其存儲在全局對象 me 中。這個全局變量包含遊戲所需的一切。
這樣,我們可以爲按下的任何按鍵設置事件處理程序。如你所見,我正在檢查鍵碼 13(代表ENTER鍵)以識別玩家何時完成輸入,否則我將確保他們輸入的是有效字符(我只是避免使用特殊字符,這樣可以防止 melonJS 提供的默認字體出現問題)。
最後我在 StateManager 對象上設置了兩個不同的狀態,lastWord 瞭解玩家輸入的最後一個單詞,partialWord 解現在正在輸入的內容。這兩種狀態很重要。
組件之間共享數據
如何在組件之間共享數據是很多框架中的常見問題。我們將捕獲的輸入作爲 game 組件的一部分,那麼該如何與他人共享這個輸入呢?
我的解決方案是創建一個充當事件發送器(event emitter)【https://nodejs.org/api/events.html】的全局組件:
1const StateManager = {
2
3 on: function(k, cb) {
4 console.log("Adding observer for: ", k)
5 if(!this.observers) {
6 this.observers = {}
7 }
8
9 if(!this.observers[k]) {
10 this.observers[k] = []
11 }
12 this.observers[k].push(cb)
13 },
14 clearObserver: function(k) {
15 console.log("Removing observers for: ", k)
16 this.observers[k] = []
17 },
18 trigger: function(k) {
19 this.observers[k].forEach( cb => {
20 cb(this.get(k))
21 })
22 },
23 set: function(k, v) {
24 this[k] = v
25 this.trigger(k)
26 },
27 get: function(k) {
28 return this[k]
29 }
30
31}
代碼非常簡單,你可以爲特定狀態設置多個“觀察者”(它們是回調函數),並且一旦設置了該狀態(即更改),便會用新值調用所有這些回調。
添加 UI
創建關卡之前的最後一步是顯示一些基本的 UI。因爲我們需要顯示玩家可以移動的方向以及需要輸入的單詞。
爲此將使用兩個不同的UI元素:
-
一個用於圖形,它將具有幾個不同的幀,本質上一個用於正常圖像,然後一個將每個方向顯示爲“selected”(與 ActionWordsService 上的 regionPostfix 屬性相關聯)
- 一個用於在圖像周圍輸出文本。順便說一下,這也與 ActionWordsService 上的 coords 屬性相關聯。
我們可以在 js 文件夾內搭上現有的 HUD.js 文件。在其中添加兩個新組件。
第一個是 ActionControl 組件,如下所示:
1game.HUD.ActionControl = me.GUI_Object.extend({
2 init: function(x, y, settings) {
3 game.HUD.actionControlCoords.x = x //me.game.viewport.width - (me.game.viewport.width / 2)
4 game.HUD.actionControlCoords.y = me.game.viewport.height - (me.game.viewport.height / 2) + y
5
6 settings.image = game.texture;
7
8 this._super(me.GUI_Object, "init", [
9 game.HUD.actionControlCoords.x,
10 game.HUD.actionControlCoords.y,
11 settings
12 ])
13
14 //update the selected word as we type
15 StateManager.on('partialWord', w => {
16 let postfix = ActionWordsService.getRegionPostfix(w)
17 if(postfix) {
18 this.setRegion(game.texture.getRegion("action-wheel-" + postfix))
19 } else {
20 this.setRegion(game.texture.getRegion("action-wheel")
21 }
22 this.anchorPoint.set(0.5,1)
23 })
24
25 //react to the final word
26 StateManager.on('lastWord', w => {
27 let act = ActionWordsService.getAction(w)
28 if(!act) {
29
30 me.audio.play("error", false);
31 me.game.viewport.shake(100, 200, me.game.viewport.AXIS.X)
32 me.game.viewport.fadeOut("#f00", 150, function(){})
33 } else {
34 game.data.score += Constants.SCORES.CORRECT_WORD
35 }
36 })
37 }
38})
看起來很多,但是它只是做了一點事情:
1.它從 settings 屬性中提取其座標,在 Tiled 上設置地圖後,我們將對其進行檢查。
2.添加對輸入了一部分的單詞作出反應的代碼。我們將 postfix 屬性用於當前編寫的單詞。
3.並添加了對完整的詞做出反應的代碼。如果某個動作與該字詞相關聯(即是正確的詞),那麼它將爲玩家加分。否則將晃動屏幕並播放錯誤聲音。
第二個圖形部分,即要輸入的單詞,如下所示:
1game.HUD.ActionWords = me.Renderable.extend({
2 init: function(x, y) {
3 this.relative = new me.Vector2d(x, y);
4
5 this._super(me.Renderable, "init", [
6 me.game.viewport.width + x,
7 me.game.viewport.height + y,
8 10, //x & y coordinates
9 10
10 ]);
11
12 // Use screen coordinates
13 this.floating = true;
14
15 // make sure our object is always draw first
16 this.z = Infinity;
17 // create a font
18 this.font = new me.BitmapText(0, 0, {
19 font : "PressStart2P",
20 size: 0.5,
21 textAlign : "right",
22 textBaseline : "bottom"
23 });
24
25 // recalculate the object position if the canvas is resize
26 me.event.subscribe(me.event.CANVAS_ONRESIZE, (function(w, h){
27 this.pos.set(w, h, 0).add(this.relative);
28 }).bind(this));
29
30 this.actionMapping = ActionWordsService.getWords()
31 },
32
33 update: function() {
34 this.actionMapping = ActionWordsService.getWords()
35 return true
36 },
37 draw: function(renderer) {
38 this.actionMapping.forEach( am => {
39 if(am.coords[0] == 0 && am.coords[1] == 1) return
40 let x = game.HUD.actionControlCoords.x + (am.coords[0]*80) + 30
41 let y = game.HUD.actionControlCoords.y + (am.coords[1]*80) - 30
42 this.font.draw(renderer, am.word, x, y)
43 })
44 }
45})
該組件的繁重工作是通過 draw 方法完成的。init 方法只是初始化變量。在調用 draw 的過程中,我們將迭代選定的單詞,並使用與之相關的座標以及一組固定數字,將單詞定位在 ActionControl 組件的座標周圍。
這是建議的動作控制設計的樣子(以及座標如何與之關聯):
當然,它應該有透明的背景。
只需確保將這些圖像保存在 /data/img/assets/UI 文件夾中,這樣當你打開 TexturePacker 時,它將識別出新圖像並將其添加到紋理中地圖集。
上圖顯示瞭如何添加 action wheel 的新圖像。然後,你可以單擊“Publish sprite sheet”並接受所有默認選項。它將覆蓋現有的地圖集,因此對於你的代碼無需執行任何操作。這一步驟至關重要,因爲紋理地圖集將作爲資源加載(一分鐘內會詳細介紹),並且多個實體會將其用於動畫之類的東西。請記住,在遊戲上添加或更新圖形時,都務必這樣做。
將所有內容與Tiled放在一起
好了,現在我們已經介紹了基礎知識,讓我們一起玩遊戲。首先要注意的是:地圖。
通過使用 tiled 和 melonJS 中包含的默認 tileet,我創建了這個地圖( 25x16 tiles 地圖,其中 tile 爲 32 x 32px):
這些是我正在使用的圖層:
- HUD:它僅包含一個名爲 HUD.ActionControl 的元素(重要的是要保持名稱相同,一會兒你會明白爲什麼)。下圖顯示了此元素的屬性(請注意自定義屬性)
img
-
collision:默認情況下,melonJS 會把以 collision 開頭的所有層都假定爲碰撞層,這意味着其中的任何形狀都是不可遍歷的。在這裏你將定義地板和平臺的所有形狀。
-
player:該層僅包含 mainPlayer 元素(一種形狀,該形狀將使 * melonJS 知道在遊戲開始時需要放置玩家的位置)。
-
entities:在這一層中,我再次添加了硬幣,它們的名稱很重要,請保持一致,因爲它們需要與你在代碼中註冊的名稱相匹配。
- 最後三層就可以在其中添加地圖和背景的圖像。
準備好之後,我們可以轉到 game.js 文件,並在 loaded 方法內添加以下幾行:
1// register our objects entity in the object pool
2me.pool.register("mainPlayer", game.PlayerEntity);
3me.pool.register("CoinEntity", game.CoinEntity);
4me.pool.register("HUD.ActionControl", game.HUD.ActionControl);
這些代碼用來註冊你的實體(你要使用 Tiled 直接放置在地圖上的實體)。第一個參數提供的名稱是你需要用 Tiled 進行匹配的名稱。
此外,在此文件中,onLoad 方法應如下所示:
1 onload: function() {
2
3 // init the video
4 if (!me.video.init(965, 512, {wrapper : "screen", scale : "auto", scaleMethod : "fit", renderer : me.video.AUTO, subPixel : false })) {
5 alert("Your browser does not support HTML5 canvas.");
6 return;
7 }
8
9 // initialize the "sound engine"
10 me.audio.init("mp3,ogg");
11
12 // set all ressources to be loaded
13 me.loader.preload(game.resources, this.loaded.bind(this));
14 ActionWordsService.init(5)
15 },
我們的基本要求是 965x512 的分辨率(我發現,當屏幕的高度與地圖的高度相同時效果很好。在我們的例子中爲 16*32 = 512)之後,將使用5個單詞(這些是你可以繼續前進的5個方向)初始化 ActionWordsService 。
onLoad 方法中另一條有趣的代碼是:
1me.loader.preload(game.resources, this.loaded.bind(this));
資源文件
遊戲需要的所有類型的資源(即圖像、聲音、背景音樂、JSON 配置文件等)都需要添加到 resources.js 文件中。
這是你資源文件的內容:
1game.resources = [
2
3 { name: "tileset", type:"image", src: "data/img/tileset.png" },
4 { name: "background", type:"image", src: "data/img/background.png" },
5 { name: "clouds", type:"image", src: "data/img/clouds.png" },
6
7
8 { name: "screen01", type: "tmx", src: "data/map/screen01.tmx" },
9
10 { name: "tileset", type: "tsx", src: "data/map/tileset.json" },
11
12 { name: "action-wheel", type:"image", src: "data/img/assets/UI/action-wheel.png" },
13 { name: "action-wheel-right", type:"image", src: "data/img/assets/UI/action-wheel-right.png" },
14 { name: "action-wheel-upper-right",type:"image", src: "data/img/assets/UI/action-wheel-upper-right.png" },
15 { name: "action-wheel-up", type:"image", src: "data/img/assets/UI/action-wheel-up.png" },
16 { name: "action-wheel-upper-left", type:"image", src: "data/img/assets/UI/action-wheel-upper-left.png" },
17 { name: "action-wheel-left", type:"image", src: "data/img/assets/UI/action-wheel-left.png" },
18
19 { name: "dst-gameforest", type: "audio", src: "data/bgm/" },
20
21 { name: "cling", type: "audio", src: "data/sfx/" },
22 { name: "die", type: "audio", src: "data/sfx/" },
23 { name: "enemykill", type: "audio", src: "data/sfx/" },
24 { name: "jump", type: "audio", src: "data/sfx/" },
25
26 { name: "texture", type: "json", src: "data/img/texture.json" },
27 { name: "texture", type: "image", src: "data/img/texture.png" },
28
29 { name: "PressStart2P", type:"image", src: "data/fnt/PressStart2P.png" },
30 { name: "PressStart2P", type:"binary", src: "data/fnt/PressStart2P.fnt"}
31];
其中你可以使用諸如圖塊集、屏幕映射之類的東西(請注意,名稱始終是不帶擴展名的文件名,這是強制性的要求,否則將找不到資源)。
硬幣
遊戲中的硬幣非常簡單,但是當你與它們碰撞時,需要發生一些事情,它們的代碼如下所示:
1game.CoinEntity = me.CollectableEntity.extend({
2
3 /**
4 * constructor
5 */
6 init: function (x, y, settings) {
7 // call the super constructor
8 this._super(me.CollectableEntity, "init", [
9 x, y ,
10 Object.assign({
11 image: game.texture,
12 region : "coin.png"
13 }, settings)
14 ]);
15
16 },
17
18 /**
19 * collision handling
20 */
21 onCollision : function (/*response*/) {
22
23 // do something when collide
24 me.audio.play("cling", false);
25 // give some score
26 game.data.score += Constants.SCORES.COIN
27
28 //avoid further collision and delete it
29 this.body.setCollisionMask(me.collision.types.NO_OBJECT);
30
31 me.game.world.removeChild(this);
32
33 return false;
34 }
35});
請注意,硬幣實體實際上是擴展了 CollectibleEntity (這給它提供了一個特殊的衝撞類型給實體,因此melonJS知道在玩家移過它時會調用碰撞處理程序),你要做的就是調用其父級的構造函數,然後當你拾起它時,在 onCollision 方法上會播放聲音,在全局得分中加 1,最後從世界中刪除對象。
成品
將所有內容放在一起,就有了一個可以正常工作的遊戲,該遊戲可以讓你根據輸入的單詞在 5 個不同的方向上移動。
它看起來應該像這樣:
並且由於本教程已經太長了,你可以在 Github【https://github.com/deleteman/plaformer-sample-1】 上查看該遊戲的完整代碼。
原文:https://blog.bitsrc.io/writing-a-typing-game-with-melonjs-ef0dd42f37bf