TGDF|以小博大:談小型技術團隊的建構與可迭代的開發架構|筆記&心得整理
這些方法不僅適用於程式,也能運用在團隊的開發製程上,提升效率,確保遊戲在不斷變動的開發過程中能夠靈活迭代。 透過這樣的方式,即使是小型技術團隊,也能善用資源,以更高效的方式達成更大的目標,真正做到「以小博大」。
TGDF|以小博大:談小型技術團隊的建構與可迭代的開發架構|筆記&心得整理
本文章主要是由個人理解的筆記&心得整理,由於個人想法可能存在偏差,不一定能完全傳達原講者的內容與意圖,若要完全了解講者之內容建議去觀看講者的演講。
[embed]
本次觀看的演講影片主要針對遊戲軟體工程師
目錄
.前言/背景
.Flow(流動)
.Entity(實體)
.Interface(介面)
.State(狀態)
.心得總結
前言/背景
.講者
李根逸|SIGONO 技術總監
.SIGONO的遊戲
[embed]
[embed]
- 注重演出,聲響。
- 後面內容會提到的幾個重點,需要很精確的控制每個瞬間的畫面與演出。
.帶領一個團隊遇到的議題
- 帶團隊與寫程式遇到的問題很類似,遇到相同問題都可使用類似的解法。
- 「人性」與「個體差異」這些帶有「感性」特質的因素,與「理性」導向的程式設計有差異,但本質上的概念差不多。
.萬能的框架(簡易且個人開發的情況)
- Start處理初始化邏輯
- Update處理時間流動邏輯
- 儲存物件的狀態

節錄至PPT第20頁
但會因為一些需求,導致開發者需要使用外部參照。
.需要引用外部參照的原因
- 類別(MakeAGame)本身的職責並不負責外部參照(如:Image、Animator…等)的職責。
- 外部參照的元件本身就是獨立運行的。
- 設定檔(GameSetting)出現時,設定設定檔的人員(企劃人員)並非懂程式。

節錄至PPT第23頁
Q:接者使用更多的外部參照,讓開發者能用更少的程式碼來完成遊戲開發,但是撰寫更少的程式碼有讓開發者更方便or更順利開發嗎?
A:決定是否使用這些外部參照(工具)時,關鍵在於評估其效益是否大於自行開發類似工具的價值,團隊使用工具並非因為無法自行開發,而是因為在許多情況下,使用現有工具能帶來更高的效益。 (例如,若自行開發工具需要額外投入三個月,而現有工具能立即使用,則選擇外部工具可能更具優勢。但反之,若自研工具雖需更長時間開發,卻能針對團隊需求客製化特定功能,並在效能上優於現有方案,那麼自行開發可能會是更好的選擇。因此,團隊需要在效率、當前需求與長遠效益之間取得平衡,來決定最適合的方案。)
.工具是軟體的一部份,與軟體有很大的關聯。
.小團隊更需要面對使用工具的情境,因為小團隊沒有太多成本什麼都自己製作。
Flow(流動)
藉由呼叫函式去改變當前的「狀態」
.非同步執行的流動 狀態改變會因為多個不同的行為,且行為們是非同步執行,抓著相同的資源,進而變的複雜無法預測結果。
// 取至PPT第38頁
// 狀態變化的來源在非同步情況下,抓著相同的狀態(_state)
void Update() {
StartCoroutine(_engineerA.Process(_state, _gameData));
StartCoroutine(_engineerA.Process(_state, _gameData));
StartCoroutine(_animator.Play(_state));
}
→ 讓資源狀態不能改寫只能是Read-Only,等到特定時間(如:Git的Merge)時,再針對合併的狀態做變化,且同時會知道合併的時機點,也就可以調整合併的優先順序。
// 取至PPT第39頁
// 設計成狀態是ReadOnly,且在特定時間點(OnFinished)才能更動狀態
void Update() {
StartCoroutine(_engineerA.Process([ReadOnly]_state, _gameData, OnFinished));
StartCoroutine(_engineerA.Process([ReadOnly]_state, _gameData, OnFinished));
StartCoroutine(_animator.Play([ReadOnly]_state, OnFinished));
}
void OnFinished(State newState){
/* 與現在的 _state 進行合併 */
}
→ 把資源的種類拆開來,各個人員寫不同類型的資源,也就不會有抓著相同資源的問題。
// 取至PPT第40頁
// 讓各自擁有各自專屬的狀態,互不搶狀態
void Update() {
StartCoroutine(_engineerA.Process(_state.partA, _gameData));
StartCoroutine(_engineerB.Process(_state.partB, _gameData));
StartCoroutine(_animator.Play(_state.anim));
}
.非同步處理議題
- 若是流程進行到一半,需要被中斷該怎麼辦? 中斷後的演出是沒有必要存在的,因為流程不需要繼續執行了。
- 能做到逐幀同步嗎? 遊戲執行時,要確認某些演出在某些時間是正確的,但遊戲不需要從頭開始跑流程,所以需要直接指定遊戲在特定幀數的演出。
- 如何確保逐幀or指定特定幀時,當前的執行順序是正確的? 執行順序是有意義的,若無法確保流程順序正確,則會產生數不盡的Bug。
而團隊在選用工具時,就需要考慮該工具是否支援這些議題。
.具有流動(Flow)性的美術工具
- Unity Animation/ Timeline
- Unity Animator
- DotTween Pro
.Daily Scrum 從專案的角度思考,Daily Scrum也有一樣的特性。

