Blackboard:動態控制類似蛇的多節實體
我們目前正在處理一個關于實體系統如何以組合方式進行管理的問題。具體來說,是在游戲中實現多個實體可以共同或獨立行動的機制。例如,我們的主角擁有兩個實體組成部分,一個是身體(或稱軀干),另一個是頭部。這兩個部分在某些情況下需要一起行動,而在其他情況下(比如受到某種魔法攻擊)也可能會分離,各自獨立運作。
我們意識到,這種“組合實體”的需求不僅僅存在于主角身上,也可能出現在敵人或其他復雜生物中。比如:
- 一條蛇形怪物,擁有一個頭部和多個身體段,每個部分可能占據不同的格子,頭部移動時,身體段需要跟隨移動。如果某個部分被攻擊切斷,那就會斷成兩截,各自繼續移動。
- 一個大型敵人可能有主體和多個附屬的“手臂”或其他攻擊部件,甚至可能有圍繞它旋轉的小型子實體。這些部分有時需要統一控制,有時也需要單獨處理(比如被破壞后分離)。
我們的目標是設計一種系統,能靈活地支持這種實體的組合與解耦,不需要提前固定它們的結構,而是允許在運行時動態決定它們的行為關系。我們希望避免將控制邏輯分散到多個地方。過去嘗試的方法是讓實體之間相互了解彼此的存在(例如,身體知道頭部是誰,頭部也知道身體是誰),但這種雙向耦合讓代碼變得不自然、不好維護。
我們反思后發現,這其實是一種典型的“面向對象陷阱”:人為地將邏輯拆分到對象內部,強行以“每個實體自己更新自己”的方式編寫代碼。這樣做的問題在于,很多行為其實跨實體才有意義,比如頭和身體協調移動,如果邏輯分散,就必須在多個地方處理相互之間的通信,顯得既不直觀也不高效。
更合理的方式是:直接以“組合控制器”的思維來處理,比如寫一個“頭-身體控制器”,專門處理這種組合。它內部檢查哪些部分存在,然后統一更新。這樣既避免了分散的邏輯,又提高了靈活性。即使沒有頭部,那控制器也只是跳過頭部邏輯即可,不會導致錯誤。
我們希望把重點從“每個實體控制自己”轉向“控制器跨實體統一處理”。這個方式更加貼近我們在實際程序開發中常用的思維方式:關注算法本身,而不是被人為拆解的對象邊界。
目前我們正在嘗試實現這一新想法,先從一個具體例子出發,比如重新實現主角的頭和身體控制邏輯,看看是否能更清晰簡潔地表達意圖。之后,我們計劃實現另外一個案例,比如蛇形怪物,用于驗證這個架構是否具有普適性。如果能支持多個不同場景,我們就可以確認這種架構是更為合理和健壯的。我們的目標是建立一個可擴展、靈活、代碼結構自然的實體控制系統。
Blackboard:控制器模板
我們目前的實體系統采用了“模擬區域(sim region)”的機制。在這個機制中,世界被劃分為多個區塊(chunks),當需要模擬某一區域時,就會將該區域的所有區塊中的實體加載并“解包”到當前模擬區域中。在這個過程中,我們會將所有實體插入一個哈希表中,以記錄它們的 ID,以便其他實體可以通過 ID 來引用它們,比如實現“追蹤某個目標”的行為。
這種“實體引用”的方式在某些情況下依然是合理的,尤其是當我們只需要表達一個實體觀察或攻擊另一個實體時。但對于多個實體需要作為一個整體來協同工作時,這種機制就不夠用了。我們的問題主要出在這部分——多個實體組成一個統一的操作單元,比如“一個身體加一個頭”這種組合,我們需要找到更自然的解決方法。
于是我們提出了一種新的設計思路:引入“控制器模板(controller templates)”。這里的模板并不是 C++ 模板,而是更通俗意義上的“模式”或“結構藍圖”。每個控制器模板代表一個可供實體組合的邏輯結構,例如:
- 英雄控制器:包括一個頭部槽位、一個身體槽位;
- 蛇型怪物控制器:可能包含多個身體段槽位、一個頭部槽位;
- 其他復雜生物控制器等。
在這種新系統中,只有那些可以被控制器管理的實體,才會擁有一個額外的數據結構,我們可以稱之為“控制器標簽(controller tag)”。它包含以下幾個關鍵字段:
- 模板類型(Template Type):表示該實體應該歸屬于哪個控制器模板,比如英雄控制器、蛇控制器等;
- 實例 ID(Instance ID):表示當前這個控制器實例的唯一標識,多個實體擁有相同的實例 ID 意味著它們屬于同一個控制組;
- 槽位(Slot):表示該實體在模板中的具體位置,比如是頭、身體、手臂,或者是某個數組槽位(支持多個實體插入)。
這些信息是每個實體自身帶有的,而不是額外維護的集中數據結構。我們再維護一個以實例 ID 為鍵的控制器實例哈希表,用于在模擬區域解包實體時動態構建控制器。當一個實體被加載進模擬區域時:
- 我們檢查其控制器標簽;
- 然后根據標簽中的實例 ID,在控制器哈希表中查找是否已有對應的控制器實例;
- 如果有,就將該實體放入指定的槽位中;
- 如果沒有,就創建一個新的控制器實例并加入該實體。
這套機制具有高度的靈活性。例如:
- 如果我們想讓某個實體“拾取”另一個實體并將其作為自己的手臂,只需要修改該實體的控制器標簽中的模板類型、實例 ID、槽位即可,無需做額外的綁定邏輯;
- 如果某個組合體中的某個部位(比如手臂)被打掉,那么該實體依然存在,只是脫離了原控制器組;
- 如果之后原主體重新靠近這個“斷肢”,它的控制器仍能識別該實體是自己的一部分,因為它的標簽信息仍然存在。
這個系統沒有任何額外的引用維護成本,也沒有清理或生命周期管理的負擔。實體的控制組是每次模擬時根據其標簽信息自動組合的,不依賴于運行時的持續狀態,因此也不會出現失效引用或資源泄漏的問題。
這種機制從根本上避免了面向對象帶來的結構僵化問題。我們不再為每個實體都構造一個特定的控制邏輯,也不需要維護復雜的雙向引用關系。控制器的邏輯是集中統一的,按模板組織而不是散布在實體之間,從而保持了代碼的簡潔性和自然表達力。
因此,我們決定采用這個設計方案,重構之前關于組合實體的實現,并放棄昨天那套基于互相引用的邏輯方式。下一步我們將清理掉之前的相關改動,并開始搭建這個新的控制器模板系統,以驗證它的有效性與可擴展性。
game_entity.h:移除 entity_relationship
和 ReferenceIsValid
,以及實體結構體中的 PairedEntityCount
和 PairedEntities
我們在處理 sim region(模擬區域)與 entity(實體)結構時,決定對之前的設計進行調整和修復。
我們原先引入的一套關系系統(relationship system),現在決定徹底移除,不再在實體之間顯式存儲這些關系。取而代之的是,回歸之前的做法,重新采用實體引用(entity references)的方式來表達實體之間的聯系。
具體修改如下:
- 原有的實體關系系統將完全移除;
- 不再在 entity 內部保留任何用于描述關系的字段或結構;
- entity references(實體引用)機制將作為保留功能繼續使用,即實體之間通過引用其他實體的 ID 來實現追蹤、攻擊、觀察等交互邏輯;
- 驗證引用是否有效的邏輯,也無需再考慮是否存在“關系結構”,這一部分相關的判斷邏輯也將一并清理;
- 所有與關系相關的字段、檢查函數、結構將不再需要,被清除出代碼;
- 最終的替代方案,是以“模板控制器”(template controller)的方式組織邏輯結構,而不是用顯式的實體關系來維持結構化的整體。
此外,正在構思的模板系統將被逐步引入,作為原本關系系統的邏輯替代核心。具體細節還在調整,但可以明確的是,實體之間將以更靈活、更輕量的方式進行組合和控制。我們將在接下來的流程中繼續推進這一系統的實現與集成。
game_entity.h:引入 entity_controller_type
、entity_controller_slot
和 entity_controller_id
,并在實體中添加這三項
我們現在在 entity
中引入了一個全新的概念——“控制器類型”(entity_controller_type
),目的是為了在邏輯上組裝出更復雜的“生物體”或“組合體”。這是對傳統實體關系模型的進一步簡化與抽象。
具體設計如下:
控制器類型
我們定義了一個枚舉類型的控制器類型字段(entity_controller_type
),用來表示當前實體是否參與某種控制器結構,示例如下:
None
:表示該實體不參與任何控制器結構;Hero
:代表它是一個“英雄”控制器的組成部分;Snake
:為將來可能的“蛇形”控制器預留。
控制器的作用是將多個實體在模擬區域中邏輯地組合成一個整體結構,用以統一行為控制、物理模擬或邏輯運算。
控制器插槽(Slot)
我們為實體加入一個 controller_slot
字段。這個字段用于指明該實體在其所屬控制器中的位置或角色。當前將其設計為一個簡單的整型索引,具體用途將在后續通過控制器代碼定義。
例如:
- 一個英雄控制器可能包含多個插槽:頭部、身體、左臂、右臂等;
- 實體通過指定插槽編號來表明它在結構中的位置;
- 插槽機制未來可以擴展為支持數組插槽(比如一個多頭怪有多個頭)。
控制器實例 ID(Instance ID)
此外,我們引入了 controller_instance_id
字段,該字段用于將多個實體歸屬于同一個控制器實例。也就是說,只要多個實體擁有相同的 controller_instance_id
,它們就會在模擬時被組裝成一個邏輯整體。
當前,我們暫時將 controller_instance_id
和 entity_id
分開處理,避免邏輯混淆。它們來源不同、使用范圍不同,不應該互換使用。控制器實例 ID 僅用于組裝邏輯結構,不涉及實體唯一標識系統。
元實體(Meta-Entity)的可選性與數據結構
我們還未完全決定“控制器本身”是否需要擁有獨立的數據結構或存儲空間。
當前設想是盡量避免為控制器本身保留冗余數據。我們更傾向于控制器的整體行為由其組成的子實體決定,例如:
- 如果一個“頭部”實體擁有智能相關的屬性,那么整個組合體就表現出智能;
- 一旦頭部被移除或破壞,整體智能能力就喪失;
- 這使得整體行為更自然、動態和模塊化,不需要在中心結構中強行保留某些全局設置。
如果未來發現控制器本身確實需要存儲特定的狀態或數據結構,我們可以通過設置獨立的“控制器實體”并將其賦予一個特定 ID 來實現。但目前保留這一點為后備方案。
總結
我們正在以更靈活、松散耦合的方式替代傳統實體繼承關系或顯式組合結構:
- 每個實體通過控制器類型、插槽編號和控制器實例 ID 進行動態組合;
- 所有結構在模擬區域加載時按需拼裝,無需靜態綁定或生命周期管理;
- 系統更加有機、模塊化、無狀態,允許實體被自由組合、斷裂和重組。
接下來將繼續推進代碼實現,通過先行編寫具體控制器邏輯(例如 Hero Controller)進一步驗證這一機制的實際效果。
game_entity.h:思考這個系統將如何替代 entity_type
我們現在正在對整個實體系統進行結構性的重構,目的是徹底擺脫“實體類型”的概念,用一種更靈活、更模塊化的控制器機制來代替。這種機制不僅簡化了架構設計,也增強了系統的可擴展性和動態行為表現。
以下是我們當前的核心設計理念和具體變動:
1. 實體不再有“類型”
我們不再為實體分配固定的“類型”(如 Hero、Enemy、Glove 等)。實體本身不攜帶類型標識。取而代之:
- 實體的“類型”由當前附加在它身上的控制器決定;
- 控制器才是唯一控制實體行為的載體,一個實體只能同時被一個控制器操控;
- 實體的屬性和行為不是由“我是什么”決定,而是由“我正被誰控制、我正處于何種結構中”所決定。
這種設計思路強調了行為的外部控制性而非內部本質性。
2. 控制器即行為裝配器
- 控制器是一種組合邏輯:多個實體被聚合到某個控制器中,形成一個統一體;
- 每個控制器有自己的插槽系統,決定每個部位對應的實體;
- 控制器可以是玩家控制器(hero controller)、AI 控制器(如 snake controller)等;
- 控制器的設計允許動態更換實體,例如一只手斷掉之后,可以被其他控制器重新拾取和使用;
- 控制器是當前行為的“主腦”,實體只是組成部件。
3. 結構靈活、可重組
- 我們將結構設計得更像“拼裝生物”;
- 比如,一個手套(glove)不再是“手套類型的實體”,它只是正好被某個控制器使用在 glove 插槽中;
- 一旦該實體脫離控制器,它不再具有任何“類型”意義,只是一個普通組件;
- 其他控制器可以接管它,并在不同上下文中使用它,體現出極高的行為靈活性和重構能力;
- 系統支持組合體斷裂、合并、替換等動態操作。
4. 類型信息被拆散為多種屬性
由于取消了統一的“類型”字段,我們需要將實體的原始“類型含義”拆解為多個獨立系統進行控制:
- 繪制邏輯(rendering)不再由類型決定,而由當前控制器+插槽上下文決定;
- 行為邏輯(AI、輸入響應)由控制器腳本決定;
- 模擬邏輯(碰撞、物理等)由控制器和其聚合結構決定;
- 控制權歸屬:通過控制器哈希(controller hash)字段標識每個實體當前隸屬于哪個控制器。
5. 引用系統調整
我們仍然保留實體引用(entity reference)的系統,但用途被局部化和場景化:
- 不再用于表達控制器—實體的配對;
- 而是用于表達如“目標引用”、“攻擊目標”、“當前追蹤對象”等臨時邏輯行為;
- 實體之間不再需要雙向綁定引用,也不再有長期耦合;
- 控制器取代了原有的結構性連接,成為唯一的組合機制。
6. 哈希系統更新
- 原有的
entity_hash
用于標識單個實體; - 新增了
controller_hash
字段,用于標識控制器實例; - 每個控制器通過哈希值管理其結構內的所有實體,支持快速查找與更新;
- 雖然叫“entity_controller”,但我們目前只定義這一類控制器,不涉及其他控制器系統,因此命名暫不修改。
總結
我們正在構建的是一個完全解耦、模塊化、可重組的實體-控制器系統:
- 實體僅為物理/邏輯組件,沒有固有類型;
- 控制器承擔邏輯聚合、行為協調和狀態分發;
- 結構變化實時同步、自動響應;
- 徹底擺脫傳統面向對象實體建模中“類型中心論”的束縛;
- 架構更自由、抽象層更清晰,具備極高的靈活性和未來擴展潛力。
接下來的工作將集中于實現第一個控制器樣例(如 Hero Controller)以驗證整體系統設計的合理性。
game_entity.h:將 entity_controller_*
重命名為 brain_*
我們覺得用“controller”這個詞不太合適,因為它容易讓人聯想到游戲手柄控制器,所以想換一個更貼切且獨特的名字。考慮用“brain”(大腦)來替代,這樣能更好地表達它作為實體行為和決策核心的角色。雖然“brain”也不是最完美的描述,但至少能夠唯一且明確地區分開。
在設計上,我們定義了一些“槽位”(slots),每個槽位就是一個“brain”的位置,用來組織和管理不同部分的行為邏輯。接下來會把每個槽位進行具體實現,比如先把它們設為二分之一(50/50)這樣的結構,方便調試和測試。接下來會寫代碼來實現這些想法。
總體來說,就是從“控制器”概念轉向“腦”這個概念,強調這是實體的思考和控制中樞,提升語義的準確性和系統的直觀性。
game_sim_region.h:引入 brain_hash
并在 sim_region
中添加一個 brain_hash
數組
我們討論了“brain hash”的概念,這個名字聽起來有點像某種不太好的東西,但它實際上是指向我們定義的某個“brain”(腦)實體的指針。這里會有一個“brain ID”,用來標識不同的腦。這個ID其實就是一個索引,表示不同的腦實例。
考慮到這些腦實例的數量不會很多,所以不太適合用過大的哈希表,比如2的冪次方大小的哈希表。未來可能會考慮用質數大小的哈希表,并用取模操作來處理,但這只是細節問題,隨時可以調整。
關于哈希的實現,這里使用的是內部鏈式哈希結構,可以處理哈希沖突和重復項,邏輯上是合理的。實體哈希里也可以直接存儲實體本身,但這可能會影響緩存性能,需要權衡。
腦的集合會隨著實體加入和分配控制器ID逐步組裝完成。考慮到維護這些哈希結構的開銷,尤其是清理過程的成本,哈希表的大小和清理策略是性能的關鍵點。清理可以采用增量式的方式減輕每幀的開銷。
控制器ID中預留了0作為“無控制器”的特殊值,這樣可以簡化判定。因為有了這個0值,腦類型(brain type)本身可能不需要專門的空值標記。如果控制器ID是0,腦類型可以不設置,簡化了邏輯。
在模擬的執行步驟中,涉及了身份識別(identity)相關的調用,但現在看來這部分代碼可能已經很少被用到,有可能會被簡化或移除。
總結來說,整個設計的核心是通過腦哈希來管理實體的控制器狀態,用哈希結構高效地查找和管理這些控制器,同時預留靈活的擴展和優化空間。
game_world.cpp:將 PackEntityReferenceArray
改名為 PackEntityReference
,并只處理一個實體
我們在清理之前的代碼損壞,特別是處理實體引用相關的部分。現在實體引用的數組結構基本不再使用了,改為更簡單的形式。核心邏輯是判斷實體引用是否有有效的指針,如果有指針,就把引用的索引設為指針所指實體的ID;如果沒有指針,則根據具體情況清除或者保留索引。
具體來說,如果當前模擬區域(sim region)中沒有包含被引用的實體,則認為這個引用應該被移除。還需要判斷該實體是否被刪除,刪除的實體引用也要清除掉,以避免引用已經不存在的實體。
在“打包”(packing)實體引用數據時,實際上并沒有真正壓縮數據,而是為了保持引用的指針和索引同步做的操作。這個過程可能比較臨時,但目的是確保引用狀態的正確性。
還有一個復雜情況是:如果引用的實體從模擬區域移出,即不再可見,指針會變成空,但索引可能仍然存在,這時需要區分索引是因為實體出區域而暫時不可見,還是因為引用已經被顯式清除。如果引用被清除了就要刪除索引,否則保留索引。
但打包時并不總有模擬區域的信息,比如世界創建階段就沒有模擬區域,所以需要有機制允許傳入模擬區域參數,如果沒有傳入,則跳過相應檢查。
整體來說,這套機制的目標是保證實體引用的有效性和一致性,能夠正確處理實體被刪除或移出模擬區域的狀態,同時保證引用數據在不同情況下合理更新和清理。當前邏輯還有一些不確定性,后續需要繼續調整和完善。
game_world.cpp:讓 PackTraversableReference
和 PackEntityIntoChunk
接收 sim_region
作為參數
我們目前正在處理模擬區域(sim region)相關的數據傳遞問題,尤其是實體引用在打包(pack)操作中如何正確保留或清除的問題。當前的設計是把模擬區域參數傳遞到引用處理的相關函數中,目的是為了判斷引用的目標實體是否還存在于當前模擬區域中。
這種傳遞方式已經在多個地方被使用,我們在多個函數中將模擬區域作為參數傳入,以便可以對照區域內的實體狀態進行邏輯處理。但整體實現方式仍有些不夠優雅,有一些結構顯得“臟”(不干凈、不規范),存在結構臃腫、邏輯重復或者耦合度過高的現象。
我們目前還沒有一個特別聰明的替代方案來簡化這個流程,也不確定是否有更優雅的方式可以替代模擬區域參數的傳遞。所以只能在現有結構下繼續推進,邊做邊調整。
接下來繼續處理“是否已刪除”的判斷邏輯,也就是is_deleted
相關的檢查機制。這個機制的作用是在引用打包過程中識別已被刪除的實體,并將其引用清除,以防止后續邏輯中訪問已無效的實體。
我們正在逐步完善整套實體引用系統,目標是確保在動態模擬過程中,實體引用始終保持有效且同步,尤其要正確處理實體被刪除、退出模擬區域或被更換控制結構等各種邊界情況。當前的實現雖然還有不足,但整體結構已在逐漸成型,并為后續更復雜的系統交互奠定基礎。
game_entity.h:引入 IsDeleted
成員
我們現在正在處理實體刪除狀態的判斷邏輯。當前,我們使用一個實體標志位(entity flag)來表示實體是否被標記為已刪除,即 EntityFlag_Deleted
。因此,我們的刪除檢查函數 is_deleted
實際上只是對這個標志位的封裝。
這個函數的實現非常簡單:它接收一個實體作為參數,然后檢查該實體的 flags
字段中是否包含 EntityFlag_Deleted
。也就是說,函數本質上等價于調用 is_set(entity, EntityFlag_Deleted)
。
由于系統中已有一個通用的 is_set
函數用于檢查實體是否具備某個標志位,因此我們其實只是借助這個現成函數來實現對“已刪除”狀態的快速查詢。
這種方式保證了判斷邏輯的統一性與可維護性,也簡化了上層邏輯在處理實體刪除情況時的判斷流程。只要一個實體被標記為 Deleted
,相關邏輯就能自動識別并作出對應處理,比如在引用打包時剔除對已刪除實體的引用。
接下來的操作將繼續圍繞引用系統與模擬區域的整合進行,使系統能在不同運行階段正確識別哪些引用仍然有效、哪些應當被清除。這個過程雖然技術上不復雜,但需要保持邏輯嚴謹性,確保實體生命周期與引用系統之間的一致性。
game_sim_region.cpp:將部分邏輯從 LoadEntityReference
移至 GetEntityByID
并由其調用
我們目前在處理通過 ID 獲取實體哈希(get hash from ID
)的邏輯時發現了一個不一致的問題。get entity by ID
和 get hash from ID
這兩個函數看起來本質上應該是做同樣的事情——根據一個 ID 找到對應的實體。但是它們內部的處理方式卻不一致。
我們意識到,這可能是架構變動過程中遺留下來的冗余實現。隨著系統不斷重構,有些函數其實已經可以統一起來使用同樣的底層邏輯,而不是各自實現自己的方法。
我們傾向于將 get hash from ID
簡化為直接調用 get entity by ID
,這樣不僅可以減少重復代碼,還能使行為更一致。get hash from ID
完全可以作為一個更輕量、便于復用的接口存在,它最終只是對實體引用的快速索引。
雖然我們也考慮了是否把它實現為一個“free函數”(free function),用來更靈活地封裝這類功能,但目前我們決定先以直接方式來處理它,也就是簡單封裝調用 get entity by ID
,保持實現清晰和簡單。
這個階段主要在清理、統一邏輯接口,消除過時的代碼和結構分裂。接下來我們還將對整套實體引用、哈希和模擬區域之間的整合做進一步的理順,以確保每個功能模塊的職責清晰,行為可靠。
game_sim_region.cpp:讓 PackEntityIntoWorld
接收 sim_region
我們目前正在處理 pack_entity_into_chunk
函數的參數問題。我們注意到它現在只接受一個 sim_region
參數,而不是三個參數。這是正確的行為。
在當前設計中,我們引入了 sim_region
(模擬區域)作為集中數據結構,用于包含與該區域相關的所有實體哈希(entity_hash
)和其他上下文信息。因此我們不再需要將多個哈希表單獨作為參數傳入,比如 entity_hash
、controller_hash
等,因為它們已經被統一包含在 sim_region
之中。
我們還檢查了多個調用 pack_entity_into_chunk
的位置,發現有些地方傳入的是 0
(即空值或無模擬區域的情況),這些地方通常是不會存在 sim_region
的上下文,比如在“將實體插入到世界中”這一類初始化或預處理操作中。在這些場景中,模擬區域尚未建立或不適用,因此函數不會傳入 sim_region
。
而另一些調用點,比如處理實體更新的部分,則明確地傳入了有效的 sim_region
。我們通過對比可以確認,現在的接口設計是統一且合理的。沒有傳入 sim_region
的調用是因為其運行環境中確實不存在模擬區域;有傳入的場景也對應的是需要查找或寫入區域數據的上下文。
這個變更是我們結構重構的一部分,目的是減少函數參數數量、簡化接口設計,并通過 sim_region
這種集中式結構將相關依賴統一管理,提高可讀性和維護性。接下來我們會繼續清理舊代碼,確保所有地方都遵循這套新的參數傳遞方式。
game_world_mode.cpp:從 AddPlayer
中移除 BodyRefs
和 HeadRefs
,為 Body 和 Head 初始化 brain_id
和一些 brain 數據
我們當前正在進行結構重構,目的是進一步優化實體系統的初始化與關聯邏輯,特別是與“brain ID”(大腦標識符)相關的部分。
首先,我們已經廢棄了一些舊的機制,例如舊的哈希表相關字段不再使用了。這些字段已經被統一納入更現代化的 sim_region
(模擬區域)結構中管理,因此舊機制已經完全不再必要。
現在,我們的目標是在新建實體時,為其分配一個 brain ID
。我們采用的是一個簡單的分配策略:調用一個類似于“添加 brain”操作的函數,該函數不需要立即創建一個完整的 brain 實例,它只是返回一個可用的 ID 令牌,供后續邏輯引用。
接著我們回到實體(entity)本身。我們會在實體結構中設置幾個關鍵字段:
controller_type
controller_slot
- 以及
brain_id
這些字段都在這個初始化階段被賦值。特別地,我們還引入了一個概念叫 brain_slot
,但目前我們對它的具體用途和設計方式還在探索中。暫時先按照簡單思路來處理,即將不同用途的實體(例如:人物控制器與 AI 控制器)分別放入 slot 0 和 slot 1,雖然這不是最終實現方式。
我們目前不打算通過手動記住具體 slot 索引值來管理這種綁定關系,因為這樣不但容易出錯,還會導致系統難以維護。我們希望未來可以設計一個更干凈、自動化的機制,避免硬編碼 slot 編號或依賴大量的枚舉定義,從而提升結構的可讀性和可擴展性。
綜上所述,這部分重構的重點在于:
- 廢除舊字段,轉向統一結構管理;
- 實體創建時分配獨立
brain_id
; - 建立 controller 與 brain 的綁定關系;
- 避免 slot 硬編碼,通過更直觀的設計提升系統健壯性。
未來我們會繼續調整 brain_slot
的處理邏輯,使其更簡潔高效。
game_world_mode.cpp:引入 AddBrain
函數
我們正在實現一個“添加 brain”的方法,其核心功能非常簡單:只是生成一個新的 brain ID
,并不會立即創建完整的 brain 對象。具體做法是直接使用當前存儲結構中的計數器(類似于 last used storage index
)來生成一個新的 brain ID
。隨后,這個 ID 就可以作為引用,和其它需要控制邏輯的實體進行關聯。
接下來,我們處理 index_map
和 identity
相關的邏輯。由于架構發生了變化,這部分代碼也需要做出調整。其中,index_map
是用來將某個標識符映射到相應實體索引的結構。我們更新了它的使用方式,確保和新的 brain 管理邏輯兼容。
當前令人振奮的地方在于,我們開始擺脫了之前繁瑣的控制器數據流傳遞方式。之前的設計里,為了支持多種控制器和實體組合,我們不得不通過各種中轉結構來“傳遞”和“找回”控制器的數據,例如把控制器綁定到實體,再在后面找回并使用。這種設計很容易導致代碼混亂,也不夠靈活。
現在我們構建的新機制,將支持一種更自然、統一的控制流程。設想我們在編寫模擬邏輯時,只需要像編寫正常邏輯那樣,直接遍歷所有的 brain,然后讓它們控制各自的實體。例如,對于玩家實體,只需“對每個 brain 執行一次決策邏輯”,然后繼續模擬世界,不需要顯式從結構中“取回”控制器,再綁定回實體,這樣的中轉流程將完全不需要。
總之,新的系統設計具備以下優勢:
- 統一邏輯接口:不管是玩家控制、AI 控制,或是其他行為邏輯,所有實體都通過統一的 brain 接口處理;
- 更自然的流程控制:控制邏輯更像傳統的思維模型,比如“讓每個 brain 做出決策”,而不是繞彎子去綁定/解綁/傳遞控制器;
- 減少中轉結構依賴:消除了大量中間數據結構(如 controller mapping)的負擔,提升整體系統的清晰度;
- 代碼更簡潔:便于未來擴展行為種類(如 AI、物理驅動等),無需針對每種控制方式寫特殊分支。
接下來我們還需要進一步回顧 sim_region
的實現細節,特別是它是如何組織 brain 實體的,以便高效地完成遍歷和處理。這個機制是整個控制系統流暢運行的關鍵。
game_sim_region.h:在 sim_region
中添加 MaxBrainCount
、BrainCount
和 *Brains
我們目前需要為系統中所有的 brain 實體建立一個統一的數據結構管理方式,其思路與管理實體(entity)的方式類似。也就是說,我們將設定一個最大 brain 數量(max_brain_count
),再配合一個實際計數器(例如 brain_count
或 used_brain_count
)和一個 brain 數組(brains[]
)來進行管理。
這套結構會在模擬區域(sim region)初始化時被創建或清空,在任何實際處理邏輯執行之前就準備好。這樣我們就能確保在進入更新流程前,所有 brain 的存儲空間已預先分配好,訪問時具有良好的內存局部性,效率更高。
此外,brain 也將擁有自己的哈希表結構,這個哈希表會作為輔助索引機制,幫助我們快速通過 ID 查找具體的 brain 實例。其內部實現可能和實體使用的哈希表一樣,采用鏈式沖突解決方式。
因此,我們可以總結為如下結構:
-
靜態預分配
在內存中預分配一個固定長度的 brain 數組,長度為max_brain_count
。 -
計數器跟蹤
使用brain_count
來追蹤當前實際已用的 brain 數量,用于分配新的 ID。 -
哈希表索引
提供brain_hash_table
來支持通過 ID 快速查找 brain,哈希沖突時采用鏈式結構解決。 -
初始化順序
在 simulation region 開始處理前,優先初始化 brain 數據結構,以確保后續操作能直接訪問。
這套機制的好處是與實體管理邏輯保持一致性,使得 brain 可以像實體一樣參與系統的通用邏輯流程,同時也便于維護和調試。后續處理過程中,任何需要訪問 brain 的地方都可以通過該系統快速獲取所需數據,無需特殊處理邏輯,從而提升系統的可擴展性和一致性。
game_world_mode.cpp:在 UpdateAndRenderWorld
中循環處理所有 brains,執行其邏輯
我們在模擬區域(sim region)中管理 brain 實體的方式基本成型,接下來我們會對每一個 brain 實體進行迭代處理。具體實現上,我們會遍歷 brain 索引列表,每一個索引對應一個 brain 實體:
-
遍歷 brain 索引
通過sim_region.brain_cap
得到總容量或計數,使用一個循環遍歷這些索引。 -
獲取 brain 實體
在每次迭代中,我們通過sim_region.brains[brain_index]
取得當前的 brain 實體指針,準備對其執行邏輯操作。 -
根據 brain 類型執行邏輯
每個 brain 都有一個類型標識字段,例如Brain_Hero
,Brain_Snake
等。我們根據其類型選擇對應的邏輯執行路徑:Brain_Hero
:玩家角色的行為控制邏輯;Brain_Snake
:可能是某種 AI 或其他單位的行為;Brain_Familiar
:一種附屬單位,是否是單獨的 brain 仍待確定;- 其他未知或未定義類型則落入默認處理路徑(可能為空或報錯處理)。
-
行為代碼的位置
各類 brain 的邏輯會集中在對應的分支中處理,當前Brain_Snake
的代碼尚未編寫,Brain_Familiar
也未完全決定其行為模型是否獨立。未來可能還會加入新的行為字段,用于指示當前行為狀態(如攻擊、巡邏、待機等),這些將作為單獨結構維護。 -
控制器輸入采樣
對于需要用戶控制的 brain(例如 hero),我們會采樣控制器輸入,用于決定行動。這部分邏輯目前已存在于某段代碼中,我們考慮將這段邏輯提取為獨立模塊,以便統一調用,提高整潔性。 -
Hero 控制結構的調整
原來 Hero 有 head 和 body 兩個實體,分別處理,現在我們可以將兩者合并為一個整體處理,避免重復冗余邏輯,使得控制流更清晰。具體整合將在編譯錯誤修復后再進一步推進。
整體思路是構建一個統一的 brain 執行框架,按類型分派對應行為邏輯,使整個模擬過程更清晰、更靈活,并預留擴展接口以便后續添加更多行為狀態與處理模式。這個結構也有利于以后引入調度系統或多線程處理。
game_sim_region.h:在 brain
結構中添加 brain_type
我們當前在模擬區域(sim region)中加入了用于管理 brain 實體的數據結構。雖然目前這些 brain 相關的結構和邏輯仍然臨時放置在模擬區域的實現文件中,但接下來我們打算將其獨立出來,放入單獨的文件以提高模塊化和可維護性。現在暫時不動,但未來一定會進行這一步。
接著我們需要處理其他相關結構中的一些“parity count”之類的字段或機制。這類字段通常用于跟蹤對象的版本、同步狀態或變化次數等,是數據一致性檢查或更新流程中關鍵的一環。
總的來說,當前階段的主要任務包括:
-
brain 結構整合在 sim region 中
當前所有與 brain 有關的結構和邏輯都臨時寫在 sim region 內部。這雖然便于開發調試,但長期看不利于代碼清晰度和邏輯分離,因此后續我們會將這部分抽出,形成獨立模塊。 -
準備遷移但尚未執行
我們明確知道這部分內容應該轉移出去,但在進行結構穩定和基本功能完善之前暫時不會移動。當前重點是先保證邏輯正確和流程跑通。 -
parity count 的存在表明有版本控制
我們還需要處理或重構一些現有的 parity count 相關邏輯,這可能用于判斷某些數據是否需要更新,或是否處于一致狀態。這種機制將在整合 brain 管理與實體同步時扮演重要角色。
整體而言,這一階段是為系統建立清晰架構做準備。在功能實現的同時,也在為模塊解耦、數據一致性機制和更清晰的控制流打基礎。我們會邊開發邊調整這些內容,等一切穩定之后再進行結構優化和代碼遷移。
game_world_mode.cpp:暫時禁用 PairedEntities
相關代碼
我們目前在進行一些清理和重構的準備工作,特別是為接下來的開發打好基礎。這一部分的調整主要是為了避免在結構尚未完善的情況下留下不完整或錯誤的狀態,以下是目前做的具體處理和目的說明:
一、暫時清空無用結構以避免干擾
我們有一些不再需要或者暫時不需要參與邏輯處理的字段或結構。當前我們選擇將它們“清零”或移除處理邏輯以保持系統簡潔,并確保不會因為這些尚未使用的部分導致程序進入錯誤狀態。此操作是臨時性的,未來會根據需要逐步引入或重構它們。
二、保持系統在可運行的最小狀態
我們做的不是永久刪除,而是為了維持代碼的基本完整性和防止編譯錯誤,以便下一步開發時可以在一個相對干凈且穩定的狀態上繼續推進。
三、處理返回值與調用一致性問題
在調整函數時,比如某些函數返回值結構發生變化,或者臨時不再執行某些邏輯,為了防止中斷調用鏈,我們將返回值填補為默認值或者做了結構兼容性處理。目的是確保已有調用代碼在功能縮減的情況下依然可以繼續工作,不會出現崩潰或錯誤。
四、優化對引用索引(Reference Index)的處理邏輯
我們在處理打包引用(pack entity reference)時加入了一個優化判斷:
**如果引用的 index 已經是零,說明這個引用已經是無效狀態,**我們就不需要再繼續對它進行處理或判斷是否清除。這個判斷可以顯著減少不必要的計算。
五、為后續開發預留穩定狀態
這部分的改動目的是為了在下一個開發周期(例如計劃中的周一開發)開始時,能夠在一個清晰、無沖突、邏輯明確的基礎上繼續推進,不被當前狀態混亂干擾思路。
總之,這是一輪必要的“代碼凈化”過程,確保當前狀態不會阻礙后續開發,同時保持結構靈活性和清晰度,是大型系統開發中非常關鍵的一步。我們將繼續圍繞引用管理、實體狀態同步、brain 結構整合等方面深入推進。
:運行游戲,此時控制器被禁用
我們現在有一類可以理解為“禁用控制器”的功能模塊,需要找到一種更干凈、整潔的寫法來實現它們。為此,我們打算從實際的使用場景出發,先思考理想狀態下希望這些控制器的調用和行為是怎樣的,然后反向推導出具體實現的代碼結構。
具體來說,我們會先設想最理想的使用方式,看看希望怎么調用這些禁用的控制器,代碼看起來最簡潔、邏輯最清晰。然后再基于這個理想的使用模式,倒推需要怎樣的接口設計和內部實現,以便讓整體代碼既易讀又易維護。
這個過程實際上是先設計“調用端”的需求和期望,再回到“實現端”去調整代碼和架構,確保二者契合。這樣可以避免寫出難用或者冗雜的代碼,也有助于形成更合理、更模塊化的系統結構。
總之,我們的目標是用“從需求倒推實現”的方法,把“禁用控制器”這部分功能寫得既干凈又高效,同時便于今后擴展和維護。
game_world_mode.cpp:將 Hero 頭部控制器邏輯提升到 Brain_Hero
并開始編寫其邏輯
我們現在面對的是一段比較亂糟糟的控制器代碼,主要涉及“英雄頭部”和“英雄身體”相關的邏輯。我們決定先把英雄頭部的代碼整體提取出來,放進一個叫做“brain hero”的模塊里。
在“brain hero”里,有一個“controlled hero”的概念,本質上就是遍歷這些被控制的英雄,找到我們需要處理的那個英雄。雖然目前實現上還不是特別高效,比如控制器ID和實際物理控制器ID沒有完全對齊,但暫時可以接受,后續可能會優化。
控制器ID會用作腦部ID(brain ID),這在判斷和關聯控制時比較方便。提取完頭部的代碼后,我們還會把身體相關的代碼也拿出來,準備放到一起。當前對身體部分的具體處理還不明確,但先把代碼整理到一起,方便后續清理和改進。
整體思路是先把分散、雜亂的代碼模塊化,集中管理,然后逐步優化,讓控制器和英雄的邏輯更清晰,更易于維護和擴展。
我們現在在處理一段比較混亂的控制器相關代碼。首先,我們決定將所有和“hero head”(英雄頭部)相關的邏輯整理出來,并將其遷移到 brain_hero
相關的代碼模塊中。在這個模塊中,有一個“controlled hero”(被控制的英雄)的概念,其實就是我們需要找出當前要操作的那個英雄實體。
原本的做法是在所有被控制的英雄中查找當前對應的那個,這其實不是特別高效。理論上,我們完全可以讓控制器的 ID 與實際的物理控制器(比如 0、1、2、3)一一對應,這樣查找就可以更直接和高效。雖然暫時還沒實現這個優化方式,但現階段我們會簡單地將 brain ID
和 test_hero
綁定起來,這樣也能滿足當前的邏輯需求。
接著,我們還要把“hero body”(英雄身體)相關的代碼也提取出來。這部分邏輯其實不需要放在當前的位置,可以與頭部邏輯一起整理,放入 brain_hero
模塊內。
通過這一系列整理,我們的目標是讓控制邏輯更加模塊化、結構更加清晰。雖然目前還沒有完成對控制器 ID 優化的處理,但當前的重構為后續優化打下了基礎,也讓代碼變得更易維護和擴展。
現在的任務是處理腦控邏輯的部分
現在的任務是處理腦控邏輯的部分:我們擁有一個“brain”(腦控單元),它控制“head”(頭部)和“body”(身體)兩個部分,而這兩個部分并不總是同時存在。因此,我們的邏輯需要適應這種不確定性。
過去,我們使用一種比較原始的“實體”結構去做判斷,現在我們不再依賴這個統一的實體概念,而是分別處理 head 和 body。我們會將某些代碼限定為“僅作用于 head”,而其他部分則適用于 head 和 body 的共同處理邏輯。例如:
- 某些邏輯僅在 head 存在時運行(這些屬于“head 風格”代碼);
- 其他邏輯不區分是 head 還是 body,通用執行。
在清理的過程中,為了判斷哪些地方還在引用已經廢棄的 entity
變量,我們使用編譯器來輔助識別,比如直接編譯,查看報錯提示,定位代碼位置。
此外,我們明確了每個 hero 的 brain ID
會與其對應的 brain 保持一致關聯,在后續操作中使用統一的 brain ID
來查找對應的控制單元或行為模塊。
總之,這一輪重構主要是為了讓代碼邏輯更清晰、可維護性更高,取消原始實體結構,轉為更模塊化的“head-body”腦控模型,并逐步清除遺留代碼中對舊結構的依賴。
game_sim_region.h:在 brain
中添加 brain_id
我們目前對實體的 ID 做了重復存儲,一方面實體自身已經有 ID,另一方面又在哈希表中保留了這個 ID。從邏輯上來說,這是為了加快比較效率,但實質上這更像是一種優化上的嘗試,并沒有明確證據證明這樣做真的帶來了性能收益。
所以我們認為這種做法屬于過早優化。在沒有性能瓶頸數據支持的情況下貿然增加這種冗余結構,并不合理。相反,這可能會引入額外的復雜性與維護成本。當前來看,這種 ID 的重復存儲并沒有明顯優勢,因此應當暫時去除這部分邏輯,除非之后通過實測發現它確實對性能有顯著提升。
總結來說,我們需要:
- 停止在哈希結構中冗余保存實體 ID;
- 在未來通過性能分析工具驗證是否真的存在必要性;
- 在代碼設計階段避免“未驗證的優化”主導決策,優先保證結構清晰可維護。
game.h:將 controlled_hero
中的 entity_id
改為 brain_id
我們當前正在處理 handmade.h
中與控制器相關的結構,特別是那些“受控英雄”的部分。在這里,我們使用的是 brain ID
來標識每個控制器關聯的實體。
目前邏輯中涉及 brain ID
的訪問時出現了問題,比如報錯提示 value
不是 brain ID
的成員。這意味著之前我們可能誤以為 brain ID
是一種包含值成員的結構,實際上它可能是個索引(index)類型,而非完整對象。
這個問題暴露出兩個方面的內容:
-
關于結構定義的誤解
brain ID
被當作有.value
的結構使用,但實際可能只是個簡單類型,比如整數或者別名類型;- 如果是別名類型,就無法通過
.value
方式訪問其中值。
-
關于控制器和大腦 ID 的映射方式
- 當前我們先保留了
controlled heroes
映射到brain ID
的邏輯; - 但實際上我們**很可能可以通過逆向查找(reverse lookup)**直接從實體獲取其
brain ID
,從而消除這一冗余結構; - 暫時不進行這一重構是為了避免引入過多新問題,保持已有邏輯的穩定。
- 當前我們先保留了
接下來我們需要注意:
- 確認 brain ID 的實際類型定義,避免錯誤地調用不存在的成員;
- 審視是否確實需要 controlledHeroes 數組,是否可以更自然地將控制器與實體直接綁定;
- 最終的目標是讓控制器的綁定邏輯更自然、更統一,減少冗余結構與多余遍歷。
總之,當前我們暫時維持原有結構以確保編譯通過,但后續應當逐步重構,利用更加簡潔的方式進行控制器與實體的管理。
game_world_mode.cpp:修復編譯錯誤
我們目前正在整理和清理與實體(entity)及其“大腦邏輯”相關的代碼,特別是針對實體的“頭部(head)”部分的一些調試性邏輯,以及如何合理組織這些代碼邏輯結構。
以下是本階段工作的詳細整理和總結:
頭部邏輯和調試生成
- 當前處理的是用于調試目的的實體生成邏輯;
- 實體生成的行為僅在其為“頭部”存在時才會觸發;
- 存在一些變量(如
DT
)是在之后階段才構造的,所以當前階段未能立即使用; debug_spawn_code
這部分實際上只是調試用途,不應出現在正式邏輯路徑中;- 所有面向
head
的邏輯目前集中處理,如方向、狀態構造等。
代碼混亂與清理規劃
-
目前邏輯比較混亂,頭部和身體邏輯混雜;
-
下一步計劃是將邏輯按功能分塊:
- 同時存在“頭”和“身體”的情況;
- 僅存在“頭”的情況;
- 僅存在“身體”的情況;
-
這樣可以清晰劃分不同分支邏輯,避免冗余處理。
刪除實體的邏輯處理
- 調用了
DeleteEntity
的函數; - 希望支持:即使實體不存在也可以“嘗試”刪除,代碼仍能正常運行;
- 檢查確認已有實現可以支持這一操作;
- 后續邏輯中要開始有意識地檢查實體是否已刪除,以防止異常。
與 brain ID 的綁定管理
-
目前所有實體都被分配一個
brain ID
; -
代碼中出現
brain_id.value
形式,但其實brain_id
不是帶成員的結構;- 這會造成訪問錯誤;
- 后續將調整使用方式,統一訪問接口;
-
我們也計劃在行為分配時更加清晰地區分邏輯入口,不再濫用 ID 判斷。
后續改進計劃
- 清理
controlled hero
、head delta
、實體方向等相關邏輯; - 抽象出獨立的邏輯處理模塊,例如行為處理器(行為樹/狀態機);
- 優化調試代碼,不讓其干擾主流程;
- 可能引入統一的調度/更新入口,確保頭與體邏輯同步一致;
- 考慮實體是否需要完全拆分出“邏輯形態”(大腦)與“物理形態”(頭/體)。
這一階段的重點是清理頭部邏輯、精簡調試代碼、加強刪除實體的魯棒性,并對 brain ID 的使用方式進行規范。整個系統正在逐漸走向結構清晰、功能分離的方向。
game_world_mode.cpp:讓 AddPlayer
返回一個 brain_id
我們目前的邏輯結構已經逐漸清晰,代碼之間的銜接也變得更加自然。以下是對當前階段內容的詳細總結:
brain ID 與控制器(controller)的綁定機制
- 每次創建一個“大腦”(brain)實例時,會生成一個唯一的
brain ID
; - 控制器隨后會附加(attach)到這個
brain ID
上; - 這讓控制關系變得更加合理和結構化,避免了原先隨意指定控制目標的方式。
玩家(Player)的構建方式更新
-
原本在添加玩家時,我們人為規定“玩家等于頭部實體”;
-
現在不再需要這樣硬編碼;
-
目前引入的是一種新的抽象結構:
- 玩家是一組松散關聯的實體(head、body、其他組件等)的集合;
- 這些實體通過共享一個
brain ID
來標識歸屬同一玩家; - 任何擁有這個標簽的實體都將處于玩家的控制之下;
-
因此不再需要人為設置哪個實體是“主控”或“從屬”,而是通過 tag(標簽)自然地組織。
自組織式的實體歸屬機制
-
當我們創建新實體時,只要它們標記為與某個
brain ID
相關聯,它們就會**自動組合(self-assemble)**成一個整體; -
這種機制更像是一種“自底向上的解析”方式(bottom-up parsing):
- 控制權不再是手動分配;
- 而是實體根據其屬性自動決定歸屬;
-
這不僅讓邏輯更具擴展性,也提升了系統的靈活度和模塊化程度。
結果與好處
- 控制結構變得解耦、靈活、易于擴展;
- 可以輕松支持復雜結構,例如多人、召喚物、分身等;
- 無需擔心在控制流程中遺漏某一部分實體;
- 行為邏輯和控制邏輯之間的邊界也更加清晰。
整個結構正在從“手動綁定控制”轉變為“自動組織歸屬”,并通過 brain ID
構建一個統一、模塊化的控制接口,為未來功能拓展和邏輯復用提供了堅實基礎。
Q&A(問答環節)
目前階段的工作暫時告一段落,即將進入問答階段,但整個系統還需要至少再進行一次全面的整理和完善。以下是本階段的詳細總結:
當前工作回顧
- 目前僅完成了“腦(brain)”系統的初步構建;
- 實際的“控制器代碼”和“實體行為”的核心內容尚未整理完畢;
- 接下來的工作目標是清理、完善并驗證現有架構的合理性。
下一階段的工作規劃(即“Brain Week”)
-
清理控制器代碼(controller code)
- 尤其是角色身體部分(body)相關的控制邏輯;
- 目標是將控制邏輯模塊化、結構清晰;
- 去除過時、混亂的舊代碼。
-
創建并整理專屬文件結構
- 將所有“大腦”(brain)相關邏輯抽出,集中放入一個或一組獨立的文件中;
- 例如:
brains.h/cpp
或其他按類型分的模塊; - 實現不同實體類型的大腦行為劃分(如英雄、蛇等)。
-
實現并測試蛇(Snake)的大腦邏輯
- 編寫
brain_snake
的具體實現; - 將其接入整體系統并實際運行測試;
- 驗證結構是否支持非玩家角色或 AI 實體。
- 編寫
-
評估整個系統的設計是否合理
- 如果新設計讓控制邏輯更加清晰、行為更易定義且便于擴展,那么這個架構就是成功的;
- 如果發現結構臃腫或處理麻煩,則需要調整設計理念或架構。
驗證機制
- 構建并測試蛇的行為是檢驗整個大腦控制架構好壞的關鍵步驟;
- 成功控制非玩家實體并進行行為測試將作為架構有效性的標志。
我好像錯過了有關蛇的部分,那是什么?
我們在討論“snake”的時候,其實是以它作為一種實驗性實體,用于驗證新的大腦控制架構是否具備良好的擴展性與表現能力。以下是對其核心思路的詳細總結:
為什么選擇“蛇(snake)”?
-
具備多個組成部分:蛇由多個部分組成,例如“頭部”、“多個身體節段”、“尾巴”等,天然適合測試如何用多個實體協同工作,組合為一個整體。
-
非固定結構:不同于英雄角色(head+body 的固定組合),蛇的結構可以是動態或不規則的,長度可變、形態靈活,更貼近真實場景中的復雜對象。
-
行為多樣性:蛇不僅可以實現“跟隨前一個身體節段”的基礎行為,還可以設計為具備全局思考能力的大腦,例如:
- 控制尾巴甩動;
- 做出轉向、攻擊等統一行為;
- 在面對障礙物或目標時,做出整體決策。
“蛇”在大腦系統中的實驗意義
-
驗證控制架構的通用性與擴展性:
- 不再只是英雄這種標準角色;
- 看看是否可以處理具有更復雜邏輯與結構的敵人單位。
-
測試實體聚合的動態控制能力:
- 各身體部分是獨立實體;
- 是否能被大腦統一協調為“一個整體”運行;
- 是否可以實現數據共享與狀態傳遞,例如方向、速度、目標感知等。
-
避免傳統設計的局限性:
- 傳統做法常常是“每個身體部分跟隨前一個部分”;
- 在新架構中嘗試采用“集體智能”的形式,例如蛇的大腦統一計算路徑,然后各部分根據全局指令行動。
蛇的控制模型設想
- 每個部分都是一個獨立實體(Entity);
- 每個實體都擁有“brain_id”字段,用于標識它屬于哪個大腦(例如同一條蛇);
- 蛇的“大腦”是一個控制器,擁有對整個結構的理解與控制;
- 行為是整體驅動的,而非節段間傳遞的。
使用蛇進行驗證的優點
- 實現簡單:蛇的基礎行為邏輯清晰,容易快速搭建測試;
- 反饋明確:通過觀察是否能控制多個節段協調運動、改變方向、攻擊等,可以迅速得出當前架構是否可行;
- 容易擴展:若驗證成功,可以進一步擴展為其他聚合式敵人,例如多頭龍、連體機器人等。
總之,蛇并不僅是一個普通的敵人,而是一個非常理想的試驗對象,它能夠幫助我們以簡單的方式驗證復雜的控制邏輯是否能夠支撐多實體組合、統一思考、集體行為的需求。這個測試將在后續的“brain week”階段推進,成為新架構設計有效性的關鍵判斷標準之一。
其實是個馬陸(千足蟲)
我們在這里進一步澄清了關于“蛇(snake)”的比喻其實更貼近于“蜈蚣(centipede)”或“千足蟲(millipede)”的概念,尤其是像經典游戲《Centipede》中那種生物的行為方式。以下是詳細的中文總結:
比喻更準確是“蜈蚣”而非“蛇”
- 雖然最初用“蛇”作為比喻,但實際設想的行為和結構,更像是蜈蚣或千足蟲那種具有大量身體段、線性排列并具有一定自主行為的生物。
- 特別是參照**游戲《Centipede》**中的敵人設定,它們雖然看似是一個整體,其實每一節也有相對獨立性,這與當前的架構理念非常契合。
為什么更像《Centipede》里的生物?
- 每一節都像一個“實體”,但通過邏輯歸屬到一個統一的大腦控制單元;
- 整體擁有一種分布式又統一的控制邏輯;
- 可以整體響應外部刺激,如轉向、避障、攻擊等;
- 每個身體部分可獨立運行,但最終決策來源于整個“生物體”的統一行為。
游戲結構測試目標
-
創建類似《Centipede》中的長生物,用來測試系統對多實體一腦控制的適應性;
-
驗證系統是否能處理:
- 多個實體之間的同步邏輯;
- 分布式位置與速度計算;
- 連續移動與協同行為;
-
更進一步的話,甚至可以測試實體的動態增減,例如被攻擊后節段減少,或生長時添加新段。
關于項目持續時間的回答
- 目標是盡可能完成基礎架構的完善;
- 尤其是這個“腦—體—控制器”架構,需要經過多個類型實體(如英雄、蜈蚣)測試,來確保其適用于更廣泛的游戲對象;
- 這將持續到至少完成幾個核心測試實體(如蜈蚣、蛇等)之后,并驗證它們能正常運行,架構設計合理;
- 并沒有一個明確的終點時間,而是取決于架構驗證的完整性與實用性是否達到目標。
你希望這個項目持續多久?結束后你會開始做不同的游戲嗎?
我們明確表示,這將是我們在直播中制作的唯一一款游戲,并沒有計劃在此項目完成后再制作其他游戲,原因如下:
項目規模和計劃
- 這個項目預計耗時 600 天;
- 這里的“天”并非指自然日,而是按每次直播約 一小時計算,因此是 600 小時;
- 如果換算成標準全職工作時間(每周 40 小時),等價于 約 15 周(大約 3 到 4 個月);
- 對于一款從零開始、不依賴現成游戲引擎、完全自建架構的游戲來說,600 小時并不算長;
- 如果是在成熟引擎(例如 Unity、Unreal)里制作,只需設置現成的實體標志位,那可以很快;
- 但我們的目標是自底向上開發,包括底層系統、渲染、控制邏輯等,因而時間成本更高。
不打算開發第二款游戲的原因
- 由于投入巨大、復雜度高,僅僅完成這一個項目就已經是非常龐大的任務;
- 整個開發周期跨越多年,并通過持續直播推進,不具備短期快速復用的條件;
- 再開發一款游戲意味著類似的大量投入,而當前這個項目已被視為長期計劃的核心工作;
- 因此我們并未考慮進行下一款游戲,而是專注于將這一款做到盡善盡美。
關于游戲內容量的考量
- 游戲內容可能會超出最初設想的 600 小時;
- 特別是游戲玩法部分還未全部明確,如果內容過于豐富,開發周期可能會延長;
- 但我們將持續評估與推進,視情況動態調整。
總結
我們專注于這一個游戲項目,投入大量精力構建從底層到表現層的所有系統,時間估算為 600 小時(直播小時計),這并不多,考慮到我們沒有使用現成引擎。這款游戲將是我們直播期間唯一開發的游戲,預計涵蓋極其豐富的內容,未來不會再做第二款。
關于每個網格只能有一個實體的檢查,如果你剛離開某個格子,一個怪物正好落在那里,現在這是個二元判定,你打算加入一點模糊處理嗎?比如剛踩離一個格子但怪物還未落地。
我們討論了“每個格子只允許一個實體”的設計限制,以及在高速動作交錯時可能出現的邊界沖突問題。具體整理如下:
格子占用沖突的背景
目前設計中,每個格子只能容納一個實體。這種“格子唯一性”邏輯導致了潛在問題,例如:
- 玩家剛剛離開一個格子,怪物跳入;
- 玩家剛準備進入一個格子,怪物才剛跳出;
- 玩家和怪物幾乎在同一幀內交叉走位。
當前處理思路
-
怪物不會跳到玩家格子
怪物不會“主動”跳到玩家所在的格子上,除非是在進行攻擊。而攻擊動作本身并不意味著怪物要“站”到玩家所在格子上,它仍可以保留當前位置來發起攻擊。 -
關鍵沖突點是“交叉進入”
真正要處理的是雙方幾乎同時進出同一格子的情況。例如怪物剛跳出一個格子,玩家也正打算跳進去。 -
跳躍邏輯為:“中心線判定”
目前的解決方案是假設在移動過程中,角色在完全越過格子的中心線前仍然視為處于原始格子。當某個實體越過中心線時,才算正式進入新格子。因此:- 玩家若想跳入某格,前提是怪物必須已經完全跳出(即中心線后的下一步);
- 若怪物未能“及時清空”目標格子,則玩家無法跳入;
- 反之亦然。
-
動作判定策略可能是“最早清空者優先”
怪物若能盡早騰出格子,就優先做出移動判斷;否則其格子仍視為“占用中”。
游戲性上的取舍
- 承認這是設計層面不可避免的“生硬感”之一;
- 有可能會出現略顯卡頓、不那么絲滑的體驗;
- 但游戲設計本身要求格子唯一占用,這是根本機制,必須優先保障。
總的來說,這是游戲底層邏輯帶來的限制之一。為保障戰斗判定的明確性,將優先保持“一個格子一個實體”的規則,即使可能會犧牲一部分動態流暢感。
有天我想問問你,如何思考程序中數據的表示。我理解結構體、枚舉、數組這些東西,但是什么時候用哪個,有什么規律?
我們談論了在程序設計中如何選擇和使用不同架構的思考方式,具體內容如下:
關于程序設計架構選擇的思考
-
沒有絕對規則指導什么時候用哪種架構
程序設計不像數學公式,有明確步驟和唯一答案。非常簡單的程序可能有標準答案,但復雜程序設計遠沒有固定模式。 -
經驗和反復實踐是關鍵
只有經過大量的實踐、嘗試不同的架構風格,才能逐漸培養出判斷和選擇的能力。通過實踐,可以不斷探索各種方案,了解每種方案的優劣。 -
架構選擇是一個不斷試驗和迭代的過程
當面對不熟悉的問題或想嘗試新的做法時,需要對多種方案進行排列組合和測試,而不是直接給出答案。
通過比較各方案的代碼復雜度、bug數量、可維護性等指標,逐漸總結經驗。 -
程序設計更像是一種藝術或運動
編程設計沒有“一招致勝”的法則,更類似于運動或藝術,需要不斷訓練和反復調整。沒有簡單的規則或公式能夠直接套用。 -
觀察和學習他人的設計過程也非常重要
看別人的設計思路和實踐能幫助提高自己的架構能力,理解不同架構如何影響代碼表現和維護。 -
導師或教練的幫助能加速成長
就像運動員有教練指導動作一樣,找一個有經驗的人點評和指導架構設計過程,指出潛在的錯誤和改進方向,會大大提升學習效率。 -
總結和記錄是提升的輔助工具
記錄各種架構嘗試的成敗原因、好壞之處,有助于內化這些經驗,形成自己的設計直覺和方法論。
總結
- 編程架構選擇沒有現成的規則或模板,更多靠不斷的實踐、試錯和總結。
- 通過不斷嘗試和觀察,培養判斷不同架構適用場景的能力。
- 這是一種長期積累的能力,不斷演進,而不是一次性完成的任務。
- 有導師指導和詳細記錄都能幫助更快提升架構設計的水平。
這種對架構設計的理解和思考方式,能幫助我們面對復雜問題時不迷失方向,持續提升設計質量。
這個系統的目標是不是既支持可以合并變形的實體組(比如偽裝的怪物),又支持這些實體脫離后能單獨行動(比如被死靈法師復活的斷臂)?
我們討論了實體系統如何支持兩種復雜情況:
實體系統的設計目標和能力
-
支持動態變化的實體組合
實體系統要能夠支持一組實體從一種形態變換成另一種,比如一個怪物能夠模仿其他對象,動態變化其組成部分。 -
支持實體的分離與獨立行動
系統還要允許實體在脫離原有組合后,可以單獨行動,比如死靈法師能復活被撕下的手臂,這個手臂作為獨立實體繼續活動。 -
跨實體算法的實現
通常每個實體自身都有“思考”能力,但要能寫出跨多個實體協同工作的算法非常重要,之前的設計難以實現這一點。 -
引入“腦”層(Brain Layer)簡化多實體協作邏輯
新的設計中,通過引入腦層概念,可以編寫跨實體的代碼,使得多實體間的協作邏輯更清晰、簡單,避免復雜交叉混亂。 -
系統的簡潔性和擴展性
現有的MV系統結構簡單,未來可以更方便地添加新的功能,比如給某個實體增加“詛咒”效果或其它特殊狀態,這種擴展將變得容易。 -
解決之前設計限制
之前的實體系統限制了跨多個實體統一思考的能力,難以干凈利落地實現多實體間復雜交互,這是推動新設計的重要原因。
總結
- 實體系統不僅要支持單個實體的行為,也必須支持實體群體動態組成和拆分后的獨立行動。
- 通過引入“腦”層,實現跨實體的統一“思考”和行為控制,提高代碼的整潔度和可維護性。
- 設計追求簡單和可擴展,方便未來快速增加復雜特性。
這使得復雜的游戲實體管理更加靈活且高效。
關于 UML,你在工作中會經常使用嗎?尤其在游戲開發行業?
在游戲行業工作中,實際上從未見過有人畫過UML圖,無論在什么情況下都沒有見過。對UML圖的使用幾乎為零,說明在實際開發過程中,UML并不是常用的工具或方法。
好答案,我明白了,多做就能知道什么有效,靠經驗。謝謝!
我們的理解是,編程架構沒有固定的規則和唯一的答案,必須通過大量實踐去摸索和積累經驗。不斷嘗試不同的方法,觀察效果,總結優缺點,才能逐漸找到適合自己的解決方案。只有不斷動手實踐,反復調整,才能提升能力,找到比較合適的架構設計方式。簡單來說,就是多做多練,通過經驗積累來理解什么方法行得通,什么不行。
你考慮過用 Vulkan 嗎?它對硬件提供了非常底層的訪問。
我們之前研究過Vulkan的設計,但不喜歡它的設計理念,因此幾乎可以確定不會在項目中使用它。除非出現特殊情況必須用到,否則不會選擇Vulkan作為方案。