目錄標題
- 1.在 2D 平臺跳躍游戲項目中,你使用了對象池來生成和回收怪物包含陣亡的動畫預制件。在對象池回收對象時,如何確保動畫狀態被正確重置,避免下次使用時出現異常?
- 2.在僵尸吃腦子模擬項目中,你創建了繼承于IAspect的aspect,并通過RefRW和RefRO定義訪問的屬性。能詳細說說在實際使用中,RefRW和RefRO有什么區別嗎?在什么場景下分別使用它們?
- 3.在 2D 平臺跳躍游戲項目中,你使用對象池來生成和回收怪物預制件。在對象池的設計與實現過程中,如何確定對象池的初始容量和最大容量呢?
- 4.在 3D 回合制策略游戲中,你使用了狀態機實現敵人的 AI,并且 AI 的行為帶權。在不同情況下,不同動作的權值是如何確定的呢?
- 5.在僵尸吃腦子模擬項目里運用了 ECS 架構,你也清楚 ECS 架構適合并行計算。那在實際開發中,是怎樣去利用 ECS 架構實現并行計算,進而提升游戲性能的呢?
- 6.在 2D 平臺跳躍游戲項目中,你使用了 TileMap 瓦礫地圖系統構建地圖。在使用過程中,如何實現地圖的動態加載與卸載,以優化游戲內存占用?
- 7.在 3D 回合制策略游戲中,你使用了狀態機來管理角色的行為狀態。當角色的狀態比較復雜,存在多種狀態之間的相互轉換,并且有一些狀態還存在子狀態時,你是如何設計狀態機以確保邏輯清晰,易于維護和擴展的呢?
- 8.在僵尸吃腦子模擬項目里,你運用了 ECS 架構。當游戲場景中僵尸數量極多,比如上萬只時,可能會面臨性能瓶頸。你打算采取哪些策略來優化 ECS 架構下大規模僵尸的性能表現呢?
- 9.在你參與的 “3D 回合制策略游戲” 項目里,你使用了狀態機實現敵人的 AI 且行為帶權。在游戲運行過程中,如果玩家做出了一個游戲設計師未曾預料到的操作,導致敵人 AI 的權值計算出現異常,你會如何排查和解決這個問題?
- 10.在 “僵尸吃腦子模擬” 項目里,你使用了 ECS 架構并創建了多個繼承于ISystem的系統。當多個系統之間存在數據依賴關系時,你是如何管理和協調這些依賴關系,以確保系統執行順序正確且數據一致性的?
豆包 中等偏上—優秀 評價
1.在 2D 平臺跳躍游戲項目中,你使用了對象池來生成和回收怪物包含陣亡的動畫預制件。在對象池回收對象時,如何確保動畫狀態被正確重置,避免下次使用時出現異常?
答:我在泛型對象池定義之初就設置了一個私有的抽象Reset函數,對于后續繼承它的對象池必須要實現它,然后在對外公開的回收預制體函數ReturnToPool中調用這個Reset函數,這樣來保證他們狀態復原,避免異常。比如我在Reset中重新設定這個預制體的動畫機中所有的轉換條件參數復原,Transform屬性復原,并且在Reset函數里使用了Try和catch來捕獲重置失敗的預制體。
2.在僵尸吃腦子模擬項目中,你創建了繼承于IAspect的aspect,并通過RefRW和RefRO定義訪問的屬性。能詳細說說在實際使用中,RefRW和RefRO有什么區別嗎?在什么場景下分別使用它們?
答:Ref RW對應的就是ref關鍵字, read and write,RO 對應的就是 read only,意思就是前者是一個可讀寫的屬性,后者是一個只可讀的屬性,這要根據需求來用,比如我項目里寫了僵尸的行走速度,我不希望他改變所以我把aspect里面對僵尸速度屬性的引用設置為只讀的意味著我不希望改變他,比如項目中墓碑的Transform屬性,我希望每個墓碑生成的位置角度是不一樣的所以我設置他為可讀寫的。
3.在 2D 平臺跳躍游戲項目中,你使用對象池來生成和回收怪物預制件。在對象池的設計與實現過程中,如何確定對象池的初始容量和最大容量呢?
答:初步預估對象池容量
統計游戲數據:在設計階段,仔細分析游戲場景和玩法,統計出游戲中不同類型對象(如怪物、子彈、道具等)同時出現的最大數量和常見數量。例如在一個射擊游戲中,統計一場戰斗里最多會同時存在多少顆子彈,一般情況下又會有多少顆子彈。
考慮擴展系數:為了應對游戲中可能出現的特殊情況,如玩家觸發特殊技能、進入特殊關卡等導致對象數量突然增加,需要在最大數量的基礎上乘以一個擴展系數(通常為 1.2 - 1.5),以此確定對象池的最大容量。初始容量可以設置為常見數量的均值。
游戲測試階段的性能監測
選擇合適的監測工具:在 Unity 中,可以使用內置的 Profiler 工具來監測對象池的使用情況,它能提供詳細的內存使用信息、對象創建和銷毀的頻率等數據。
關注關鍵指標:重點關注對象池的擴容次數、閑置對象數量、對象創建和銷毀的時間等指標。如果對象池頻繁擴容,說明初始容量設置過小;如果大部分時間對象池都有大量閑置對象,說明最大容量設置過大。
根據監測數據進行動態調整
增大初始容量:如果發現對象池頻繁擴容,導致游戲性能下降,可以適當增大初始容量。例如,將初始容量增加 20% - 30%,然后再次進行測試,觀察性能是否有所改善。
減小最大容量:如果對象池大部分時間都有大量閑置對象,占用了過多的內存,可以考慮減小最大容量。每次減小的幅度不宜過大,建議在 10% - 20% 之間,然后重新測試,確保不會影響游戲的正常運行。
4.在 3D 回合制策略游戲中,你使用了狀態機實現敵人的 AI,并且 AI 的行為帶權。在不同情況下,不同動作的權值是如何確定的呢?
答:
我的項目中敵人和友軍都是繼承自player這個父類的,所以他們能使用的動作是一樣的,對于敵人他有移動,射擊,手雷。3個動作,我把移動的權值設置成10 - 可攻擊范圍內敵人的數量 * 3,這樣如果范圍內有敵人那么他們就不更容易去移動,對于射擊權值就是 8,手雷權值是8 + (可攻擊范圍內敵人的數量 - 1) * 2,于是在有敵人且只有一個的情況下會使用射擊,有敵人且有多個的情況下會使用手雷,沒有敵人的情況下會使用移動,同時攻擊還跟敵人剩余的血量有關,對于移動還要計算每個可到達的地點的權值不同于之前的權值系統,它會優先選擇更多可接觸敵人的地點到達,這些其實都只和設計者的需求來設定。
5.在僵尸吃腦子模擬項目里運用了 ECS 架構,你也清楚 ECS 架構適合并行計算。那在實際開發中,是怎樣去利用 ECS 架構實現并行計算,進而提升游戲性能的呢?
答:
首先建立一個Job : IEntityJob,把這個job需要用到的所有東西比如EntityCommandBuffer.ParallelWriter如果不是并發的那么不用使用ParallelWriter(ecb用于更改實體組件,生成實體等)可以暫存某些指令等到某個時間點一起執行以免出現并發的線程爭奪和數據錯讀問題,還有一些數據,或者實體。然后實現這個Job的Execute方法,如有需要還要提供sortKey這個參數,在調用這個作業的時候會自動檢索含有Execute帶有的組件的實體,并且在運行的時候給他發配一個sortKey。最后只需要在調用這個任務的system中new這個job,然后給他提供所需要的參數比如使用SystemAPI.Getsingleton,GetComponent等等,用來new這個job并且調用它,需要注意的是在EntityCommandBufferSystem創建EntityCommandBuffer的時候要AsParallelWriter否則會報錯,這里state作用是在非托管世界申請一個命令緩沖區并且視為并發寫者來緩存并執行并發的命令,因為要并發執行,最后在new的尾處.ScheduleParallel()進行并發執行任務。
避免在一個任務中修改另一個任務需要讀取的數據
6.在 2D 平臺跳躍游戲項目中,你使用了 TileMap 瓦礫地圖系統構建地圖。在使用過程中,如何實現地圖的動態加載與卸載,以優化游戲內存占用?
答:
地圖分塊處理
劃分地圖區域:把整個游戲地圖按照一定規則劃分成多個小的地圖塊,比如以正方形或矩形區域為單位。每個地圖塊都可以獨立加載和卸載。例如,在一個大型的 2D 平臺跳躍游戲世界中,可以將其劃分為多個 10×10 個 Tile 的地圖塊。
確定加載范圍:依據玩家當前所在的位置和視野范圍,確定需要加載的地圖塊。一般來說,除了玩家當前所在的地圖塊,還需要加載其周圍的一些地圖塊,以保證玩家移動時地圖的連貫性。比如,玩家在一個地圖塊中,那么可以同時加載其上下左右以及四個對角方向相鄰的地圖塊。
實現動態加載與卸載機制
加載邏輯:當玩家接近某個未加載的地圖塊時,觸發加載操作。可以使用 Unity 的場景加載 API(如 SceneManager.LoadSceneAsync )來異步加載地圖塊。異步加載不會阻塞主線程,能保證游戲的流暢性。在加載時,還可以顯示一個加載提示,提升用戶體驗。
卸載邏輯:當某個地圖塊遠離玩家的視野范圍,并且在一段時間內不會被玩家訪問到時,觸發卸載操作。使用 SceneManager.UnloadSceneAsync 來異步卸載地圖塊,釋放內存。
緩存機制
臨時緩存:為了避免頻繁的加載和卸載操作,可以設置一個緩存區,將最近使用過的地圖塊暫時保留在內存中。當玩家再次需要訪問這些地圖塊時,可以直接從緩存中獲取,而不需要重新加載。
緩存淘汰策略:由于緩存區的容量是有限的,當緩存區達到最大容量時,就需要移除一些數據來為新的數據騰出空間。常見的淘汰策略有先進先出(FIFO)、最近最少使用(LRU)等。
7.在 3D 回合制策略游戲中,你使用了狀態機來管理角色的行為狀態。當角色的狀態比較復雜,存在多種狀態之間的相互轉換,并且有一些狀態還存在子狀態時,你是如何設計狀態機以確保邏輯清晰,易于維護和擴展的呢?
答:我使用狀態機系統來維護我的狀態,狀態機內部只有 state currentstate,兩個公開的方法public void changeState(State stateName)和public void Initial,然后每個state都繼承自Istate必須實現Istate里的抽象方法,Enter,Exit,Update,然后在FSM類里創建一個枚舉類State用于枚舉每個狀態的名字,和一個字典關聯 State state 和 Istate theState,然后FSM里包含對狀態機的聲明,并且有AddState方法用于往字典里添加狀態,在聲明fsm變量的腳本中的Onupdate調用currenState.Update,這樣就可以確保狀態之間的模塊化和解耦而且擴展方便,維護方便。子狀態也同樣可以這樣使用和添加,確保同一個時間只有一個狀態在執行。
8.在僵尸吃腦子模擬項目里,你運用了 ECS 架構。當游戲場景中僵尸數量極多,比如上萬只時,可能會面臨性能瓶頸。你打算采取哪些策略來優化 ECS 架構下大規模僵尸的性能表現呢?
答:
空間分區與裁剪:
將游戲場景劃分為多個空間區域,比如使用**四叉樹(2D 場景)或八叉樹(3D 場景)**結構。為每個僵尸分配所屬的區域,當進行渲染、碰撞檢測或 AI 計算時,只處理玩家所在區域及相鄰區域內的僵尸。例如,玩家在一個特定房間內,只計算該房間及其相鄰房間內的僵尸,避免對整個場景中所有僵尸進行不必要的操作。
對于視野范圍外的僵尸,不進行渲染和復雜的 AI 計算,只保留基本的位置追蹤等輕量級信息,當僵尸進入玩家視野范圍時再進行完整的處理。
批處理與合并:
在渲染方面,將多個僵尸的渲染數據合并成一個批次進行繪制。利用 GPU 的批處理功能,減少 Draw Call 的數量。比如,可以將相同類型、相同材質的僵尸的頂點數據、索引數據等合并,一次性提交給 GPU 進行渲染,提高渲染效率。
對于僵尸的物理碰撞檢測,可以將多個僵尸分組,對組內的僵尸進行統一的碰撞檢測處理,而不是逐個僵尸進行檢測,減少碰撞檢測的計算量。
優化組件與數據結構:
確保僵尸的組件設計簡潔,只包含必要的數據和功能。避免在組件中存儲過多冗余或不常用的數據。例如,如果僵尸的某個屬性只在特定情況下使用,可以考慮在需要時動態獲取,而不是一直存儲在組件中。
選擇合適的數據結構來存儲僵尸相關的數據。例如,使用數組而不是鏈表來存儲僵尸列表,因為數組在隨機訪問和遍歷方面通常具有更好的性能。同時,可以對數據進行預排序,以便在進行某些計算(如距離排序)時提高效率。
并行計算與多線程優化:
充分利用 ECS 架構的并行計算優勢,將僵尸的 AI 計算、物理模擬等任務拆分成多個并行任務,利用多核 CPU 進行加速。例如,為每個僵尸的 AI 行為計算創建一個 Job,通過 ScheduleParallel() 方法并行執行這些 Job。
注意避免多線程之間的資源競爭和數據沖突。合理使用鎖機制或無鎖數據結構來保護共享資源。例如,對于僵尸的生命值等共享數據的修改,使用線程安全的方式進行操作。
LOD(Level of Detail)技術:
為僵尸設置不同的細節層次模型。當僵尸距離玩家較遠時,使用低細節模型,減少模型的頂點數量和紋理復雜度,降低渲染成本。當僵尸靠近玩家時,切換到高細節模型,提供更好的視覺效果。
同樣,對于僵尸的 AI 行為也可以設置不同的細節層次。例如,遠處的僵尸可以采用簡單的巡邏行為,而近處的僵尸則執行更復雜的追逐和攻擊行為。
9.在你參與的 “3D 回合制策略游戲” 項目里,你使用了狀態機實現敵人的 AI 且行為帶權。在游戲運行過程中,如果玩家做出了一個游戲設計師未曾預料到的操作,導致敵人 AI 的權值計算出現異常,你會如何排查和解決這個問題?
答:
我應該會在敵人AI采取行動的使用使用Try和Catch去捕捉錯誤,如果沒有捕捉到說明沒有語法上或者訪問越界等的問題而是邏輯上的問題,那么如果是后者只能去跟隨敵人狀態機進行一步一步的調試,如果它引起了空間或者性能問題那么可以使用性能檢測工具profile來檢測哪個函數或者哪個部分出現了內存性能異常。
10.在 “僵尸吃腦子模擬” 項目里,你使用了 ECS 架構并創建了多個繼承于ISystem的系統。當多個系統之間存在數據依賴關系時,你是如何管理和協調這些依賴關系,以確保系統執行順序正確且數據一致性的?
答:
在ECS架構中不像Unity自帶一個腳本執行順序的調整功能。在ECS架構中你運行項目的時候如果先后順序有問題那么大概率會報錯并且無法運行,屆時你可以到Windows->Entities->System中查看每個系統的執行時間,但是這里不像unity原生開發框架可以直接拖動,你必須到每個系統中使用UpdateInGroup(typeof(InitializationSystemGroup))此處可以替換為很多ECS內定好的系統組或者UpdateAfter,Before某個System來調整它們的執行順序。通過這個動作來保證依賴的關系不會出現問題,比如我項目中僵尸生成系統就定義了[UpdateAftertypedef(TombStoneSpawnSystem)]因為我僵尸生成的位置依賴于墓碑生成的位置。
避免循環引用導致相互依賴,可以設置中間層來接收相互依賴之間的數據避免直接依賴來解耦,或者調整循環依賴之間的關系,讓其中一個不再引用環中的數據。