節錄至PPT第44頁
.合併的重要性
合併時必須要花較多成本來處理,那是因為可以減少除錯的範圍。確保穩定性,避免出現難以解決的Bug,需要花更多的成本來修復。 有些管理專案的Git(如:Git hub),有提供一些功能能確保合併後,專案不會出錯。

節錄至PPT第45頁
Entity(實體)
能被參照的東西
// 取至PPT第48頁
// 在這個情況下移除物件,編譯不會通過
// EngineerA _engineerA = new();
State _state;
void Update() {
//編譯失敗
_state = _engineerA.Init(_state);
}
編譯無法通過時,是一種靜態的測試,能告訴開發者這邊出現錯誤。
如果是使用外部參照的情況下,無法在編譯時就知道錯誤(如:Missing Reference)。本質上這與程式的Missing差不多,但目前沒有編譯器能處理所有外部參照的Missing。
.需要解決的外部參照Missing問題
- 參照的實體是否依舊存在? 一旦參照的實體遺失,那執行就會錯誤。
- 是否有實體沒有被參照到? 實體沒有被參照使用到,那該實體就是多餘的存在,對於玩家來講是多餘的資源,可以移除。

節錄至PPT第49頁
要解決外部參照Missing的問題,可以實作ID系統,讓程式能夠藉由ID獲取到這些資源。
.ID的特性
- 不變性:一旦創建完後就不該更動,且具有意義的名稱不適合當ID。
- 格式:字串與數字是常見的ID格式。
- 唯一性:確保整個專案只有一個,全域or巢狀。
.建構ID的延伸效益
- 得知不同實體的相依性
- 明確解耦合
- 直接從ID載入實體
- 可直接讀取該ID的資料層
建構ID後,在Unity遇到Missing Reference就可以製作工具去檢查是否有Missing。
靜態ID vs 動態ID
.靜態ID(如:Enum) → 執行前靜態生成的ID → 從遊戲開始至結束都是固定的數量,測試上較方便、穩定,易管理。
.動態ID(如:Dictionary) → 動態生成的ID → 出現與消失的時機點較難確保,不易管理。

