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
//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(); //UE封装了很多绘制函数,可以去看下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);//把自己传进去,然后把FPrimitiveSceneProxy信息返回 } 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构造函数中编写逻辑自动挂载组件:
#include "MyTestActor.h" AMyTestActor::AMyTestActor()//构造函数这里进行挂载 { PrimaryActorTick.bCanEverTick = true; UMyPrimitiveComponent* Gizmo = NewObject<UMyPrimitiveComponent>(this, "Gizmo"); AddInstanceComponent(Gizmo); Gizmo->RegisterComponent(); }