Day29 完成了遺跡路線整理、Boss 戰後回報,以及背景音樂與音效系統。到這一步,遊戲主線已經可以從遺跡開始,經過村莊、森林、洞穴,最後擊敗 Boss 並回村回報。
所以 Day30 的目標不再是新增關卡或敵人,而是把整個專案整理成比較完整的遊戲作品。
這一天主要完成三件事。
第一,加入設定系統。玩家可以按 Esc 開啟暫停 / 設定頁面,在裡面調整音樂音量與音效音量,也可以繼續遊戲或退出遊戲。設定頁面打開時,遊戲會暫停,背景音樂也會暫停。
第二,建立遊戲首頁。打包後玩家點開遊戲時,不會直接進入遺跡或某個測試場景,而是先看到首頁。首頁有「繼續遊戲」、「新遊戲」和「退出遊戲」三個選項。
第三,整理 Build Settings,讓 MainMenuScene 成為打包後第一個啟動場景,並準備 Windows 版本打包流程。
前面 29 天大多是在做遊戲內容:角色、地圖、NPC、任務、商店、森林戰鬥、Boss 戰、音樂與音效。Day30 則是在補玩家真正打開遊戲時會遇到的入口與設定。
一、加入設定系統
Day30 一開始,我先要求加入設定系統。
這個設定系統要支援幾個基本功能:
按 Esc 開啟或關閉設定頁面。
打開設定頁面時暫停遊戲。
打開設定頁面時暫停背景音樂。
可以調整音樂音量。
可以調整音效音量。
可以繼續遊戲。
可以退出遊戲。
Codex 新增了設定系統主腳本:
SettingsMenuUI.cs

這個腳本放在:
Assets/Game/UI/Scripts/SettingsMenuUI.cs
它負責設定頁面的顯示、Esc 輸入、遊戲暫停、音量滑桿、繼續遊戲和退出遊戲。Codex 的對話紀錄中也明確提到,Day30 新增 SettingsMenuUI.cs,並修改 GameAudioManager.cs,讓設定可以調整音量、暫停遊戲、暫停音樂,並且設定 UI 會全域自動建立,不需要每個場景手動放物件。
遊戲裡已經有很多場景:遺跡、村莊、主角家、村長家、森林、酒館、武器匠鋪、洞穴道路、洞穴。設定頁面如果每個場景都手動放一次,很快就會出現某些場景忘記放、某些場景放兩個的問題。
所以這次把設定選單做成全域物件,載入場景後自動建立,並跨場景保留。玩家不管在哪個場景,只要按下 Esc,都能開啟設定頁面。
二、Esc 開啟暫停 / 設定頁面
進入 Play Mode 後,按下 Esc,畫面會出現暫停 / 設定頁面。

這個頁面中央有一個深色面板,背景會變暗,讓玩家知道目前不是正常遊玩狀態,而是暫停狀態。
設定頁面上方顯示:
遊戲設定
頁面中有兩條滑桿:
音樂音量
音效音量
玩家可以透過滑桿調整背景音樂與音效大小。音量旁邊也會顯示百分比,讓玩家知道目前大概調到多少。
頁面下方有兩個按鈕:
繼續遊戲
退出遊戲
按下「繼續遊戲」後,設定頁面會關閉,遊戲恢復。
按下「退出遊戲」後,在 Unity Editor 測試時會停止 Play Mode;正式打包後則會關閉遊戲。
設定頁面開啟時,遊戲會暫停。也就是說,玩家調整音量或考慮要不要退出時,不會被怪物繼續攻擊。這不是什麼革命性設計,只是基本禮貌,但遊戲開發很常需要把基本禮貌也寫成程式碼,真是人類工程學的悲哀。
這張圖就足夠代表 Day30 的設定頁面,不需要再另外拆成「音量滑桿一張」、「繼續遊戲一張」、「退出遊戲一張」
三、建立遊戲首頁
設定系統完成後,接著做遊戲首頁。
Day30 的第二個大目標是:打包後玩家點開遊戲時,不能直接進入測試場景,而是要先進入首頁。
Codex 新增了:
MainMenuScene.unity
main_menu_background.png
MainMenuController.cs

