[程式] 在UE4開放場景批次計算Navigation Mesh
網頁版
https://yekdniwue.blogspot.com/2020/07/AutoBuildNavMesh.html
Build Static Navigation Mesh in World Composition
簡介
先前有提到因為地圖被細切成很多張的關係,想要使用static nav mesh,
有幾個困難處要解決:
1. 地圖範圍很大的時候,是沒辦法一次載入所有地圖,
只按一次build path就完成的。
不僅會執行很久,也會遇到build path失敗的情況。
失敗會有警告訊息並且有部分nav mesh不完整。
2. 如果想要每張地圖各別計算,要在編輯器內重複的
讀取子地圖
build path
子地圖存檔
卸載子地圖
這樣的流程其實更適合用自動化來做。
名詞與縮寫說明
在開始之前,先介紹一些本篇文章會用到的名詞或是縮寫。
NBV: Navigation Bounds Volume,用來定義navigation mesh的範圍。
P-Level: Persistent Level。在本篇指的是在World Composition模式下的主地圖。
Sublevel: 在本篇指的是在World Composition模式下的各個子地圖,
可能是透過tiled height map匯入進來的。
前置準備
要能夠執行期間讀取/卸載存在子地圖的靜態nav mesh資料,需要以下步驟。
我試過很多方法,下面的步驟缺一不可。
這些步驟都是在開啟P-Level的模式下運作。
1. 放置一個NBV在P-Level中,可以不需要跟任何東西交集。
2. 選擇P-Level內自動產生的RecastNavMesh Actor。
3. Runtime Generation 設為Static。
4. Fixed Tile Pool size設為true。
5. Tile Pool size有可能需要隨著地形大小調大。
6. 在每個子地圖放置需要的NBV。
以上的步驟完成之後,就可以手動對一個子地圖build path再執行,
確認是不是能夠真的動態載入。
如果能夠動態載入/卸載,就代表成功了。
要嚴謹一點的話,最好存檔後重開編輯器再測試,
最嚴謹的話,甚至要package測試。
基本上我在實驗的過程中各種情況都遇到了,例如:
只有編輯器預覽正常
或是編輯器重啟後不正常
或是package出來才不正常...
但是沒有關係,只要照著上面的步驟作,應該是不會有問題的。
注意事項
World Composition模式下的Static Nav Mesh,
似乎只能在主地圖開啟的模式下build path。
如果你直接打開子地圖Build path再存檔的話,
這個子地圖的nav mesh反而不會被即時載入。
因為nav mesh直接被存進子地圖的RecastNavMesh這個actor內了。
但是在world composition模式主要的RecastNavMesh是放在P-Level內。
UE4可能不支援多個RecastNavMesh actor,所以會有無法載入的問題。
這個細節不一定每個團隊人員都會知道,所以會造成開發上的麻煩。
例如可能某個人只單獨開啟子地圖編輯場景,
修改場景內容的同時也按了build path並存檔。
(又或是他的editor設定為自動更新navigation)
這樣一作下去這塊地圖的nav mesh就壞了,如圖所示。
[圖]
所以引入Commandlet自動化的話,比較不會有這樣的疑慮。
NavMesh的部分就一律交給機器更新。
Commandlet Construction
Commandlet可以開啟無使用者介面的UE4 editor,
並執行Commandlet內撰寫的流程。
這次的目標是開啟P-Level,依序載入子地圖,計算路徑後對子地圖存檔。
所以我們需要繼承已經有讀檔/存檔能力的UResavePackagesCommandlet。
不過因為Commandlet是Editor用,直接創在專案內會影響打包流程。
可能會有編譯錯誤的問題,所以製作成Plugin會比較好管理。
創造新的Commandlet流程
1. 在Editor創一個Blank Plugin。(不用是Editor Plugin)
2. 修改.uplugin檔Modules內的參數
"Type": "Editor"以及"LoadingPhase": "Default"
3. 修改Build.cs檔PrivateDependencyModuleNames內的參數
新增"UnrealEd"來開啟相關的功能
4. 在Plugin內新增C++ class 並繼承UResavePackagesCommandlet
5. Override PerformAdditionalOperations,需要實作的行為寫在這裡
[圖]
Modify .uplugin.
[圖]
Add UnrealEd into build.cs in plugin.
ResavePackagesCommandlet介紹
在這次的環境,我們需要ResavePackagesCommandlet內建的幾個重要功能,
包含
1. InitializeResaveParameters
2. LoadAndSaveOnePackage
3. PerformAdditionalOperations
4. CheckoutFile
InitializeResaveParameters
我們需要InitializeResaveParameters來解析輸入參數,主要是要擷取Map參數,
讓後續的LoadAndSaveOnePackage使用。
LoadAndSaveOnePackage
LoadAndSaveOnePackage算是ResavePackagesCommandlet的主函式。
主要內容就是讀檔,作事情(存檔,算Lighting資訊等等)
與上傳(負責與source control溝通)。
PerformAdditionalOperations
因為LoadAndSaveOnePackage實作了太多事情,
我找到PerformAdditionalOperations有提供virtual可以實作,
除了會把讀地圖檔建立好的World傳進來。
函式本身的程式碼也非常具有參考價值,尤其是Setup the World的部分,
充分說明了在Commandlet裡面要如何讀一個地圖檔並且建立出正確的World,
如同Editor中一樣。
CheckoutFile
因為我們是使用Perforce作版本控管,所以還多需要CheckoutFile的功能。
Commandlet Implementation
這邊還可以分為幾個部分:
Build Navigation必要的程式碼
正確的讀取子地圖必要的程式碼
如果是要使用Commandlet計算靜態場景(非World Composition)的路徑,
就不用了解後者
。
只需要參考Build Navigation必要的程式碼就好。
Build Navigation必要的程式碼
需要Build Navigation總共需要呼叫兩個函式:
FNavigationSystem::AddNavigationSystemToWorld
NavigationSystem::Build
呼叫AddNavigationSystemToWorld的原因,是因為我們需要建立 MainNavData。
call stack 大概是
AddNavigationSystemToWorld
InitializeForworld
ProcessRegisterationCandidates
如果沒有呼叫這行,後續要執行NavigationSystem::Build的時候,
會因為無資料而直接結束Build流程。
NavigationSystem::Build
呼叫Navigation build nav mesh的最主要函式,所有的資料
都要準備齊全才會有正確的結果。
我有追查到在編輯器點選Build Path其實是會呼叫
FEditorBuildUtils::EditorBuild
不過在Commandlet使用會因為GUnrealEd是null而當機,所以不能直接呼叫。
正確的讀取子地圖必要的程式碼
Load Sublevel
Initialize Sublevel
Save Sublevel
Unload Sublevel
CollectGarbage
Load Sublevel
首先我們有的是P-Level的UWorld*,從
World->WorldComposition->TilesStreaming
可以拿到子地圖的資訊。
利用GetWorldAssetPackageName可以查到子地圖的完整路徑,
再透過LoadPackage讀入記憶體。
Initialize Sublevel
此時P-Level跟讀入的子地圖還是沒有關連的
所以要透過AddStreamingLevel建立P-Level跟子地圖的關係。
然後子地圖要呼叫一連串的函式,設定狀態,再透過FlushLevelStreaming更新。
但是只有這樣還不夠,我發現子地圖其實是以原點為中心儲存的,
Engine會讀取Tile資訊的Absolute position,
然後呼叫ApplyWorldOffset將地圖內所有actor的座標更新。
Navigation mesh才會計算到對的位置。
Tile的Absolute position的抓取可看下面的程式碼
TArray<FWorldCompositionTile>& tileList =
worldComposition->GetTilesList();
TArray<ULevelStreaming*> tilesStreaming =
worldComposition->TilesStreaming;
for (int32 index = 0; index < tilesStreaming.Num(); ++index)
{
auto perLevelStreaming = tilesStreaming[index];
auto tileInfo = tileList[index].Info;
FIntVector levelOffset = tileInfo.AbsolutePosition;
}
在設置的最後一個步驟記得要呼叫EditorLevelUtils::SetLevelVisibility,
宣告Sublevel的顯示狀態visible。
這樣navigation build的時候才抓的到SubLevel的NBV。
詳細的程式碼可看圖。
[圖]
Initialize sublevel.
Save Sublevel
在儲存Sublevel之前,要先還原剛剛為了正確計算nav mesh的位移操作。
所以要呼叫一次ApplyWorldOffset,但是這次要乘上-1。
儲存相關的程式碼請參考圖。
[圖]
Save sublevel.
Unload Sublevel
Unload 相對簡單,子地圖設MarkPendingKill,P-Level移除StreamingLevel與
RemoveFromWorld就可以了。如圖所示。
[圖]
Unload sublevel.
CollectGarbage
為了避免持續讀取地圖造成記憶體不足,每處理完一個子地圖就呼叫
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS)
可以釋放記憶體。
整合以上重點項目後的最後流程
1. AddNavigationSystemToWorld
2. For each Sublevel
Load Sublevel
Initialize Sublevel
NavigationSystem::Build
Save Sublevel
Unload Sublevel
CollectGarbage
結論與未來工作
結論
藉由這次的項目,我學到很多新的觀念,包含了解navigation mesh,
commandlet,package操作,world composition等系統。
navigation mesh相關的部份,我得知了navigation的build到底是如何運作的;
需要哪些資訊才能正確build path;
程式碼在哪邊;nav mesh的儲存規則;子地圖的offset機制。
commandlet則是學到如何在commandlet模式讀取一個地圖檔;
根據地圖檔產生UWorld;了解ResavePackage執行的項目;
為了能存檔讀檔,UPackage、ULevel、ULevelStreaming等資料型態也概略的看過,稍微
分辨得出差異。
World Composition系統雖然很大,從子地圖資料的抽取;
子地圖載入/卸載;如何獲得Tile資訊,也都包含在這次的研究範圍。
未來工作
以目前的版本來說,已經達到我原先想作的目標了,
不過我依然有注意到一些項目是可以再進一步改進的。
首先就是這個Commandlet儲存出來的地圖,
在World Composition預覽會變成預設圖。
而原來在編輯器操作並儲存的版本預覽圖會是正確的。
日後如果真的要使用這套流程的話應該要修正。
commandlet如果遇到checkout失敗要怎麼辦,也是可以再延伸的課題。
另外一個重要的項目就是,大型場景不會那麼乾淨,只有一張地形檔,
一次只需要計算一個Sublevel。
實際上可能會再細分企劃場景(內含有碰撞會影響nav mesh的Actor),
建築物,零碎物件等等。
但是在計算nav mesh的時候要將這些地圖一起納入再算,才會是正確的結果。
所以除了開發之前要制定良好地圖資料夾規範以外,
commandlet也要隨著這個規範稍作修改。
要能支援多階層的地圖結構,並且某一個階層下的地圖會把所有subLevel讀取進來,
統一build。
舉例來說,地圖結構可能如圖所描述:
[圖]
這時候navigation mesh應該是存於X0_Y0以及X0_Y1內,計算兩次就好。
最後,其實這次對World Composition的了解還不夠深入;
包含調整地圖讀取的優先順序;
如何確保地形載入後再spawn玩家;Sublevel LOD的產生與影響。
都是還沒了解的部份,有實際需求的話是要優先研究的。
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 59.120.146.90 (臺灣)
※ 文章網址: https://www.ptt.cc/bbs/GameDesign/M.1595387022.A.63E.html
推
07/22 11:38,
4年前
, 1F
07/22 11:38, 1F
推
07/22 21:55,
4年前
, 2F
07/22 21:55, 2F
推
07/23 21:53,
4年前
, 3F
07/23 21:53, 3F
GameDesign 近期熱門文章
PTT遊戲區 即時熱門文章