在UE4 Editor繪製除錯用的元件

簡介

在遊戲中,我們都知道可以使用Blueprint的DrawDebug系列的功能,用來繪製圓球,膠囊體等物件提供除錯。

但是如果想在編輯器的View視窗用DrawDebug是做不到的(註1),至少UE4引擎內部並不是這樣用。


註1.我有試過在ConstructorScript呼叫DrawDebugSphere,結果是會畫出來,但是永遠不會消失了。


不過引擎是有提供編輯器除錯用的顯示功能的,我們可以在幾個地方看到:

1. Visual Logger

2. EQS Testing Pawn

3. NavigationTestingActor


使用Visual Logger可以在編輯器看到AI角色的位置資訊被記錄下來,也可以自己透過API紀錄想要的資訊,如圖所示。

Draw primitives in visual logger
Draw primitives in visual logger


EQS Testing Pawn則是可以隨著編輯過程的狀況不同,顯示/更新Query的結果,如圖所示。

Query result with EQS testing pawn in Editor.


NavigationTestingActor是比較罕見的功能,可以在編輯器就預覽路徑搜尋的結果。

有個時候專案也會需要這方面的功能,如果沒有心理準備就直接看上面三個系統的實作,通常會被複雜的程式碼勸退。

這次我硬著頭皮研究並試著實作了一輪,簡單的做個筆記與分享。

最主要是去除複雜的程式碼,只列出最基本要實作的項目。有需要改進的部分再從上面提到的系統抽取出來即可。


本篇文章的內容以NavigationTestingActor為出發點作分析,雖然比較少見,但似乎是這幾個系統裡面相對簡單的部份。


架構組成


1. AActor
2. URenderComponent
3. FSceneProxy

要完成這個功能,首先要有三個class,一個就是顯示項目要依附在的Actor。
然後要為這個Actor新增自定義的RenderComponent,作為顯示用。
在RenderComponent內會創出自定義的SceneProxy,你要繪製的項目要實作在SceneProxy的函式內。

以下實際用TestActor, TestRenderComponent, TestSceneProxy分別做為案例。


TestActor要實作的項目

在TestActor的member加上

UPROPERTY()
class UTestRenderComponent* TestRenderComp;

如果只想讓這個功能在Editor中出現,可以使用macro

#if WITH_EDITORONLY_DATA
#endif

將所有使用到TestRenderComponent的地方夾起來。

TestActor的Constructor加上

TestRenderComp = CreateDefaultSubobject<UTestRenderComponent>(TEXT("TestRenderComp"));
TestRenderComp->PostPhysicsComponentTick.bCanEverTick = false;

根據需求,Override PreEditChange或PostEditChangeProperty或PostEditMove。
在有資料變動的時候呼叫MarkRenderStateDirty。

TestRenderComp->MarkRenderStateDirty();

PostEditChangeProperty內可以實作property內容變動後該做的事;
PostEditMove則是Actor在編輯器內被拖拉移動的時會進入的事件;


TestRenderComponent要實作的項目


首先TestRenderComponent要繼承UPrimitiveComponent,然後實作下面兩個函式。

virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
virtual FBoxSphereBounds CalcBounds(const FTransform &LocalToWorld) const override;

CreateSceneProxy就是new出SceneProxy,並且把component自己傳入SceneProxy的constructor,說這麼多其實直接看程式碼比較快。

FPrimitiveSceneProxy* UTestRenderComponent::CreateSceneProxy()
{
FTestSceneProxy* newSceneProxy = new FTestSceneProxy(this);
return newSceneProxy;
}

CalcBounds比較簡單,就是決定這個component的bounding box範圍,用來決定這個component會不會進rendering。
可以從GetOwner拿到TestActor,然後直接回傳Actor的BoundingBox即可。

FBoxSphereBounds UTestRenderComponent::CalcBounds(const FTransform &LocalToWorld) const
{
FBox BoundingBox(ForceInit);
AActor* TestActor = GetOwner();
if (TestActor)
{
BoundingBox = TestActor->GetComponentsBoundingBox(true);
}
return FBoxSphereBounds(BoundingBox);
}

TestSceneProxy要實作的項目

TestSceneProxy反而是最複雜的項目,要實作的函式比較多,實際上要繪製的程式碼也寫在這。


GetTypeHash() 
Constructor(const UTestRenderingComponent* inComponent);
Destructor
GetDynamicMeshElements()
GetViewRelevance()
GetAllocatedSize()


其中GetTypeHash,Constructor, Destructor, GetViewRelevance, GetAllocatedSize建議直接參考

Engine/Source/Runtime/NavigationSystem/Private/NavMesh/NavTestRenderingComponent.cpp

就好,GetTypeHash, Destructor, GetViewRelevance我是直接照抄。


Constructor目的是要透過TestRenderingComponent拿到TestActor的資料,存入自定義的資料結構。在GetDynamicMeshElements會使用這些資料結構做繪製。


GetAllocatedSize要回傳TestSceneProxy額外使用到的記憶體大小,請參考NavTestRenderingComponent。


GetDynamicMeshElements是主要繪製的程式碼入口,請參考以下的範例程式碼:


for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
if (!(VisibilityMap & (1 << ViewIndex)))
{
continue;
}
// Write your codes here!!
}


在Engine/Source/Runtime/Engine/Public/SceneManagement.h內提供了許多繪製形狀的函式,所以從Constructor拿到的資料在這裡傳入繪圖函式就可以了。

繪製的函式基本上又可以分為兩大類,一類是以Get為開頭的函式;另一類則是Draw開頭的函式。

Get系列要傳入Material,所以需要產生FMaterialRenderProxy後傳入。

Draw系列需要FPrimitiveDrawInterface (引擎縮寫PDI),可以呼叫Collector.GetPDI(ViewIndex)取得。

以上兩個系列的程式碼在FNavTestSceneProxy::GetDynamicMeshElements內挖的到,請直接參考就好。

下圖 為分別使用DrawWireSphereAutoSides與GetSphereMesh的範例結果。



結論

這次的範例是精簡過後的程式碼結果,如果想要往後鑽研的話,參考GameplayDebuggerCategory裡面的使用方法是最好的。

在實作這些功能之前,最好是先在遊戲中輸入'開啟debug模式預覽結果。


在融會貫通之後,就可以針對專案開發出獨特的工具。

例如在Unreal Fest Europe 2019的演講中提到,該Studio會在編輯器內顯示被選擇的Actor,以連線的方式顯示參照的對象與被參照的對象。如圖 所示。

用來輕易地理解場景物件彼此的相關性, 如果場景物件關係複雜的話,這個功能會特別有用。


參考資料



留言

這個網誌中的熱門文章

UE4 除錯技巧分享 (一)

UE4 GameplayAbilityTask介紹

UE4 Navigation Mesh 心得