MainMenuScene.unity 是首頁場景。
main_menu_background.png 是首頁背景圖,放在:
Assets/Game/UI/Resources/Menu/main_menu_background.png
首頁背景圖用來建立遊戲氛圍。這個專案的主題包含遺跡、森林、洞穴和古樹 Boss,所以首頁不能只是空白背景加三個按鈕,那樣看起來太像剛開專案五分鐘的測試場景。
首頁是玩家打開遊戲後看到的第一個畫面。
它不一定要做得很華麗,但至少要讓玩家知道自己進入的是一個完整遊戲,而不是 Unity 測試工程。人類很容易被第一印象騙到,遊戲首頁就是專門利用這個弱點的地方。
四、首頁三個按鈕
首頁目前有三個按鈕:
繼續遊戲
新遊戲
退出遊戲

「繼續遊戲」會讀取最後進入過的遊戲場景,讓玩家從先前進度繼續。
「新遊戲」會重置資料,並進入 TutorialScene。
重置內容包含主線、背包、等級和 Boss 狀態。Codex 的對話紀錄中也確認,新遊戲會重置主線、背包、等級與 Boss 狀態。
「退出遊戲」則會關閉遊戲。
這三個選項是遊戲首頁最基本的功能。
有了首頁後,遊戲啟動流程就比較完整:玩家先進首頁,再選擇要繼續、重開或退出,而不是一打開就被丟進遺跡。直接丟進場景也不是不能玩,只是很像開發者忘了前門在哪。
首頁也會沿用安全區背景音樂,讓玩家進入遊戲時不會是完全安靜的畫面。這部分由 GameAudioManager.cs 的場景音樂規則處理,不另外截圖,因為靜態圖片無法證明音樂正在播放。
五、整理 Build Settings 場景順序
首頁做好後,下一個重點是 Build Settings。
如果要讓玩家打開遊戲時先看到首頁,就必須把 MainMenuScene 放到 Build Settings 的第一個場景,也就是 index 0。

Day30 修改了:
ProjectSettings/EditorBuildSettings.asset
把:
Assets/Scenes/MainMenuScene.unity
放到第一個場景。
目前打包場景順序整理為:
六、打包前設定
接著進入打包前設定。
這次我不讓 Codex 直接打包,而是自己用 Unity 內建流程打包。Codex 只負責手把手說明步驟。
Unity 上方選單打開:
File > Build Profiles...
如果 Unity 版本顯示舊名稱,就找:
File > Build Settings...

平台選擇 Windows 或 PC Standalone。
Architecture 選擇:
x86_64
Player Settings 建議確認:
Company Name
Product Name
Default Screen Width:1280
Default Screen Height:720
Full Screen Mode:Windowed 或 Fullscreen Window
接著回到 Build Profiles 或 Build Settings,點:
Build
不要點 Build And Run。
輸出資料夾建議建立:
D:\Ithome\IronRPG_Build\Day30_Final
Codex 的打包教學也提到,要確認平台是 Windows、架構是 x86_64,場景列表第一個是 MainMenuScene,然後選一個新的資料夾輸出 Build。
這一步代表專案開始離開 Unity Editor 的保護區。Play Mode 能跑只代表 Editor 裡能跑,打包成 exe 後才比較接近玩家真正會開到的狀態。
七、打包後輸出資料夾
打包完成後,輸出資料夾裡會看到遊戲執行檔與資料夾。

輸出資料夾範例:
D:\Ithome\IronRPG_Build\Day30_Final
裡面應該會有:
IronRPG_2D.exe
IronRPG_2D_Data
以及其他 Unity 執行時需要的檔案。
打包完成後,不能只看 Unity Console 沒報錯就結束。
還要直接雙擊 IronRPG_2D.exe 測試。
測試重點包括:
是否進入首頁。
新遊戲是否能進入 TutorialScene。
繼續遊戲是否能讀取最後場景。
設定頁面是否能暫停和調音量。
場景切換是否正常。
背包、戰鬥、音效是否有反應。
如果打包時出現紅色錯誤,就要看 Console 最上面的第一條紅字。通常第一條才是主因,後面那些只是跟著崩潰的群眾,不必一開始就全看。
八、程式碼重點:SettingsMenuUI.cs
SettingsMenuUI.cs 是 Day30 新增的設定頁面控制腳本。
它負責自動建立設定 UI、讀取 Esc 輸入、暫停遊戲、暫停音樂、繪製設定面板、調整音樂與音效音量,以及處理繼續遊戲和退出遊戲。