節錄至PPT第56頁
有了ID後,參照就不需要任何實體,以ID的形式動態加載,又可以檢查Missing等問題。 另外建構ID後,也解決了前面提到的外部參照Missing問題。
同時,ID也能用在製程管理上面。 → 輸入QA的ID後,直接導入到QA的詳細資訊(測試影片,問題描述等)。 → 輸入Asset的ID後,可以直接導入到相關的使用說明文件。
Interface(介面)
並非只是程式當中的Interface,而是廣義的規格與使用方式。
.可迭代的開發架構
- 快速迭代、及早驗證
- 及時反饋、及早修正
如果開發團隊只有一個人,是能符合以上兩點的開發架構,但一旦開發團隊人數增加後,合作的成本隨之增加,會導致開發的複雜度上升,進而影響迭代。
開發階段,迭代的是介面,當介面決定完後,就可以進入量產期。 介面決定了,即使內部的東西不夠完善,也已經具備了穩定的開發基礎。反之介面尚未決定前,都還是在開發期。
.制定介面
- 理解需求
- 確保實作的成本(不該有包山包海的介面,對於後續維護很差)
- 能夠應應變化(需求會改變) .功能沒有特例或通則,只有要做or不要做。 .當需求改變時,要花多少成本去修正錯誤。 .介面因為需求變化發生問題時,開發者需要明確知道問題點。
.從分工合作的角度去制定介面
介面是一種保證,保證一定會照著介面執行,且盡量不改變。
- 一件項目只有一位工程師知道 → 風險太大,今天當該工程師請假的話,就沒有人可以接手處理。
- 一件項目每位工程師都知道 → 溝通成本太大,每位人員需要同步的介面過多,也就是要保證的事情太多。
- 一件項目有部分工程師知道 → 相較於前兩者,實務上更為合理。

節錄至PPT第63~65頁
.與遊戲引擎耦合的項目

節錄至PPT第66頁
假若引擎改版了,抓著該項目的使用者都得更著改變,以下案例中的Transform,另外再開一個介面(ITransform),主要是基於以下考量:
→ 不是所有事情都不會改變。 → 開發者不需要用到 Transfrom 的所有API。 → 能確保/限制使用者依照介面的規則去實作項目。
// 取至PPT第67頁
class UnityTransform : ITransform {
Transform _transform;
Vector3 ITransform.Position => _transform.position;
public UnityTransform(Transform t) {
_transform = t;
}
}
但是過度的設計界面也會導致一些負擔,例如: → 會有過多的腳本存在,導致開發管理變得複雜。 → 開發速度可能會減緩。
因此,是否抽取介面應視專案需求進行評估,適度的設計能提升靈活性,但過度抽象反而會增加額外的負擔。
.工程師團隊適合的介面
建立遵守規則的文化,透過討論與辯證的過程,選擇能夠帶來正面效益的規則。
- Coding Style/Coding Convention → 容易制定且有強固的驗證機制
- 使用拼字檢查 → commit進行拼字檢查,避免查找commit找不到 → 專案常見的縮寫可以建立一個縮寫表格使用 → 任何檢查都需要有白名單機制以允許合理的例外情況
- 依照資料特性替名稱加上前綴or後綴名稱 → 執行時會變化的狀態(如:XXXState) → 執行時不會變化的設定(如:XXXSetting) → 其他暫時資料(如:XXXData)
- 避免使用無法掌握的語言特性 → Partial class → Extension methods 團隊內的開發人員可能對某些進階特性不熟悉,若誤用這些特性,反而會造成理解與維護上的困難,增加錯誤解讀的風險。
.程式碼架構
第一層ISample.Run() 明確實作 ISample提供的行為,確保外部使用能夠使用。第二層Run()會使用一些資料物件的成員變數,其他的函式(如: DoRun() )皆為靜態函式的Pure Function,並不會使用到成員變數,會讓整體的Flow更加明確(只有Run()會影響成員變數)。
//取至PPT第77頁
class Sample : ISample {
State _state;
void ISample.Run() {
Run();
}
void Run() {
_state = DoRun(_state);
}
static State DoRun(IReadOnlyState state) {
/* 讀取 State 後產生 newState */
return newState;
}
}
.迭代介面
- 制定規則避免合併時衝突過多 .檔案目錄跟Namespace一致 .檔案名稱與類別名稱一致 .using namespace使用特定順序
- 驗證介面沒改壞 .靜態驗證:使用編譯器or工具自動檢查,可在寫程式的當下立刻修正。 .動態驗證:實際運行後才能驗證。 → Daily QA機制:使用雙分支模型,來限制問題發生的地點,且更好的解決問題。

