
整個心得分成程式 美術 音樂,三個部份來說明,該篇為程式部分。
前言
一開始做技術評估,我原先只會html跟CSS還有jquery,為了快速驗證構想加上本次開發的類型為音樂相關遊戲,透過Gemini 跟 ChatGpt 交叉比對建議,最後選擇了網頁做為載體,原因是音樂相關的處理透過網頁較容易,發發速度也較快,之前也有試著開發安卓App,並上架到google play,有興趣可以看一下我的練習作品,兩款加起來也投注了快半年的時間。
回到正題,由於之前沒有開發過網頁遊戲,我的第一個動作是快速驗證我要用到的技術框架的可行性,我快速的製作了一款測試音訊相關的應用程式,哼唱鴨鴨,花了我9天的時間,驗證pitchy、 tone.js、 vexflow 是否符合我的需求,學習zustand 做狀態控制,並簡單了解了tailwindcss的運作。

然後,接著是惡夢的開始,理想與現實有著差距,第一個構想是透過聲音控制子彈的發射,然而,面臨兩個問題 :
1.遊戲難度過高,其實人類很難準確地發出自己想要的絕對音高。
2.音訊輸入處理的判定十分困難,因為人的聲音其實是抖動的波形,提取出麥克風輸入時需要很複雜的技術。
後來只好簡化,改為用聲音輸入來切換武器跟升級武器,不過我仍然覺得不太滿意,所以沒有繼續開發,不過在其間也算是把聲音輸入的處理給練習了,額外多學了好幾個:Web Audio API、音高偵測Pitchfinder、Howler聲音播放
然後後端的部分我摸得不太熟,但也是在模糊的狀態下把功能正常做完,用的是.NET平台 Web API,然後搭配MySQL資料庫,選擇ORM 框架 EF Core,也因為有上傳分數功能,做了一個上傳分數的簡易Api。還額外學了 Jwt token,製作google登錄,之前在Unity開發也有做,其實感覺上較簡單,網頁的第三方登錄反而麻煩了一點,因為我做前後端分離,要先處理跨域協定,然後調整Program.cs。



