線上實測與發布產物: 專案已成功部署至 itch.io,可在瀏覽器中直接運行實測:
https://jasonhung.itch.io/obsidiantrial

除了 Web 版本外,同一個 Godot 專案也順利產出了 Android (.apk) 與 iOS (.ipa) 的發布檔,並完成實機安裝與運行驗證。
這次跨平台發布最值得紀錄的一點:
遊戲程式碼並未針對這三個平台進行任何調整。
整體開發時間主要集中在平台專屬設定、環境建置,以及各平台的規範配置上。
到昨天為止,《異世界救援》主要還是在我的電腦裡跑。
雖然到目前為止,畫面還很陽春。
不過今天目標是驗證同一個 Godot 專案發布至三個不同平台的可用性。
相關發布產物與驗證情境如下:
| 平台 | 產物 | 這次的用途 |
|---|---|---|
| Web | index.html + .wasm + .pck |
瀏覽器直接玩 |
| Android | .apk |
實際裝進 Android 手機 |
| iOS | .ipa |
實際裝進 iPhone, iPad |
測試結果: 三個平台皆順利完成發布與實機驗證。
這次跨平台發布的經驗顯示:
AI 適合執行自動化匯出流程,但「目標部署路徑與交付物內容」,仍須人工審核與確認。
當專案從單機開發進入對外部署階段,作業範疇將擴展至發布憑證、數位簽章、帳號授權與公開網域託管:
在進行 Godot 跨平台發布時,必須先釐清兩項功能相似但權責不同的核心概念:
| 是什麼 | 從哪裡來 | 存在哪裡 | |
|---|---|---|---|
| 匯出樣板(export template) | Godot 官方預先編好的引擎執行檔 | 官方伺服器下載 | 系統使用者目錄(所有專案共用) |
| 匯出預設(export preset) | 專案專屬的平台發布設定(如 App 名稱、架構、圖示、簽章等) | 開發者自行建立 | 專案裡的 export_presets.cfg |
簡單講:
於 Godot 編輯器中點選 「專案 → 匯出」,點擊左上角 「新增...」 即可建立 Web、Android、iOS 等平台的發布預設。

新增後,各平台將擁有獨立的配置集:

在此面板中,有三項影響 CLI 自動化與部署發布的關鍵設定:
在 CLI 執行自動化建置時,指令會參照此處定義的名稱:
godot --headless --export-release "Web" build/web/index.html
指令中的 "Web" 即為此組預設的命名(可依專案需求自訂)。
定義編譯產物的指定放置位置。將 Web 版路徑明確隔離為:
export_path="build/web/index.html"
此設定能確保發布產物獨立存在,避免後續 CLI 自動部署時誤將其他平台的安裝檔一併打包。
本專案維持預設關閉。此設定可避免 Web 版發布後需要額外配置伺服器端的 SharedArrayBuffer、COOP(Cross-Origin-Opener-Policy)與 COEP(Cross-Origin-Embedder-Policy)等跨來源隔離標頭。
除了「匯出預設」外,另一個必須先行配置的元件為「匯出樣板」。
設定位置於:「編輯器 → 管理匯出樣板...」:

透過點擊 「下載並安裝(Download & Install)」 或 「Install All Templates」,即可一次性下載各平台所需的編譯預設環境(容量約 1GB)。