節錄至PPT第82頁
- 非程式碼直接控管的部分不易迭代 → 遊戲內的設定資料 → 資源素材的檔案名稱 → [SerializedField] → Node Canvas的節點內容
在資料端就做驗證,驗證成功後,就可不用擔心後續流程使用的正確性問題。
- 對於程式人員以外的通知系統 對於程式人員來說,出現錯誤會顯示在開發軟體上,但對於其他開發人員(如:企劃人員、製作人等等),無法直接地得知錯誤發生,這時就可以利用一些工具來通知其他的開發人員。

節錄至PPT第89頁
State(狀態)
遊戲進行時,隨著時間累積變化的資料
- 狀態依照分工分到多個系統(System)進行管理
- 遊戲運行時是透過Flow進而修改State,確保變更邏輯清晰且可控。
- 隨時記錄State就可取得遊戲狀態的變化 → 可透過存檔檢視器除錯 → 可透過編輯存檔與載入存檔測試 → 可以實現遊戲在任何時間點皆可存檔/讀檔,進而方便測試
// 取至PPT第91頁
// 狀態已樹狀的結構形式存在
struct AppState{
SystemAState _systemA;
SyatemBState _systemB;
};
.四層架構
視專案需求,上面兩層(App、System)可以用Singleton和DI的形式至實作,底下兩層則負責具體管理。
- App
- System
- Controller
- Wrapper

節錄至PPT第93頁
任何會改變狀態的操作應透過 System 層呼叫 → App.PlayerSystem.MoveForward(character);
心得總結
觀看完講者的內容後,我更能理解為何講者會以「小型技術團隊的建構與可迭代的開發架構」作為主題。 這些方法不僅適用於程式,也能運用在團隊的開發製程上,提升效率,確保遊戲在不斷變動的開發過程中能夠靈活迭代。 透過這樣的方式,即使是小型技術團隊,也能善用資源,以更高效的方式達成更大的目標,真正做到「以小博大」。
메타데이터
- post_id
- c89886ffece5
- slug
- tgdf-以小博大-談小型技術團隊的建構與可迭代的開發架構-筆記-心得整理-c89886ffece5
- url
- https://medium.com/@cpa11225637/tgdf-%E4%BB%A5%E5%B0%8F%E5%8D%9A%E5%A4%A7-%E8%AB%87%E5%B0%8F%E5%9E%8B%E6%8A%80%E8%A1%93%E5%9C%98%E9%9A%8A%E7%9A%84%E5%BB%BA%E6%A7%8B%E8%88%87%E5%8F%AF%E8%BF%AD%E4%BB%A3%E7%9A%84%E9%96%8B%E7%99%BC%E6%9E%B6%E6%A7%8B-%E7%AD%86%E8%A8%98-%E5%BF%83%E5%BE%97%E6%95%B4%E7%90%86-c89886ffece5
- canonical_url
- https://medium.com/@cpa11225637/tgdf-%E4%BB%A5%E5%B0%8F%E5%8D%9A%E5%A4%A7-%E8%AB%87%E5%B0%8F%E5%9E%8B%E6%8A%80%E8%A1%93%E5%9C%98%E9%9A%8A%E7%9A%84%E5%BB%BA%E6%A7%8B%E8%88%87%E5%8F%AF%E8%BF%AD%E4%BB%A3%E7%9A%84%E9%96%8B%E7%99%BC%E6%9E%B6%E6%A7%8B-%E7%AD%86%E8%A8%98-%E5%BF%83%E5%BE%97%E6%95%B4%E7%90%86-c89886ffece5
- author_url
- https://medium.com/@cpa11225637
- status
- ok
- fetched_at
- 2026-07-26 06:30:57