Unity的Gizmos可以很方便的在編輯器下進行調試,Unreal中也有一些辦法可以達到效果。
本文主要參考:https://zhuanlan.zhihu.com/p/363625037,進行了一些簡化。並在Unreal 4.27中實現。
具體流程如下:
- 需要繪制Gizmo的Actor掛載繼承UPrimitiveComponent的組件;該組件重寫了CreateSceneProxy方法,這個方法里可以拿到PDI繪制
- 然后進行這個組件的編寫(繼承UPrimitiveComponent實際上也繼承了USceneComponent),手動掛載到Actor上就可以繪制Gizmo了
- 再在Actor的構造函數中編寫自動掛載該組件的邏輯,方便使用
先來編寫繪制Gizmo的組件,這里命名為UMyPrimitiveComponent:
MyPrimitiveComponent.h (MYPROJECT_API注意替換)
//MyPrimitiveComponent.h #pragma once #include "CoreMinimal.h" #include "Components/PrimitiveComponent.h" #include "PrimitiveSceneProxy.h" #include "MyPrimitiveComponent.generated.h" UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) class MYPROJECT_API UMyPrimitiveComponent : public UPrimitiveComponent { GENERATED_BODY() public: //繪制邏輯主要在這里 virtual FPrimitiveSceneProxy* CreateSceneProxy() override; //如果要在非選中情況下始終繪制的話,需要有Bounds信息,所以要重寫該函數 virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const; };
MyPrimitiveComponent.cpp
//MyPrimitiveComponent.cpp #include "MyPrimitiveComponent.h" FPrimitiveSceneProxy* UMyPrimitiveComponent::CreateSceneProxy() { class FMySceneProxy : public FPrimitiveSceneProxy { public: SIZE_T GetTypeHash() const override { static size_t UniquePointer; return reinterpret_cast<size_t>(&UniquePointer); } FMySceneProxy(const UPrimitiveComponent* InComponent) : FPrimitiveSceneProxy(InComponent) { CacheInComponent = InComponent; bWillEverBeLit = false; } virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override { QUICK_SCOPE_CYCLE_COUNTER(STAT_Draw3DAgentSceneProxy_GetDynamicMeshElements); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { if (VisibilityMap & (1 << ViewIndex)) { const FSceneView* View = Views[ViewIndex]; FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); //拿到Actor的矩陣繪制,而不是Component自己的 const FMatrix& LocalToWorld = CacheInComponent->GetTypedOuter<AActor>()->GetTransform().ToMatrixWithScale(); //繪制函數可以去看下PrimitiveDrawingUtils.cpp DrawOrientedWireBox(PDI , LocalToWorld.TransformPosition(FVector::ZeroVector) , LocalToWorld.GetScaledAxis(EAxis::X) , LocalToWorld.GetScaledAxis(EAxis::Y) , LocalToWorld.GetScaledAxis(EAxis::Z) , FVector(100.0, 100.0, 100.0) , FLinearColor(1.0, 0.0, 0.0, 1.0) , SDPG_World , 1.0); } } } virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override { /*const bool bVisibleForSelection = IsSelected(); const bool bShowForCollision = View->Family->EngineShowFlags.Collision && IsCollisionEnabled(); FPrimitiveViewRelevance Result; Result.bDrawRelevance = (IsShown(View) && bVisibleForSelection) || bShowForCollision; Result.bDynamicRelevance = true; Result.bShadowRelevance = IsShadowCast(View); Result.bEditorPrimitiveRelevance = true; Result.bEditorNoDepthTestPrimitiveRelevance = true; */ //上面這段表示選中繪制Gizmo FPrimitiveViewRelevance Result; Result.bDrawRelevance = true; Result.bDynamicRelevance = true; Result.bShadowRelevance =false; Result.bEditorPrimitiveRelevance = true; Result.bEditorNoDepthTestPrimitiveRelevance = true; //這段表示始終繪制 return Result; } virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } private: const UPrimitiveComponent* CacheInComponent; }; return new FMySceneProxy(this); } FBoxSphereBounds UMyPrimitiveComponent::CalcBounds(const FTransform& LocalToWorld) const { return FBoxSphereBounds(FBox(FVector(-50, -50, -50), FVector(50, 50, 50))).TransformBy(LocalToWorld); //因為縮放是1,這里填的就是實際尺寸,也可以遍歷所有組件取最大Bounds. }
然后手動再掛載一下組件,就有效果了:
如果需要像Unity那樣;直接就有Gizmo,可以在Actor構造函數中編寫邏輯自動掛載組件:
AMyTestActor::AMyTestActor() { UBBoxActorGizmoComponent* Gizmo = CreateDefaultSubobject<UBBoxActorGizmoComponent>(TEXT("Gizmo")); if (Gizmo) { Gizmo->SetupAttachment(GetRootComponent()); } }