自從上次更新 ECS Survivors 專案已經過了很長一段時間。生活總會有各種干擾,或者說我有點懶散。但終於,我要向大家介紹 ECS Survivors 新增的閃亮功能!

在本系列的這一部分,我將介紹過去七個月內分四個「部分」加入的多項系統與功能。我的記憶可能無法回想起每次更新的細節,因此這次將以較表層的方式說明,而非像之前文章那樣深入探討。廢話不多說,現在就揭開這層覆滿灰塵的窗簾!(最近讀了很多托爾金的作品,對他的意象、隱喻和明喻讚嘆不已,想把這種風格融入我的隨筆中,請見諒😄)

我想為遊戲注入一些生命力;到目前為止,背景只是簡單的灰色。Tilemap 是大多數 2D 平台遊戲、俯視冒險、求生遊戲等的基礎。在這裡,我不想重新發明輪子,打造一個過度設計的關卡編輯工具。Tiled 是一款很棒的工具,可以快速製作複雜關卡並嵌入元資料。辨識可碰撞物件(例如地圖邊界)非常簡單。我使用 tmxlite 函式庫載入 tilemap 檔案。早期,我為原始檔案中指定的每個圖塊建立一個新的 tile 實例,並逐一繪製。碰撞體的邏輯也相同;我為每個帶有元資料的 tile 建立一個盒狀碰撞器。

細心的讀者會發現這兩種實作都存在效能不佳的問題。

之前:每個 tile 都有一個碰撞器(30 個碰撞器)| 現在:碰撞器合併(4 個碰撞器)

目前碰撞運作良好,但當遊戲難度提升,加入數千個敵人時,現有的二次方碰撞偵測與解決方式將無法負荷。因此,我們加入一種簡單且高效的加速方法——空間雜湊網格。簡單來說,現在偵測一個實體的碰撞時,會與所有其他實體進行檢查(n x n)。使用空間雜湊網格後,區域被分割成多個格子,偵測碰撞時只需檢查鄰近格子內的實體。推薦參考 Ericson 的《Real-Time Collision Detection》(2004)第7.1章「均勻網格」。除了加速結構帶來的效能提升外,這次更新沒什麼其他可說的。我們從 600 個碰撞器提升到 7000 個,效能提升超過 10 倍!

我們在做遊戲,當然需要一些進度機制。求生類遊戲的典型設計是,玩家透過獲取經驗資源或擊殺敵人累積經驗,升級時可從三個隨機強化中選擇。我決定只透過擊殺敵人獲得經驗,且目前玩家升級時的選擇是固定的。未來隨著更多技能與強化方式加入,這可能會改變。這很簡單,我使用自己打造的 GUI 框架,完成了玩家成長系統。

這部分花了我最多時間,也讓我有些疲憊。完成升級系統後,我想加入更多遊戲玩法,特別是我之前玩過的程序生成(參見我另一篇文章),想把它引入遊戲。但要做到這點,需要一個工具來建立程序生成規則。為了打造這個工具,我必須再次重構程式碼,讓遊戲和其他應用共用同一個視窗管理器。你可能會發現這是一個無止盡的需求螺旋,導致程式碼頻繁大改。無論如何,我花了很長時間思考如何將程式碼妥善分層架構,以下是重點說明。

首先,我想重新整理檔案結構,因為原本有點混亂,主要是為了能產生不同的函式庫與建置目標。這讓我能在必要時為每個模組建立獨立的 GitHub 倉庫,保持專案分離,避免過度耦合。這也讓從不該使用的模組匯入變得更困難,因為目標連結在 CMake 檔案中定義。例如,無法在輸入模組中包含渲染模組的檔案。唯一的限制是我可能因懶惰而想偷懶包含不該用的模組。

接著,我覺得用相同的模組重構出獨立應用程式是個好主意。我建立了一個基底 Application 類別,包含 init() 和 run() 函式。這個類別不依賴 Raylib,因此可用作無頭應用,例如未來可能的網路伺服器功能。我也有一個 GraphicalApplication 類別,基本相同但包含 Raylib 用於視窗管理。基於此,我建立了 ECS-Survivors 原始遊戲、ECS-Survivors 編輯器,以及一個基本的無頭應用,主要用於概念驗證。

你可能注意到截圖中有格式化的主控台日誌。每個好的編輯器都需要日誌功能,因此我做了一個簡單的日誌系統。這個日誌器是單例模式,可以註冊「sink」,即匿名函式處理日誌訊息與其他資訊。基本的 Application 預設會輸出到主控台(init() 預設實作)。編輯器則可新增另一個 sink,將日誌輸出到底部的文字區塊。

還有許多改動我未在本文詳述,未來也不會全部說明。我嘗試挑戰過多,尤其是後期更新,導致整體進度放緩,也忘記了許多小細節。最糟的是我還有些清理工作沒完成,但我認為 ECS-Survivors 已經足夠穩定,可以開始開發新功能。這個專案似乎永遠有無盡的支線任務,這很好,但展望未來,我必須專注於一件事。下一步是什麼?我還不確定,但很想專注於遊戲玩法元素,也許加入近戰攻擊會是不錯的選擇。簡單明確的目標,哈哈。

一如既往,最新可遊玩版本可在 Itch.io 下載,原始碼則在 GitHub 上公開。下次見!