安裝完成後,可至系統使用者目錄下檢查相關檔案(以 macOS 為例):
ls ~/Library/Application\ Support/Godot/export_templates/4.7.1.stable/
會看到目錄內容將包含各平台的二進位檔與壓縮包像:
android_debug.apk
android_release.apk
android_source.zip
ios.zip
web_debug.zip
web_release.zip
web_nothreads_debug.zip
web_nothreads_release.zip
在配置匯出樣板時,有兩項影響自動化建置的重要規則:
版本字串必須完全一致
樣板目錄名稱(如 4.7.1.stable)必須與執行的 Godot CLI 版本(godot --version)完全匹配,否則 CLI 建置時將無法正確載入對應樣板。
跨平台樣板需預先完整安裝
若系統僅安裝 Web 平台的樣板,後續進行 Android 或 iOS 匯出時,建置流程將因「找不到目標平台樣板」而直接中斷。因此在進行多平台 CLI 自動化發布前,須確保所有目標平台的樣板皆已安裝完畢。
Web 是三個平台裡最直接的一個。
mkdir -p build/web
godot --headless --export-release "Web" build/web/index.html
python3 -m http.server 8060 --directory build/web
然後瀏覽器打開:
http://localhost:8060
先在本機確認。
mkdir -p 不能省,Godot 不會幫你建立不存在的輸出目錄。
這次產出的主要檔案有:
| 檔案 | 大小 | 用途 |
|---|---|---|
index.wasm |
38MB | Godot 引擎本體 |
index.pck |
9.5MB | 遊戲內容 |
index.js |
273KB | Web 端的支援程式 |
這裡有一個跟 Day 09 很像的細節:--export-release 的 exit code 是有意義的。
成功是 0,失敗是 1。
所以它可以放心放進自動化腳本。
這跟 Day 09 的 --check-only 不一樣。
同一個 Godot 執行檔,不同功能的 exit code 行為可能不同,不能因為之前踩過一個坑,就把所有 CLI 都當成同一種規則。
這次有兩個設定,編輯器裡看起來都正常,匯出後才會知道有沒有設對。
[gui]
theme/custom_font="res://assets/fonts/NotoSansTC-Regular.otf"
如果沒有設定,匯出版的中文可能全部變成空白方框。
原因很簡單:
在 macOS 編輯器裡,Godot 可以借用作業系統的中文字型。
但 Web 和手機沒有這個環境可以借。
所以本機看起來正常,不代表匯出後也正常。
所有中文都顯示成空白方框:
中文正常顯示:
這我第一次撞到的時候,沒有立刻處理。
因為當時遊戲還是灰盒,美術和字型都還沒定案。
我只先確認:
知道是字型原因就好,目前不會阻止匯出流程。
等到後面確定視覺風格,再換正式字型。
這樣比較不會在還沒決定的東西上先花時間。
另一個設定是:
[internationalization]
locale/include_text_server_data=true
預設是 false。
少了它,中文可能會出現奇怪的斷行,例如:
失去 3
點生命,本場戰
鬥 +3 攻擊力。
第一個反應很容易是去改 autowrap_mode。
我當時也這樣想。
所以我把同一段文字用四種設定並排測試,包括:
WORD_SMART
ARBITRARY
language="zh"
結果四種換行都一樣,而且桌面版本來就是對的。
接著又懷疑是不是手機把卡片壓窄了。
量尺寸:
Card 實際大小 = (200.0, 260.0)
DescLabel 大小 = (160.0, 176.0)
字級 = 21
行數 = 4
尺寸也沒有變。
最後才確認:
問題在匯出版沒有帶完整的 ICU 斷行資料。
這次我就直接記下來:
如果匯出後中文斷行怪怪的,先查
include_text_server_data,不要先去改autowrap_mode。
代價是包體多約 4.4MB。
另外,匯出樣板裡還有一個容易忽略的:
常見 → ICU Data
我把它也裝起來。
匯出時原本看到:
使用編輯器內嵌文字伺服器資料,若匯出範本使用不同 ICU 版本建置,
匯出專案中的文字顯示可能會異常
安裝後變成:
Using text server data from export templates.
這裡重新匯出後做差分比對,檔案位元組完全沒有改變。
因為目前 Godot 版本相同,編輯器內嵌的 ICU 資料和匯出樣板裡的本來就是同一份。
所以這不是「裝完就修好一個 bug」。
它比較像是把未來升級 Godot 時可能出現的版本差異固定住。
Android 前置需要:
| 需要 | 怎麼確認 |
|---|---|
| JDK 17 以上 | java -version |
| Android SDK | ls ~/Library/Android/sdk/build-tools |
| debug keystore | ls ~/.android/debug.keystore |
Android SDK 和 Java 的路徑要填在 Godot 的編輯器設定裡,不是專案設定。
確認後建立 Android 匯出預設,至少調整:
| 欄位 | 值 |
|---|---|
package/unique_name |
例如 com.你的名字.遊戲名 |
version/name |
0.1.0 |
architectures/arm64-v8a |
開 |
screen/immersive_mode |
開 |
然後:
godot --headless --export-debug "Android" build/ObsidianTrial.apk
adb install -r build/ObsidianTrial.apk
adb shell monkey -p com.你的名字.遊戲名 -c android.intent.category.LAUNCHER 1
adb exec-out screencap -p > shot.png
最後一行很實用。它可以直接把手機畫面抓回電腦,之後要檢查或交給 AI 看都很方便。
我另外檢查了 APK 裡到底裝了什麼:
aapt2 dump badging build/ObsidianTrial.apk \
| grep -E "^package|application-label|native-code"
實際輸出:
package: name='com.jasonhung.obsidiantrial' versionCode='1' versionName='0.1.0'
application-label:'黑曜石之試煉'
native-code: 'arm64-v8a'
再確認簽章:
apksigner verify --print-certs build/ObsidianTrial.apk
這次還順手確認了一件事:
aapt2 dump permissions build/ObsidianTrial.apk
結果是空的。這是一個單機離線遊戲,確實沒有需要額外權限。
我原本以為手機版還要另外處理一套觸控程式碼。
結果不用。
Godot 的:
emulate_mouse_from_touch = true
預設就是開啟的,所以 Button 的 pressed 可以直接收到手指點擊。
這次唯一跟滑鼠有關、到了手機失效的,是 tooltip_text。
因為手機沒有 hover。程式不會壞,只是那個功能自然不會觸發。
iOS 平台的發布環境須具備以下前置條件:
首先列出本機可用的簽章身分:
security find-identity -v -p codesigning
接著解析目標憑證的詳細資訊:
security find-certificate \
-c "Apple Development: 你的名字 (ABCDE12345)" \
-p \
| openssl x509 -noout -subject
解析輸出範例:
subject= /C=TW/O=.../OU=XXXXXXXXXX/CN=...
其中 OU= 後方的 10 位字串(如 XXXXXXXXXX)即為 Team ID。
我第一次就是拿錯括號裡那組去填。
所以這裡不要看錯。
iOS 發布機制對 App 圖示有強制規範:
[application]
config/icon="res://assets/images/icon.png"
需準備一張 1024×1024 像素 的 PNG 圖片,Godot 在編譯時會自動裁切與產生各尺寸所需的圖示檔。
這就是附錄裡第四個坑。
godot --headless --export-debug "iOS" build/ios/App.xcodeproj
Godot 除了產生 App.xcodeproj 專案檔外,會自動呼叫後續的 xcodebuild 流程,最終於指定目錄生成以下產物:
build/ios/App.xcodeproj # Xcode 專案檔
build/ios/App.xcarchive # 編譯歸檔包
build/ios/App.ipa # iOS 安裝套件
取得產物後,可使用 Xcode 的 devicectl 工具將應用程式安裝至實機:
# 1. 查詢已連接的實機 UUID
xcrun devicectl list devices
# 2. 將產物安裝至指定的實機
xcrun devicectl device install app --device <UUID> \
build/ios/App.xcarchive/Products/Applications/App.app
# 3. 啟動應用程式
xcrun devicectl device process launch --device <UUID> <bundle-id>
在測試過程中,Apple Silicon(M 晶片) Mac 上的 iOS 模擬器測試路徑無法運行。
後來確認,Godot 官方提供的 iOS 模擬器樣板僅包含 x86_64 架構,而 Apple Silicon 的模擬器環境要求 arm64 架構。因此在 Apple Silicon 環境下,發布驗證須直接採用 iOS 實機進行。
在三個平台的發布過程中,Web 版的上線授權是資安與路徑控制最關鍵的一環。
itch.io 提供了官方 CLI 工具 butler,驗證流程如下:
butler login
執行後會開啟瀏覽器進行 OAuth 授權,憑證將安全保留於本機環境,無須將帳密交由 AI 處理。登入後確認版本:
butler version
接著於 itch.io 後台建立專案並完成基礎配置:
Kind of project 選 HTMLVisibility 改成 PublicAI 最初提供的上傳指令如下:
butler push build 你的帳號/專案代號:html
在執行前,經確認 butler push 的第一個參數屬性為「指定目錄且會進行遞迴上傳(Recursive Upload)」。
檢視當時的 build/ 目錄結構:
build/
├── index.html、index.wasm、index.pck ... 網頁版,48MB
├── ObsidianTrial.apk Android 安裝檔
└── ios/ Xcode 專案、xcarchive、ipa,578MB
整體目錄大小達 626MB,其中 578MB 為非 Web 相關產物,且包含簽章後的二進位檔。若直接將整個 build/ 推送至公開頁面,將引發以下三個層面的問題:
資安與敏感資料外洩
ios/ 目錄內包含 Xcode 專案檔、編譯產物與簽章組態。將其暴露於公開網站,可能導致憑證設定或內部測試環境的資安漏洞。
無謂的上傳成本與頻寬浪費
Web 版實際檔案僅 48MB,若連同 Android/iOS 的產物一併打包,上傳體積將暴增至 626MB(高出 13 倍),大幅延長部署時間。
網頁載入與部署異常
itch.io 的 HTML5 播放器會直接讀取根目錄檔案,混雜大型二進位檔(.apk、.ipa)容易導致解壓縮超時或檔案讀取異常。
最終處理方案
最終將 Web 版的輸出路徑獨立隔離:
export_path="build/web/index.html"
並僅針對 Web 專屬目錄進行推送:
butler push build/web 你的帳號/專案代號:html
透過發布路徑的獨立隔離,在無需修改遊戲程式碼的前提下,精確規避了潛在的資安與部署風險。
Web 版編譯產物的檔案總和約為 47.48MB,但實際在線上環境(如 itch.io CDN)部署時,伺服器會啟用 HTTP 傳輸壓縮(gzip / brotli)。
透過 curl 量測 itch.io CDN 的實際傳輸數據:
curl -s -o /dev/null --compressed \
-w "傳輸 %{size_download} bytes 時間 %{time_total}s\n" \
https://你的遊戲網址/index.wasm
Web 部署產物傳輸數據對比表
| 檔案 | 磁碟大小 | 實際傳輸 | 壓縮後 |
|---|---|---|---|
index.wasm |
37.68MB | 10.12MB | 73% |
index.pck |
9.53MB | 7.75MB | 19% |
index.js |
0.27MB | 0.07MB | 74% |
| 總計 | 47.48MB | 17.95MB | 62% |
實測顯示,玩家首次載入遊戲時的實際網路下載量約為 17.95 MB,整體減少了 62% 的傳輸負擔。
資源壓縮效能與 ICU Data 分析
此傳輸數據亦提供了更精準的資源優化方向:
程式碼與 WebAssembly 壓縮效益高
index.wasm 與 index.js 屬於文字與二進位程式碼結構,經過 CDN 傳輸壓縮後,體積降至原有的 26–27%。
多媒體與套件檔壓縮效益有限
index.pck 包含遊戲素材與 ICU 語系資料(約占 4.4MB)。由於 .pck 內部資料在打包階段已進行過一次壓縮,在 CDN 網路傳輸時無法再大幅二次壓縮(僅有 19% 壓縮率),幾乎需以原始體積全額傳輸。
在評估 Web 版載入效能時,應以 HTTP 實際傳輸量(Transfer Size) 作為指標,而非單純觀察本機檔案系統的靜態體積。
檢視 Web、Android 與 iOS 三個平台的發布過程,專案並未針對個別平台重構遊戲核心程式碼。
在此架構下,自動化工具與人工審核的職責範疇可劃分如下:
必須由開發者人工審核的項目:
以 butler push 為例,AI 提供的指令語法本身並無錯誤,關鍵在於開發者必須識別「將整份目錄作為參數傳遞」所隱含的部署行為。
這與 Day 06 審視 git init 的核心邏輯一致:
凡涉及資料導出、本機環境變更或對外部署的指令,皆須進行邊界審核。
釐清這道界線後,即可在大幅提升自動化效率的同時,精確控管系統的營運與資安風險。
專案成功部署至 Web、Android 與 iOS 三個平台後,開發的重點也從「編輯器內的邏輯實現」轉移至「真實環境的發布細節」。
實際發布時面臨的挑戰,包含:
這些範疇與遊戲核心邏輯(如 BattleScene.gd)屬於截然不同的維度。這也印證了自動化流程中「監督」的必要性:
AI 能夠高效執行指令,但開發者仍須清晰識別指令的影響範圍——究竟是在「修改本機環境」,還是在「對外發布資料」。
在執行 butler push 前花費十秒檢查參數,成功避免了 578MB 敏感檔案的誤上傳。在自動化發布流程中,這類人工審核的成本極低,卻能有效防範重大部署風險。
下一步:Day 20
發布流程驗證完畢後,下一個階段將回歸使用者體驗。
接下來將進行無盲測引導的真人實機測試:把遊戲交付給完全未接觸過說明的玩家,觀察遊戲機制與引導設計是否符合預期。
上面是完整流程。
下面留下這次實際撞到的六個坑,之後自己或其他人再遇到,可以直接回來查。
Android 匯出時:
ERROR: 在預期路徑找不到匯出範本:
.../export_templates/4.7.1.stable/android_debug.apk
原因就是第一次安裝樣板時只勾了 Web。
回到「編輯器 → 管理匯出樣板」重新下載完整包即可,不需要先移除舊的。

