設(shè)計:團(tuán)隊分工與C++底層實現(xiàn))
1. 從零開始理解游戲引擎的團(tuán)隊分工邏輯很多人第一次接觸“游戲引擎架構(gòu)”這個詞腦子里浮現(xiàn)的是一堆類和繼承關(guān)系圖或者某個開源引擎的源碼目錄樹。但我在實際帶項目和跟團(tuán)隊協(xié)作的過程中發(fā)現(xiàn)真正決定一個引擎能不能跑起來、能不能被團(tuán)隊用起來的往往不是某個渲染算法有多精妙而是團(tuán)隊分工和底層架構(gòu)之間的匹配關(guān)系。換句話說架構(gòu)不是畫出來的是被團(tuán)隊的組織方式和協(xié)作模式“逼”出來的。這個系列我打算從最基礎(chǔ)的地方講起第一篇就聊清楚兩件事一個游戲引擎團(tuán)隊通常怎么分工以及這些分工如何映射到底層架構(gòu)的模塊劃分上。適合剛?cè)胄邢肜斫庖嫒驳拈_發(fā)者也適合已經(jīng)在一線寫業(yè)務(wù)邏輯、想往引擎層深入的朋友。我會盡量用實際項目中的例子來說明而不是照搬教科書上的分層圖。1.1 為什么先講團(tuán)隊分工而不是直接講代碼你可能會問講引擎架構(gòu)為什么不直接從渲染管線、內(nèi)存管理、資源系統(tǒng)這些硬核內(nèi)容開始原因很簡單引擎架構(gòu)的本質(zhì)是協(xié)作契約。一個引擎的模塊邊界幾乎總是對應(yīng)著團(tuán)隊里某個角色或某個小組的職責(zé)邊界。如果你不理解誰在用什么、誰在改什么你就無法理解為什么某個模塊要設(shè)計成那樣。舉個例子渲染組的人希望材質(zhì)系統(tǒng)盡可能靈活能直接操作Shader參數(shù)但工具組的人希望材質(zhì)系統(tǒng)有穩(wěn)定的序列化格式和編輯器界面。這兩個訴求會直接影響到材質(zhì)系統(tǒng)在架構(gòu)上被拆成“運(yùn)行時核心”和“編輯器層”兩個部分。如果你只看代碼會覺得這是過度設(shè)計但如果你知道有兩個不同角色在同時使用這個系統(tǒng)就會明白這種拆分是必然的。所以我的建議是在學(xué)習(xí)任何引擎源碼之前先花點(diǎn)時間搞清楚一個典型引擎團(tuán)隊的角色劃分。這比死磕某個類的實現(xiàn)細(xì)節(jié)有用得多。1.2 一個典型游戲引擎團(tuán)隊的角色劃分不同規(guī)模的團(tuán)隊分工粒度差別很大。大廠可能一個渲染模塊就有幾十號人小團(tuán)隊可能一個人同時負(fù)責(zé)渲染、物理和工具。但不管規(guī)模如何核心職能是逃不掉的。我把它歸納為五個方向核心運(yùn)行時組負(fù)責(zé)引擎最底層的模塊包括內(nèi)存管理、數(shù)學(xué)庫、容器、任務(wù)調(diào)度、平臺抽象層。這個組的人通常C功底極深關(guān)注性能和跨平臺兼容性。渲染與圖形組負(fù)責(zé)渲染管線、材質(zhì)系統(tǒng)、光照、后處理、Shader管理。這個組需要同時懂圖形API和GPU硬件特性。物理與動畫組負(fù)責(zé)碰撞檢測、剛體模擬、骨骼動畫、IK等。這個組對數(shù)值穩(wěn)定性和實時性要求極高。資源與工具組負(fù)責(zé)資源導(dǎo)入管線、序列化、編輯器、資產(chǎn)管理系統(tǒng)。這個組是引擎和內(nèi)容創(chuàng)作者之間的橋梁。游戲框架組負(fù)責(zé)實體組件系統(tǒng)、腳本綁定、事件系統(tǒng)、網(wǎng)絡(luò)同步等。這個組直接服務(wù)于游戲玩法開發(fā)者。這五個方向不是孤立的它們之間有大量的交叉依賴。而底層架構(gòu)要解決的核心問題就是如何讓這五個方向的人能并行工作而不互相踩腳。1.3 分工如何影響架構(gòu)的模塊邊界我拿一個實際場景來說明。假設(shè)渲染組要新增一種材質(zhì)類型工具組要能在編輯器里編輯這種材質(zhì)的參數(shù)游戲框架組要能在腳本里動態(tài)修改材質(zhì)屬性。這三個訴求對應(yīng)到架構(gòu)上就要求材質(zhì)系統(tǒng)至少分成三層第一層是運(yùn)行時材質(zhì)核心只包含GPU需要的參數(shù)和狀態(tài)由渲染組維護(hù)。第二層是序列化與反射層負(fù)責(zé)把材質(zhì)參數(shù)映射到編輯器可編輯的字段由工具組維護(hù)。第三層是腳本綁定層把材質(zhì)接口暴露給腳本系統(tǒng)由游戲框架組維護(hù)。如果架構(gòu)上沒有做這個拆分而是把材質(zhì)做成一個巨大的類那么三個組的人就會同時修改同一個文件代碼沖突和溝通成本會急劇上升。這就是為什么我說架構(gòu)是協(xié)作契約——它的第一目標(biāo)是讓不同角色能高效并行而不是追求代碼本身的優(yōu)雅。注意很多新手在自研引擎時容易把所有功能塞進(jìn)一個模塊覺得“反正我一個人寫”。但一旦團(tuán)隊超過三個人這種架構(gòu)就會成為瓶頸。提前做好模塊邊界劃分比后期重構(gòu)代價小得多。2. 底層架構(gòu)的核心分層與C實現(xiàn)要點(diǎn)聊完團(tuán)隊分工接下來進(jìn)入底層架構(gòu)本身。游戲引擎的底層架構(gòu)通常分為幾個大層每一層解決不同的問題。我用一個從下往上的順序來講這樣你能看清楚依賴關(guān)系是怎么建立的。2.1 平臺抽象層屏蔽操作系統(tǒng)差異最底層是平臺抽象層它的職責(zé)是把操作系統(tǒng)相關(guān)的API封裝成統(tǒng)一的接口。比如文件讀寫、線程創(chuàng)建、時間獲取、窗口管理這些在不同平臺上實現(xiàn)方式不同但上層模塊不應(yīng)該關(guān)心這些差異。為什么這一層要用C來做因為平臺抽象層需要直接調(diào)用系統(tǒng)API而C提供了足夠的底層控制能力同時又能通過虛函數(shù)或函數(shù)指針實現(xiàn)運(yùn)行時多態(tài)。常見的做法是定義一組純虛接口然后每個平臺提供一份實現(xiàn)。// 平臺抽象層的文件接口示例 class IFileSystem { public: virtual ~IFileSystem() default; virtual FileHandle Open(const char* path, FileMode mode) 0; virtual size_t Read(FileHandle handle, void* buffer, size_t size) 0; virtual size_t Write(FileHandle handle, const void* buffer, size_t size) 0; virtual void Close(FileHandle handle) 0; };這個接口看起來簡單但實際項目中需要考慮的細(xì)節(jié)很多。比如路徑分隔符在Windows上是反斜杠在類Unix系統(tǒng)上是正斜杠文件編碼在不同平臺上也有差異。這些細(xì)節(jié)都應(yīng)該在這一層被消化掉上層模塊只看到統(tǒng)一的接口。我在實際項目中的一個經(jīng)驗是平臺抽象層的接口設(shè)計要盡量窄。不要為了“以后可能用到”而暴露大量平臺特有的功能。接口越窄跨平臺移植時的工作量越小。曾經(jīng)有個項目在平臺層暴露了Windows特有的重疊IO接口結(jié)果移植到其他平臺時不得不重寫整個資源加載模塊。2.2 核心系統(tǒng)層內(nèi)存、數(shù)學(xué)與容器平臺抽象層之上是核心系統(tǒng)層這一層是引擎的“基礎(chǔ)設(shè)施”。主要包括內(nèi)存管理、數(shù)學(xué)庫、容器和字符串處理。內(nèi)存管理是引擎性能的關(guān)鍵。游戲引擎通常不會直接使用全局的new和delete而是實現(xiàn)自己的內(nèi)存分配器。原因有三個第一減少內(nèi)存碎片第二提高分配速度第三方便追蹤內(nèi)存泄漏。常見的做法是分層分配器底層是一個大塊內(nèi)存池上層根據(jù)不同用途劃分不同的分配策略。比如幀分配器用于每幀臨時分配的內(nèi)存生命周期只有一幀對象池用于頻繁創(chuàng)建銷毀的同類型對象通用堆分配器用于長生命周期對象。// 簡單的幀分配器示意 class FrameAllocator { public: void* Allocate(size_t size, size_t alignment) { // 從當(dāng)前幀的內(nèi)存塊中分配 // 每幀結(jié)束時整體重置不需要逐個釋放 } void Reset() { // 幀結(jié)束時調(diào)用一次性回收所有分配 } };數(shù)學(xué)庫方面引擎通常需要向量、矩陣、四元數(shù)、射線、包圍盒等基礎(chǔ)類型。這些類型的實現(xiàn)要特別注意精度和性能的平衡。比如矩陣乘法用SIMD指令加速可以帶來數(shù)倍的性能提升但代碼復(fù)雜度也會上升。我的建議是先用標(biāo)量實現(xiàn)跑通邏輯確認(rèn)瓶頸后再做SIMD優(yōu)化。容器方面引擎通常不會直接用STL的全部容器而是根據(jù)場景選擇或自研。比如std::vector在大多數(shù)場景下夠用但std::unordered_map的哈希函數(shù)和內(nèi)存布局可能不適合性能敏感的場景。很多引擎會實現(xiàn)自己的開放尋址哈希表或扁平化容器。2.3 資源系統(tǒng)層資產(chǎn)的生命周期管理資源系統(tǒng)負(fù)責(zé)管理游戲中的各種資產(chǎn)包括紋理、模型、音頻、材質(zhì)、動畫等。這一層的核心問題是如何高效地加載、引用、緩存和釋放資源。資源系統(tǒng)的架構(gòu)通常圍繞幾個核心概念展開資源句柄、資源加載器、資源緩存、引用計數(shù)。資源句柄是一個輕量級的標(biāo)識符上層模塊通過句柄來引用資源而不是直接持有資源指針。這樣做的好處是資源可以在內(nèi)存中移動或重新加載而句柄保持不變。資源加載器負(fù)責(zé)從磁盤或網(wǎng)絡(luò)加載資源數(shù)據(jù)并轉(zhuǎn)換成運(yùn)行時格式。這里涉及到一個重要的設(shè)計決策同步加載還是異步加載。同步加載實現(xiàn)簡單但會阻塞主線程導(dǎo)致卡頓。異步加載需要配合任務(wù)系統(tǒng)和回調(diào)機(jī)制復(fù)雜度更高但能保證流暢體驗。我在實際項目中的做法是關(guān)鍵路徑上的小資源用同步加載大資源和非關(guān)鍵資源用異步加載。同時提供一個“占位資源”機(jī)制在異步加載完成前先用低精度版本頂上避免畫面出現(xiàn)空洞。2.4 渲染層從場景數(shù)據(jù)到屏幕像素渲染層是引擎中最復(fù)雜的模塊之一它的職責(zé)是把場景數(shù)據(jù)轉(zhuǎn)換成最終的屏幕像素。這個過程涉及場景遍歷、剔除、排序、批處理、Shader執(zhí)行、后處理等多個階段。渲染層的架構(gòu)設(shè)計要解決的核心問題是如何在保證畫質(zhì)的前提下最大化性能。這涉及到很多權(quán)衡。比如延遲渲染和前向渲染的選擇前者適合大量動態(tài)光源的場景后者適合透明物體和抗鋸齒要求高的場景。很多現(xiàn)代引擎會同時支持兩種路徑根據(jù)場景配置切換。渲染層的另一個重要設(shè)計是渲染圖的概念。渲染圖把一幀的渲染過程描述成一組帶依賴關(guān)系的Pass引擎可以根據(jù)依賴關(guān)系自動調(diào)度執(zhí)行順序并管理中間渲染目標(biāo)的分配和復(fù)用。這種架構(gòu)讓渲染管線的修改和擴(kuò)展變得非常靈活。// 渲染圖的簡化示意 class RenderGraph { public: void AddPass(const char* name, std::functionvoid(RenderContext) execute, const std::vectorResourceHandle inputs, const std::vectorResourceHandle outputs); void Compile(); // 分析依賴分配資源 void Execute(); // 按拓?fù)漤樞驁?zhí)行 };2.5 游戲框架層面向玩法開發(fā)者的接口最上層是游戲框架層它直接服務(wù)于游戲玩法開發(fā)者。這一層包括實體組件系統(tǒng)、事件系統(tǒng)、腳本綁定、輸入處理等。實體組件系統(tǒng)的核心思想是組合優(yōu)于繼承。一個游戲?qū)ο蟛皇峭ㄟ^繼承層次來定義而是通過掛載不同的組件來組合出不同的行為。這樣做的好處是靈活避免了深層繼承樹帶來的僵化問題。腳本綁定層負(fù)責(zé)把引擎的C接口暴露給腳本語言。常見的方案有手動綁定、自動生成綁定和直接嵌入腳本虛擬機(jī)。手動綁定控制力最強(qiáng)但工作量大自動生成綁定效率高但可能產(chǎn)生冗余代碼。選擇哪種方案取決于團(tuán)隊的技術(shù)棧和迭代速度要求。提示游戲框架層的接口設(shè)計要以“玩法開發(fā)者的使用體驗”為第一優(yōu)先級。引擎內(nèi)部可以復(fù)雜但暴露給玩法層的API要盡量簡潔直觀。我見過太多引擎在內(nèi)部架構(gòu)上很優(yōu)雅但玩法開發(fā)者用起來極其痛苦最后大家寧愿繞過引擎自己造輪子。3. 實操搭建一個最小可用的引擎骨架前面講了架構(gòu)分層和團(tuán)隊分工的對應(yīng)關(guān)系這一節(jié)我來實際演示如何搭建一個最小可用的引擎骨架。這個骨架不追求功能完整但會包含核心的分層結(jié)構(gòu)和模塊通信機(jī)制你可以在此基礎(chǔ)上逐步擴(kuò)展。3.1 項目目錄結(jié)構(gòu)與構(gòu)建系統(tǒng)選擇首先確定目錄結(jié)構(gòu)。我習(xí)慣按照架構(gòu)分層來組織目錄這樣模塊邊界一目了然engine/ source/ platform/ # 平臺抽象層 core/ # 核心系統(tǒng)層 resource/ # 資源系統(tǒng)層 renderer/ # 渲染層 framework/ # 游戲框架層 third_party/ # 第三方庫 build/ # 構(gòu)建腳本 tests/ # 單元測試構(gòu)建系統(tǒng)方面C項目常見的選擇有CMake、Premake、Meson等。CMake是目前最主流的選擇生態(tài)成熟跨平臺支持好。我建議用CMake的target機(jī)制來管理模塊依賴每個層作為一個獨(dú)立的target上層target鏈接下層target。# 核心系統(tǒng)層 add_library(engine_core STATIC source/core/memory.cpp source/core/math.cpp source/core/container.cpp ) target_link_libraries(engine_core PUBLIC engine_platform) # 渲染層 add_library(engine_renderer STATIC source/renderer/render_graph.cpp source/renderer/material.cpp ) target_link_libraries(engine_renderer PUBLIC engine_core engine_resource)這種寫法的好處是依賴關(guān)系顯式聲明如果某一層引用了不該引用的下層模塊編譯時會直接報錯。這比靠文檔和口頭約定來維護(hù)架構(gòu)邊界可靠得多。3.2 模塊間通信事件總線與依賴注入引擎各層之間需要通信但又不希望產(chǎn)生強(qiáng)耦合。常見的解決方案有兩種事件總線和依賴注入。事件總線適合處理“某件事發(fā)生了誰關(guān)心誰處理”的場景。比如資源加載完成、窗口大小改變、幀開始/結(jié)束等。實現(xiàn)上通常是一個類型安全的回調(diào)注冊表。class EventBus { public: templatetypename EventType void Subscribe(std::functionvoid(const EventType) handler); templatetypename EventType void Publish(const EventType event); };依賴注入適合處理“某個模塊需要另一個模塊的服務(wù)”的場景。比如渲染層需要資源系統(tǒng)來加載紋理但不應(yīng)該直接依賴資源系統(tǒng)的具體實現(xiàn)。做法是定義一個接口渲染層依賴接口資源系統(tǒng)提供實現(xiàn)。// 渲染層定義的資源加載接口 class ITextureLoader { public: virtual ~ITextureLoader() default; virtual TextureHandle LoadTexture(const char* path) 0; }; // 資源系統(tǒng)提供實現(xiàn) class ResourceSystem : public ITextureLoader { public: TextureHandle LoadTexture(const char* path) override; };這兩種方式各有適用場景。我的經(jīng)驗是跨層的通知用事件總線同層或相鄰層的服務(wù)調(diào)用用依賴注入。不要把所有通信都塞進(jìn)事件總線否則代碼會變得難以追蹤。3.3 內(nèi)存追蹤與性能計數(shù)器的接入在骨架階段就把內(nèi)存追蹤和性能計數(shù)器接進(jìn)來后期會省很多事。內(nèi)存追蹤的基本思路是重載全局的new和delete記錄每次分配的大小、位置和調(diào)用棧。void* operator new(size_t size) { void* ptr malloc(size); MemoryTracker::RecordAllocation(ptr, size); return ptr; } void operator delete(void* ptr) noexcept { MemoryTracker::RecordDeallocation(ptr); free(ptr); }性能計數(shù)器則是在關(guān)鍵路徑上打點(diǎn)記錄耗時。比如每幀的渲染時間、物理模擬時間、資源加載時間等。這些數(shù)據(jù)可以用一個簡單的環(huán)形緩沖區(qū)存儲然后在調(diào)試界面上可視化。class ProfileScope { public: ProfileScope(const char* name) { m_name name; m_start GetHighResolutionTime(); } ~ProfileScope() { auto elapsed GetHighResolutionTime() - m_start; Profiler::RecordSample(m_name, elapsed); } private: const char* m_name; uint64_t m_start; };這兩個工具在骨架階段接入的成本很低但后期排查性能問題和內(nèi)存泄漏時價值巨大。我強(qiáng)烈建議在項目一開始就做這件事。3.4 一個可運(yùn)行的最小示例把上面的東西串起來一個最小的引擎主循環(huán)大概長這樣int main() { // 初始化各層 PlatformLayer::Initialize(); CoreSystems::Initialize(); ResourceSystem::Initialize(); Renderer::Initialize(); Framework::Initialize(); // 主循環(huán) while (!PlatformLayer::ShouldQuit()) { PlatformLayer::PollEvents(); { PROFILE_SCOPE(Frame); ResourceSystem::Update(); Framework::Update(); Renderer::Render(); } PlatformLayer::SwapBuffers(); } // 按相反順序關(guān)閉 Framework::Shutdown(); Renderer::Shutdown(); ResourceSystem::Shutdown(); CoreSystems::Shutdown(); PlatformLayer::Shutdown(); return 0; }這個骨架看起來簡單但它已經(jīng)包含了引擎架構(gòu)的核心要素分層初始化、主循環(huán)、性能打點(diǎn)、有序關(guān)閉。你可以在這個基礎(chǔ)上逐步往每一層里填充具體功能。4. 常見問題與排查技巧實錄在實際搭建和迭代引擎的過程中我踩過不少坑。這一節(jié)整理一些典型問題和排查思路希望能幫你少走彎路。4.1 模塊循環(huán)依賴的識別與打破循環(huán)依賴是引擎架構(gòu)中最常見的問題之一。比如渲染層依賴資源層來加載紋理資源層又依賴渲染層來創(chuàng)建GPU資源這就形成了環(huán)。識別循環(huán)依賴的方法很簡單在CMake中把每個層設(shè)為獨(dú)立target如果出現(xiàn)循環(huán)鏈接錯誤就說明有循環(huán)依賴。打破循環(huán)依賴的常見手段有三種第一種是提取公共接口層。把雙方都依賴的部分抽到一個更底層的模塊中。比如上面例子中可以把“紋理句柄”和“紋理描述”抽到核心層渲染層和資源層都依賴核心層而不是互相依賴。第二種是依賴倒置。讓上層定義接口下層實現(xiàn)接口。比如渲染層定義ITextureLoader接口資源層實現(xiàn)它。這樣依賴方向就從“資源層←渲染層”變成了“渲染層←資源層”打破了環(huán)。第三種是事件解耦。把直接調(diào)用改成事件通知。比如資源層加載完成后發(fā)布一個事件渲染層訂閱這個事件來創(chuàng)建GPU資源。這種方式適合異步場景但會增加代碼的追蹤難度。4.2 跨平臺編譯中的典型坑跨平臺編譯是C引擎開發(fā)中繞不開的問題。我整理了一個常見問題速查表問題現(xiàn)象可能原因排查方向Windows編譯通過Linux鏈接失敗符號可見性設(shè)置不同檢查是否有__declspec(dllexport)等平臺特有修飾結(jié)構(gòu)體大小不一致對齊方式或類型長度不同用static_assert檢查sizeof統(tǒng)一使用固定寬度類型運(yùn)行時崩潰但編譯無警告未定義行為在不同平臺表現(xiàn)不同開啟所有警告使用AddressSanitizer等工具文件路徑找不到路徑分隔符或大小寫敏感差異統(tǒng)一使用正斜杠避免依賴大小寫多線程行為異常內(nèi)存模型或線程調(diào)度差異使用標(biāo)準(zhǔn)庫的原子操作和內(nèi)存序我的經(jīng)驗是盡早建立跨平臺CI。不要等到項目后期才做跨平臺適配那時候問題會堆積如山。每天自動在多個平臺上編譯和跑測試問題能在第一時間被發(fā)現(xiàn)。4.3 性能瓶頸的定位思路引擎性能問題通常集中在幾個地方渲染、物理、資源加載、內(nèi)存分配。定位瓶頸的基本流程是先測量再分析最后優(yōu)化。測量階段用性能計數(shù)器記錄各模塊耗時。如果某個模塊耗時明顯偏高就進(jìn)入分析階段。分析階段可以用采樣分析器或插樁分析器來定位具體的熱點(diǎn)函數(shù)。優(yōu)化階段則根據(jù)熱點(diǎn)類型選擇策略計算密集型的考慮算法優(yōu)化或SIMD內(nèi)存密集型的考慮緩存友好性或減少分配。注意不要憑直覺優(yōu)化。我見過太多人花幾天時間優(yōu)化一個自認(rèn)為很慢的函數(shù)結(jié)果發(fā)現(xiàn)它只占總耗時的百分之二。先用數(shù)據(jù)說話再動手。4.4 團(tuán)隊協(xié)作中的架構(gòu)腐化與應(yīng)對架構(gòu)腐化是團(tuán)隊項目中的隱形殺手。表現(xiàn)包括模塊邊界模糊、依賴關(guān)系混亂、公共模塊越來越臃腫、編譯時間越來越長。應(yīng)對架構(gòu)腐化的關(guān)鍵是自動化約束。靠代碼審查和口頭約定是不夠的要把架構(gòu)規(guī)則寫成可執(zhí)行的檢查。比如用CMake的target依賴來強(qiáng)制模塊邊界用靜態(tài)分析工具檢查代碼規(guī)范用編譯時間監(jiān)控來發(fā)現(xiàn)模塊膨脹。另外定期做架構(gòu)回顧也很重要。每個迭代結(jié)束時花半小時看看最近的代碼變更是否違反了架構(gòu)原則及時糾正。這比等到問題積累到無法收拾再重構(gòu)要劃算得多。4.5 從個人項目到團(tuán)隊項目的架構(gòu)演進(jìn)個人項目轉(zhuǎn)團(tuán)隊項目時架構(gòu)需要做幾個關(guān)鍵調(diào)整。首先是接口穩(wěn)定性個人項目可以隨意改接口但團(tuán)隊項目中接口變更會影響其他人需要更謹(jǐn)慎。其次是文檔和注釋個人項目可以靠記憶團(tuán)隊項目必須靠文檔。最后是構(gòu)建和測試自動化個人項目可以手動構(gòu)建團(tuán)隊項目必須自動化。我的建議是即使一開始是個人項目也盡量按照團(tuán)隊項目的標(biāo)準(zhǔn)來要求自己。用CMake管理構(gòu)建寫單元測試維護(hù)接口文檔。這些習(xí)慣在項目變大或加入新成員時會帶來巨大回報。5. 引擎架構(gòu)后續(xù)擴(kuò)展的方向這個骨架搭起來之后后續(xù)可以往幾個方向擴(kuò)展。渲染方向可以加入渲染圖和延遲渲染管線資源方向可以加入異步加載和熱重載框架方向可以加入實體組件系統(tǒng)和腳本綁定。每個方向都可以獨(dú)立演進(jìn)只要保持模塊邊界清晰。我個人在實際操作中的體會是引擎架構(gòu)沒有“完成”的狀態(tài)它隨著團(tuán)隊規(guī)模和項目需求不斷演進(jìn)。重要的不是一開始就設(shè)計出完美的架構(gòu)而是建立一套能讓架構(gòu)持續(xù)演進(jìn)的機(jī)制——清晰的模塊邊界、自動化的約束檢查、定期的架構(gòu)回顧。有了這套機(jī)制架構(gòu)就能跟著項目一起成長而不是成為項目的負(fù)擔(dān)。