【melonJS】幾十行 JS 程式碼簡單編寫一個小遊戲「尋找掘金醬」

語言: CN / TW / HK

theme: fancy highlight: a11y-dark


我正在參加「碼上掘金挑戰賽」詳情請看:碼上掘金挑戰賽來了!

前言

如你所見,這是一個~~萌系休閒類~~小遊戲,應該非常適合在深夜裡一個人打發寂寞時光!(查詢作者精神狀態

遊戲是這樣的,通過控制滑鼠可以在這個被黑夜籠罩的都市中開啟一束光,照亮某片區域,玩家要儘可能快地尋找到 掘金醬 的身影,滑鼠只要命中即為遊戲結束,此時如果繼續滑動滑鼠則會看到 掘金醬 向你鬼畜而來.....(期初可能只是一個BUG,但我覺得挺有趣的就保留了下來,我們通常應該可以將此類事件稱之為——"創意")

本遊戲採用 melonJS 2 進行開發,melonJS 2melonJS 遊戲引擎的現代版本。它幾乎完全使用 ES6 的類、繼承和語義等進行了重建,並使用 Rollup 打包以提供現代功能。瞭解更多可以檢視我的這篇文章: 全新輕量級 2D 開源遊戲引擎,採用現代化構建,只需要會使用 JS(ES6語法) 即可開始編寫遊戲,接下來進入正題。

建立場景

```js import * as me from "https://esm.run/melonjs";

me.device.onReady(function () { // 初始化 if (!me.video.init(728, 360, { parent: "screen", scaleMethod: "flex-width", renderer: me.video.WEBGL })) { return; } // 註冊事件 me.state.set(me.state.PLAY, new PlayScreen()); me.state.set(me.state.GAME_END, new EndingScreen()); // 載入資源 me.loader.crossOrigin = "anonymous" // 這裡因為我載入的是網路資源 me.loader.preload(resource, () => { me.state.change(me.state.PLAY); startTime = new Date().getTime() }); });

var resource = [{ name: "background", type: "image", src: "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/33127b0ebc424d188c048574fa8f4dc0~tplv-k3u1fbpfcp-watermark.image?" }, { name: "jjj", type: "image", src: "https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf2e426ed75a4df099433c8a169cf029~tplv-k3u1fbpfcp-watermark.image?" }] var isEnd = false // 結束標識 var startTime = 0 // 開始時間戳 var endTime = 0 // 結束時間戳 ```

在裝置與引擎準備完畢時,會觸發 onReady 回撥,這裡我們先初始化一個畫布,renderer 可以改變渲染器的方式,預設是 Canvas,因為我使用到了2D點光源的效果,所以改成在 WebGL 渲染更好。

me.state 名稱空間是重要的一個概念,它用來設定和改變遊戲中的生命週期狀態,比如 暫停遊戲開始/結束遊戲進入選單等等,這裡通過 set 方法分別設定了遊戲啟動時的場景例項和遊戲結束時的場景例項。

loader.preload 是用於預載入資源的方法,資源通過物件陣列注入,其中name引數標識了對應資源的名稱,後續引用資源不需要變數,可以直接使用名稱就能找到對應資源。當資源載入完畢後,觸發回撥函式,回撥中修改狀態來開始遊戲,並記錄下一個時間戳,用於過程中統計遊戲進行的時間。

下面我們開始為遊戲編寫第一個場景。

遊戲場景

js class PlayScreen extends me.Stage { onResetEvent() { // 背景元素 var bg_sprite = new me.Sprite( me.game.viewport.width / 2, me.game.viewport.height / 2, { image: "background", anchorPoint: { x: 0.5, y: 0.5 }} ); // 新增目標 var target_sprite = new me.Sprite(point.x, point.y, { image: "jjj" }); // 新增元素進畫布 me.game.world.addChild(bg_sprite); me.game.world.addChild(target_sprite); } };

通過 Sprite 物件建立精靈圖,在2D遊戲中,通常以一張順序包含幀動畫的圖片來製作動態的影象,沒錯,就跟CSS精靈技術是同種原理,不過這裡我們並不做到那麼複雜,只是靜態顯示。

image.png

我們繼續豐富場景,作為遊戲中的上帝,怎麼能沒有光呢?場景繼承的基類 Stage 中有一個 lights 屬性用於設定光源列表,我們找到一個 Light2d 聚光燈的類,例項化一個燈光系統設定進光源列表中,這樣我們的場景中就有了一束光:

js // 燈光系統 var whiteLight = new me.Light2d(0, 0, 100, 70, "#fff", 0.7); // 設定燈光 this.lights.set("whiteLight", whiteLight);

現在該讓光束隨著滑鼠移動起來了,你完全可以使用 DOM 的監聽事件來做,當然melonJS下同樣內建了許多輸入監聽事件,這裡的 pointermove 事件是不是跟 document 中的 mousemove 事件很類似?只不過它以傳入第二個引數的方式來設定監聽範圍:

js // 光隨著滑鼠事件移動 me.input.registerPointerEvent("pointermove", me.game.viewport, (event) => { whiteLight.centerOn(event.gameX, event.gameY); });

動起來了,是不是很簡單?

2022-09-09 09.41.42.gif

最後為場景新增一個純黑遮罩,營造出一點氛圍感~就是開頭看到的效果

js this.ambientLight.parseCSS("#000");

建立角色

上面我們往遊戲中添加了靜態的精靈圖,但是遊戲需要互動動作才能進行下去,這時我們就需要建立一個新的類繼承精靈圖,就叫它 Actor 好了,接著擴充套件一下這個類,這裡我們使用遊戲引擎提供的物理模型 Ellipse 物件,只是單純為了新增一個橢圓作為物理身體,引數比較隨意,然後設定了這個類的碰撞事件,在觸發碰撞檢測時執行遊戲結束的相關動作。

js class Actor extends me.Sprite { constructor() { super(me.Math.random(-15, me.game.viewport.width), me.Math.random(-15, me.game.viewport.height), { image: "jjj" }); // 為角色設定身體 this.body = new me.Body(this, new me.Ellipse(6, 6, this.width - 6, this.height - 6)); this.body.gravityScale = 0; // 消除掉重力 } onCollision() { // 標記遊戲結束,在滑鼠移動事件中會讀取該全域性變數進行判斷 isEnd = true // 記錄下遊戲結束時間,計算遊戲時長 endTime = new Date().getTime() // 改變遊戲場景,進入 GAME END 遊戲結束場景 me.state.change(me.state.GAME_END) return false; } };

對於遊戲引擎中的物理模型來說,通常都會有一個重力屬性,我們的遊戲本質還是靜態的角色,所以這裡需要把重力 gravityScale 設定為 0 ,否則我們的 掘金醬 會像這樣掉下去(原諒我不厚道地笑了):

2022-09-09 12.52.51.gif

由於我們的光源並沒有物理模型,那要怎麼讓滑鼠和掘金醬之間產生碰撞呢?這裡我取巧了一下,利用 Actor 類,建立了一個"小掘金醬",讓它跟隨滑鼠移動,然後隱藏它,這樣就能觸發物理碰撞的判定了(~~畫外音:這個類取名 Actor 原來是這個意思嗎!~~):

js const point_sprite = me.game.world.addChild(new Actor()); point_sprite.scale(0.5) // 縮小一點 point_sprite.setOpacity(0) // 變成透明 // 滑鼠移動事件: me.input.registerPointerEvent("pointermove", me.game.viewport, (event) => { if (!isEnd) { // 移動光源 whiteLight.centerOn(event.gameX, event.gameY); // 移動透明的物理模型,把它當成滑鼠指標 point_sprite.centerOn(event.gameX + 22, event.gameY + 22); } else { target_sprite.setOpacity(0.7) target_sprite.scale(1.02) } });

image.png

結束場景

這個場景就蠻簡單的了,就是輸出文字內容,程式碼很好理解:

js class EndingScreen extends me.Stage { onResetEvent() { me.game.world.addChild(new me.Text(me.game.viewport.width / 2, me.game.viewport.height / 2 - 20, { font: "Arial", size: 50, fillStyle: "#FFFFFF", textAlign: "center", text: "恭喜你找到了掘金醬!\n通關時間:" + ((endTime - startTime) / 1000).toFixed(2) + '秒' })); } }

完整的程式碼和遊戲演示

完整的程式碼和遊戲演示(由於引用資源第一次載入可能需要等待時間),因為懶沒有做遊戲介面,所以猛戳上面的 ○ 執行 按鈕來重複開始遊戲:

程式碼片段

試玩一下吧!看看你最快多少秒可以抓住掘金醬?

結束

總結一下,利用 melonJS 我們僅用了幾十行程式碼就完成了一個小遊戲,雖然這個遊戲並不複雜,即使用原生 JS 可能也不難實現,但你卻很難自己輕易實現一個 WebGL / Canvas 級別的渲染器,使用遊戲引擎可以做到更多,這裡只是現學現賣小試了一下牛刀,順便也可以練習 ES6 語法,如果你感興趣,也可以仔細參閱官方的API文件和Demo,做出更好玩的東西~

最後建議碼上掘金的編輯器後續可以考慮做個檔案目錄的功能,以支援元件/小檔案引用。目前寫寫內容少的小網頁還行,稍微複雜點的拆不了元件,做這類演示專案時資源也不知道要放哪裡(比如引入圖片、JSON檔案啥的),本文中引用到的圖片資源我還是傳到文章裡再隱藏的,哈哈。

image.png