第 5 到 19 行是設定頁面的基本欄位。
PanelWidth 和 PanelHeight 決定設定面板大小。
instance 用來做單例,確保全場只有一個設定頁面控制器。
overlayStyle、panelStyle、titleStyle、labelStyle、buttonStyle 是 UI 樣式。
overlayTexture 和 panelTexture 是背景遮罩與面板貼圖。
previousTimeScale 用來記住打開設定前的遊戲速度。
IsOpen 用來記錄設定頁面是否開啟。
這段不直接處理操作,而是先準備設定頁面需要的資料。

第 21 到 48 行負責自動建立設定選單,並避免重複。
第 21 到 25 行使用:
RuntimeInitializeOnLoadMethod
讓場景載入後自動執行 Initialize()。
第 27 到 39 行的 EnsureInstance() 會檢查 instance 是否存在。
如果已經存在,就直接回傳。
如果不存在,就建立新的 GameObject,掛上 SettingsMenuUI,並使用:
DontDestroyOnLoad(settingsObject)
讓它跨場景保留。
第 41 到 48 行的 Awake() 則負責防止重複物件。
如果場景裡已經有其他 SettingsMenuUI,新的那個就會被刪除。
這樣設定頁面不需要每個場景都手動放一次,它會自動建立,也會跨場景保留

第 50 到 72 行負責 Esc 輸入與開啟條件。
Update() 先取得目前鍵盤:
Keyboard.current
如果沒有鍵盤,就直接 return。
接著判斷玩家是否在這一幀按下 Esc:
keyboard.escapeKey.wasPressedThisFrame
如果設定頁面已經開啟,就呼叫 CloseSettings() 關閉。
如果設定頁面尚未開啟,並且 CanOpenSettings() 回傳 true,就呼叫 OpenSettings()。
第 67 到 70 行的 CanOpenSettings() 會確認背包沒有開啟,且目前不是敵人暫停狀態。
這樣可以避免 UI 疊在一起。

第 74 到 98 行負責開啟與關閉設定。
OpenSettings() 會先確認設定是否已經開啟。
如果還沒開啟,就記住目前的 Time.timeScale,接著把:
Time.timeScale = 0f
讓遊戲暫停。
然後呼叫:
GameAudioManager.SetMusicPausedBySettings(true)
讓背景音樂也暫停。最後把 IsOpen 設成 true。
CloseSettings() 則做相反的事。
它會還原 Time.timeScale,並呼叫:
GameAudioManager.SetMusicPausedBySettings(false)
讓背景音樂恢復。最後把 IsOpen 設成 false。
這段是暫停頁面的核心。設定頁面不是單純蓋在畫面上,而是真的暫停遊戲狀態。

第 100 到 146 行負責畫出設定頁面。
OnGUI() 只有在 IsOpen 為 true 時才會執行。
接著呼叫 EnsureStyles(),確保 UI 樣式已建立。
然後程式會先畫出全畫面的半透明背景遮罩,再計算中央面板位置,畫出設定面板。
設定面板裡依序顯示:
遊戲設定
音樂音量滑桿
音效音量滑桿
繼續遊戲 按鈕
退出遊戲 按鈕
Esc:關閉設定並繼續遊戲
按下「繼續遊戲」會呼叫 CloseSettings()。
按下「退出遊戲」會呼叫 QuitGame()。
這裡使用 OnGUI(),不是 Unity UI Canvas。
做法比較簡單直接,適合 Day30 收尾時快速做出可用設定頁面。

第 148 到 160 行是 DrawVolumeSlider()。
這個方法會畫出一條音量滑桿。
左邊顯示標籤,例如 音樂音量 或 音效音量。
中間是 GUILayout.HorizontalSlider。
右邊顯示目前百分比。
如果滑桿的新值和舊值不同,就呼叫傳入的 setter。
音樂音量會呼叫:
GameAudioManager.SetMusicVolume
音效音量會呼叫:
GameAudioManager.SetSfxVolume
這樣兩條滑桿共用同一個方法,不需要重複寫兩份幾乎一樣的程式。

第 162 到 168 行是 QuitGame()。
在 Unity Editor 裡,按下退出會執行:
UnityEditor.EditorApplication.isPlaying = false
也就是停止 Play Mode。
在正式打包後,則會執行:
Application.Quit()
關閉遊戲。
這裡用條件編譯區分 Editor 和正式版本。
因為 Editor 測試時不能用正式 exe 的退出方式處理。