匯出成功、遊戲也能操作,但中文全部變成方框。
數字和 HP 這些英文正常。
原因是編輯器在 macOS 上可以借用系統中文字型,但瀏覽器沒有同樣的環境。
設定專案字型後就正常。

這也是我第一次刻意「先確認原因、晚一點再修」的例子。
當時遊戲還在灰盒階段,字型風格還沒決定。
知道原因就夠了。

修正前會出現:
失去 3
點生命,本場戰
鬥 +3 攻擊力。
我先懷疑 autowrap_mode。
測了四種設定,結果一樣。
再懷疑卡片是不是被手機壓窄。
量 Card.tscn:
Card 實際大小 = (200.0, 260.0)
DescLabel 大小 = (160.0, 176.0)
字級 = 21
行數 = 4
也沒變。
最後才找到 include_text_server_data。
缺少 ICU 斷行資料,中文被當成一個沒有斷點的長字,才會出現這種結果。

這個坑有一個很容易誤判的地方:
它不是全部都壞。
例如另一張卡前後斷行剛好都落在合法位置,所以看起來完全正常。
只測一張卡,很可能就會得到「沒問題」的結論。
Android 沒有設定專案圖示時:
ERROR: No project icon specified.
看起來很嚴重。
但 APK 還是成功產出,只是使用 Godot 預設圖示。
iOS 則不同:
ERROR: 匯出圖示: Invalid icon (icons/settings_58x58): ''
ERROR: Project export for preset "iOS" failed.
直接失敗。
所以不能只看到 ERROR 就猜它在所有平台都會有同樣結果。
我一開始準備了兩台 iOS 模擬器,結果一台都沒用上。
檢查 Godot 官方樣板:
lipo -info libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/libgodot.a
結果:
Non-fat file: ... is architecture: x86_64
但 Apple Silicon 上的模擬器目標是 arm64。
所以最後連結時會撞到:
ld: symbol(s) not found for architecture arm64
這次直接改用實機。
如果真的需要模擬器,就得自己從原始碼編一份帶 arm64 切片的樣板。
最後一個很容易誤會。
butler push 一開始甚至會直接拒絕:
itch.io API error (400):
Please verify your account's email address before uploading a build
先驗證 itch.io 帳號 email。
驗證完成後,butler push 成功。
結果網頁還是 404。
原因是新專案預設是草稿。
要再到專案編輯頁把:
Visibility → Public
所以:
butler status 顯示 build 正常,不代表別人已經看得到。
上傳成功和公開成功,是兩件不同的事。