接下來是本專案的程式部分啦
逃跑騎士-音樂之國的繼承者
程式-前端
延續之前練習使用的技術React + Tailwind,前端方面幾乎都是使用之前比較熟悉的,搭配學到的 eventemitter3 。
遊戲事件
這裡也採了一個坑,因為我是混合式,介面用react,遊戲運行用phaser,一開始事件用了只能在phaser起作用的自訂的GameEvents,然後用phaser內建的事件發射器,導致有些沒辦法透過zustand(zustand可以跨react跟phaser)的資訊很難傳遞,後來改用GlobalBusEventsg使用eventemitter3 之後,才能跨react跟phaser溝通,但由於此時代碼已經非常多,重構起來非常費力,學乖了一開始就用全局事件,不要用phaser內建的事件發射器,一開始多輕鬆,後面就多痛苦。
狀態管理
Zustand做非機密的狀態控制,一開始是資料都先存localStorage,等功能確定沒問題,再把重要資訊改為上傳到後端,這樣的話還能支援本地遊玩跟登入模式,不過這裡我也是採了個坑,一開始沒有把store分得較細,導致物品,卡片,裝備等等的store都跟玩家放在一起,維護起來蠻混亂的,建議一開始就都分開會比較好,雖然到了後端我仍然將他們都轉成不同的表格,但這樣看起來就會有點前後端不一致。
遊戲邏輯
基本上都是交由AI撰寫比較多,我的目標是讓他好維護,好看懂,所以我進良遵循自己領悟的設計原則
| 層級 (Layer) | 職責 (Role) | 物品系統 (Item System) | 任務系統 (Quest System) |
| 1. Data (數據) | 保存狀態 (State) | ItemStore (Zustand) – inventory: [] – gold: 100 | QuestStore (Zustand) – activeQuests: [] – completedIds: [] |
| 2. Logic (邏輯) | 規則運算 (Manager) | ItemManager – dropItem(loots) – consumeItem(id) SpawnManager (生成邏輯) | QuestManager – acceptQuest(id) – checkProgress(event) – completeQuest(id) |
| 3. View (介面) | UI 呈現 (React) | InventoryUI – 顯示格子與 Icon – 裝備介面 | QuestLogUI – 顯示任務列表 – 顯示進度條 (0/10) |
| 4. Entity (實體) | 遊戲內物件 (Phaser) | ItemEntity (Sprite) – 地上的掉落物 – ItemComponent (拾取行為) | (無專屬實體) – 但可能關聯到 NPC Entity (頭頂驚嘆號) |
介面(Interface)
定義這個東西是誰,要包含哪些東西,沒啥特別好說的,唯一要注意的是最好將行為跟數據的介面分開,除非你非常確定他不會再擴充,或是很簡單。
數據
這邊用了太多ts直接定義,導致後續維護很困擾,後來我學會一開始就使用excel做資料,然後自己寫個簡單的python轉json匯入使用。
管理器
基本上就是橋梁,可以負責溝通很多不同的東西,看你賦予給他的職責,通常物件的管理器,我還寫了生成的邏輯在內部,更細一點可以再拆出去。
而系統的管理器不生成,畢竟他也沒有物件,他負責調用store中的方法,實現更複雜的務邏輯運算,例如使用物品,管理器需要從ItemStore調用目前該玩家的物品數量,然後使用store中增加物品的方法,更新ItemStore狀態,然後讓ItemStore發送全局事件,負責同步後端的管理器執行上傳到後端的動作,然後因為ItemStore使用zustand技術的關係,UI就會直接更新了。
組件
這個是最一開始最煩的,因為Unity的組件跟UI的組件完全是不同的東西,可是他們英文都一樣(component),一開始很容易搞混,簡單來說:
Unity的component是可重複使用的功能,我都稱他為組件
在開發phaser遊戲過程中,我仍然是用了非常多組件,基本的健康組件,移動組件,攻擊組件,技能組件(搭配策略模式跟子類)。
UI的component是可重複的樣式,我都稱他為元件
然而,因為我在這邊偷懶的關係,很多UI重複的地方都沒有元件化,後續要改雖然也是AI改啦,但我發現AI其實比較能調整的是非元件化的代碼,雖然看起來很醜,但是AI直接修改樣式方面效果比較好,請他去修改元件通常出來會不如預期,不過元件化後人工比較好維護,也比較能達成一致性,但在一開始追求好看的樣式,我會覺得要先直接請AI寫代碼,然後再轉成元件,而不是直接寫元件套進去。
UI 介面
基本上Gemini能寫得很不錯,只要記得寫完後把喜歡的樣式轉成元件即可,不然後續要調整視覺一致性會想哭。
React Hooks 在遊戲開發中的角色
在本專案中,我採用 Zustand 進行全局狀態管理,並結合 React Hooks 來控制遊戲容器GameContainer的生命週期與 UI 互動。以下是關鍵的技術實作:
1. useEffect:遊戲生命週期的核心驅動
這是連接 React 與 Phaser 的橋樑。在 GameContainer 中,我利用 useEffect 來監聽 gameKey 或 levelKey 的變化:
- 啟動遊戲:當組件掛載 (Mount) 時,初始化
new Phaser.Game(config)。 - 銷毀清理:利用
useEffect的return清理函式,在頁面切換或組件卸載時自動呼叫game.destroy(true),防止記憶體洩漏或重複創建 Canvas。 - 事件監聽:在
MainGamePage中,用來訂閱GlobalBus的遊戲結束事件,確保 UI 能正確響應遊戲內部的邏輯。
2. useRef:持有遊戲實例 (重要)
在 GameContainer 中,我使用了兩個關鍵的 Ref:
gameRef:用來綁定 HTMLdiv元素,告訴 Phaser 遊戲畫面要渲染在哪裡。gameInstance:用來儲存 Phaser 的遊戲實體。因為 Phaser 實體不需要參與 React 的渲染流 (Render Flow),使用useRef可以讓我們操作遊戲物件而不會觸發 React 不必要的重新渲染。
3. useState:控制純 UI 的顯示邏輯
雖然大部分遊戲數據都在 Zustand 中,但對於「僅與當前頁面有關」的 UI 狀態,我仍使用 useState。
4. useCallback:穩定回調函式
用於優化效能與相依性管理。
- 例如:
handleNavigate函式。為了將它放入useEffect的依賴陣列中而不造成無窮迴圈,使用useCallback確保該函式的參照 (Reference) 保持穩定。
5. Zustand Hooks (useGameStore 等):取代傳統 Context
我沒有使用 React 內建的 Context API,而是使用 Zustand 生成的 Hooks(如 useGameStore, usePlayerStore)。
這讓我能在任何 UI 組件(如血條、任務列表)中直接讀取遊戲數據,且 Zustand 會自動處理效能優化,只有當訂閱的數值改變時,該組件才會重新渲染。
程式-後端
我後端真的就比較不熟悉了,現在屬於還在摸索的狀態,使用
| MySQL (資料庫) |
| .Net Web Api(ASP.NET Core) |
| ORM 框架 EF Core |
| Jwt (JSON Web Token) |
MySQL (資料庫)
這邊比較單純,就是建表,加上也有plgin可以直接裝在Vscode,有時候也可以直接操作,不過還是建議用語法,建比較快,雖然後面我就使用EF Core直接建了,定義好 C# 的類別 (Class),EF Core 就會自動幫你翻譯成 MySQL 的 SQL 語法並建立資料庫表,更快,但也有衍生一些問題,主要就是我常寫完class忘記更新EF Core要看的DbContext。
ORM 框架 EF Core
既然都提到資料庫就順便講這個,我覺得他很方便是會有類似專屬於資料庫的版本紀錄,會出現Migrations資料夾,我最早的一次是上一款聲音射擊遊戲,然後覺得很方便,就延到這款逃跑騎士了,一直到12月才建表處理後端。

.Net Web Api(ASP.NET Core)
我一開始覺得這個技術框架選用名稱超長,所以還得先瞭解這在幹嘛,他是
.NET: 表明這是基於 Microsoft 的 .NET 平台。
Web API: 指明是提供 HTTP 服務的 API 應用程式,而不是傳統的網頁應用程式 (MVC) 或其他類型的應用程式。
(ASP.NET Core): 強調使用的是 ASP.NET Core 這個現代、跨平台且高性能的 Web 框架,而不是舊版的 .NET Framework (例如 ASP.NET Web API 2)。
好,加起來就變超長,這邊的話我一開始都寫controller + 用剛才的 models就完事,一直到LinePay交易跟物品合成才把處理邏輯寫到service,導致前面的controller 都蠻腫的,應該要遵循“Skinny Controller, Fat Service” (瘦控制器,胖服務) 原則才對,然後拿資料我目前也是直接寫在controller ,但應該要寫在Repositories,這樣換資料庫才不會很痛苦。
目前就像是餐廳服務生controller 坎啜爾包辦所有工作,拿食材(Repositories),點/出菜,兼廚師(Service)>< 下個專案要改進。
Models: 給資料庫看的。
DTOs: 給前端看的。
上面兩者記得分開放,我一開始覺得他們都是資料結構就放一起,後面要全部搬。






發佈留言