第 170 到 243 行負責 UI 樣式與資源清理。
EnsureStyles() 會建立半透明黑色遮罩、深色面板、金黃色標題、白色文字和粗體按鈕樣式。
這些樣式讓暫停頁面看起來比較清楚。
如果直接把文字貼在遊戲畫面上,很容易被背景吃掉。加上遮罩和面板後,玩家比較能明確知道目前在設定頁面。
後面的 MakeTexture() 會建立 1x1 的 Texture2D,作為遮罩與面板背景。
OnDestroy() 則會在物件銷毀時清掉 overlayTexture 和 panelTexture,避免動態建立的資源殘留。
雖然這些貼圖很小,但該清還是要清。
九、程式碼重點:GameAudioManager.cs
Day30 也修改了 GameAudioManager.cs,讓它支援設定頁面的音量控制、PlayerPrefs 儲存,以及設定頁面開啟時暫停背景音樂。

第 14 到 17 行新增音量設定常數:
MusicVolumeKey
SfxVolumeKey
DefaultMusicVolume
DefaultSfxVolume
MusicVolumeKey 和 SfxVolumeKey 是寫入 PlayerPrefs 時使用的 key。
DefaultMusicVolume 和 DefaultSfxVolume 則是第一次啟動遊戲時使用的預設音量。
第 32 到 33 行新增:
musicVolume
sfxVolume
這兩個欄位保存目前音樂音量與音效音量。
一開始會使用預設值,之後會從 PlayerPrefs 讀取玩家保存的設定。
這段讓音量設定不再只是 UI 上的數字,而是音訊管理器裡真正保存的狀態。玩家調過的音量可以留下來,不會每次開遊戲又回到原點。

第 66 到 82 行新增給設定頁面呼叫的公開入口:
MusicVolume
SfxVolume
SetMusicVolume(float volume)
SetSfxVolume(float volume)
SetMusicPausedBySettings(bool paused)
MusicVolume 和 SfxVolume 讓設定頁面可以讀取目前音量。
SetMusicVolume() 和 SetSfxVolume() 讓設定頁面可以修改音量。
SetMusicPausedBySettings() 則讓設定頁面打開時暫停背景音樂,關閉時恢復。
這樣 SettingsMenuUI.cs 不需要直接碰 AudioSource。
它只要呼叫 GameAudioManager 提供的方法即可。UI 管 UI,音訊管理器管音訊。這種分工很普通,但普通得很正確。程式裡最可怕的就是每個東西都想管所有東西,像一場沒有議程的會議。



第 143 到 146 行負責初始化時讀取音量設定。
GameAudioManager 會從 PlayerPrefs 讀取音樂音量與音效音量,然後套用到:
musicSource.volume
sfxSource.volume
第 238 到 254 行新增實際寫入音量的方法:
SetMusicVolumeInternal(float volume)
SetSfxVolumeInternal(float volume)
這兩個方法會把音量限制在 0 到 1 之間,更新內部欄位,套用到 AudioSource,並寫入 PlayerPrefs。
第 256 到 277 行新增:
SetMusicPausedBySettingsInternal(bool paused)
設定頁面開啟時,會呼叫:
musicSource.Pause()
設定頁面關閉時,會呼叫:
musicSource.UnPause()
必要時也會重新播放音樂。
這代表 Day30 的設定頁面不是只改 UI。
音量滑桿會真的改變 AudioSource 音量,音量會保存,設定頁面打開時背景音樂也會暫停。
十、Day30 小結
Day30 完成後,遊戲已經具備正式遊戲入口與基本設定系統。
目前達成狀態如下。
新增 SettingsMenuUI.cs 設定系統。
玩家可以按 Esc 開啟暫停 / 設定頁面。
設定頁面開啟時,遊戲會暫停。
設定頁面開啟時,背景音樂會暫停。
設定頁面可以調整音樂音量。
設定頁面可以調整音效音量。
音量設定會保存到 PlayerPrefs。
設定頁面提供「繼續遊戲」與「退出遊戲」。
設定 UI 會全域自動建立,不需要每個場景手動放。
新增 MainMenuScene 作為遊戲首頁。
首頁有「繼續遊戲」、「新遊戲」、「退出遊戲」。
新遊戲會重置主線、背包、等級與 Boss 狀態。
首頁背景圖已加入。
Build Settings 已把 MainMenuScene 放在第一個場景。
打包場景列表已整理,移除不存在場景。
Windows 打包流程已確認。
打包後會產生 exe 和資料資料夾,方便直接執行測試。
Day30 到這裡,整個 30 天專案進入正式收尾。
前面做的是遊戲內容,Day30 做的是玩家真正啟動遊戲時會遇到的入口和設定。
Github:https://github.com/Moriartyhung/IronRPG_2D