Written for Unreal Engine 5.1 C++

Introduction

Working with actor components that don’t have a physical representation may be challenging. Recently, I learned about Component Visualizers which makes it possible to draw anything in the Unreal Editor for each component when selected.

In my sandbox construction game, each building piece has a set of polar connection points that may be either positive or negative. Two points with the same polarity cannot be attached to each other. That is, a positive point may only be attached to a negative point and vice-versa. Each connection point is represented by a custom Scene Component that has a polarity property. There are three relevant properties here: Location, Rotation, and Polarity. It would be helpful if I can visualize all three properties as a colored arrow, but only in the Unreal Editor when I’m designing a building piece.

Fortunately, Unreal Engine makes it easy to do this with FComponentVisualizer. Component visualizers are drawn when a component is selected.

Getting Started

Component visualizers must be in an editor-only module that’s loaded after the engine has been initialized. Create one if you do not have one yet.

// File: MyGame.uproject

"Modules": [
  {
    "Name": "MyGame",
    "Type": "Runtime",
    "LoadingPhase": "Default"
  },
  {
    "Name": "MyGameEditor",
    "Type": "Editor",
    "LoadingPhase": "PostEngineInit"
  }
]

Add UnrealEd and ComponentVisualizers to the editor module’s dependencies.

// File: MyGameEditor.Build.cs

PublicDependencyModuleNames.AddRange(
    new string[]
    {
        "Core",
        "MyGame"
    }
);

PrivateDependencyModuleNames.AddRange(
    new string[]
    {
        "CoreUObject",
        "Engine",
        "UnrealEd",
        "ComponentVisualizers"
    }
);

Create the Component Visualizer

In the editor module, create a class derived from FComponentVisualizer. Override either DrawVisualization or DrawVisualizationHUD depending on whether the visualization renders inside the scene or on the editor’s viewport.

// File: MyComponentVisualizer.h

#pragma once

#include "ComponentVisualizer.h"

class FMyComponentVisualizer : public FComponentVisualizer
{
public:
    // Override this to draw in the scene
    virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View,
        FPrimitiveDrawInterface* PDI) override;
	
    // Override this to draw on the editor's viewport
    virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport,
        const FSceneView* View, FCanvas* Canvas) override;
};
// File: MyComponentVisualizer.cpp

#include "MyComponentVisualizer.h"
#include "MyComponent.h"

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View,
    FPrimitiveDrawInterface* PDI)
{
    // Draw a visualization here using PDI (or Canvas if using DrawVisualizationHUD)
}

FPrimitiveDrawInterface provides basic drawing functions such as DrawLine and DrawMesh. Utility functions for drawing boxes, sphere, torus, and other advanced shapes are also available. Check out Primitive Drawing Functions for a comprehensive list and examples.

⚠️ The component’s absolute location and rotation should be used in the drawing functions. If the relative location is used, then the visualization will be rendered incorrectly when the component’s owning actor is selected in the level editor.

Register the Component Visualizer

The component visualizer is registered in StartupModule and unregistered in ShutdownModule. Call RegisterComponentVisualizer with the name of the component that should be visualized.

// File: MyGameEditor.cpp

#include "MyGameEditor.h"

#include "MyComponent.h"
#include "MyComponentVisualizer.h"
#include "UnrealEdGlobals.h"
#include "Editor/UnrealEdEngine.h"

#define LOCTEXT_NAMESPACE "FMyGameEditorModule"

void FMyGameEditorModule::StartupModule()
{
    if (GUnrealEd)
    {
        TSharedPtr<FMyComponentVisualizer> Visualizer = MakeShareable(new FMyComponentVisualizer());
        GUnrealEd->RegisterComponentVisualizer(UMyComponent::StaticClass()->GetFName(), Visualizer);
        Visualizer->OnRegister();
    }
}

void FMyGameEditorModule::ShutdownModule()
{
    if (GUnrealEd)
    {
        GUnrealEd->UnregisterComponentVisualizer(UMyComponent::StaticClass()->GetFName());
    }
}

#undef LOCTEXT_NAMESPACE
    
IMPLEMENT_MODULE(FMyGameEditorModule, MyGameEditor)

That’s it! :)

Primitive Drawing Functions

Examples

DrawPoint

A screenshot of a yellow point being drawn at the component's location.

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View,
    FPrimitiveDrawInterface* PDI)
{
    const UMyComponent* MyComponent = Cast<UMyComponent>(Component);
    if (!MyComponent)
    {
        return;
    }

    double Thickness = 15;
    PDI->DrawPoint(MyComponent->GetComponentLocation(), FLinearColor::Yellow, Thickness, SDPG_World);
}

DrawLine

A screenshot of a yellow line being drawn on the X axis.

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View,
    FPrimitiveDrawInterface* PDI)
{
    const UMyComponent* MyComponent = Cast<UMyComponent>(Component);
    if (!MyComponent)
    {
        return;
    }

    double Length = 100;
    double Thickness = 1;
	
    FVector Start = MyComponent->GetComponentLocation();
    FVector End = Start + FRotationMatrix(MyComponent->GetComponentRotation()).GetScaledAxis(EAxis::X) * Length;

    PDI->DrawLine(Start, End, FLinearColor::Yellow, SDPG_World, Thickness);
}

DrawTranslucentLine

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View,
    FPrimitiveDrawInterface* PDI)
{
    const UMyComponent* MyComponent = Cast<UMyComponent>(Component);
    if (!MyComponent)
    {
        return;
    }

    double Length = 100;
    double Thickness = 1;
	
    FVector Start = MyComponent->GetComponentLocation();
    FVector End = Start + FRotationMatrix(MyComponent->GetComponentRotation()).GetScaledAxis(EAxis::X) * Length;

    FLinearColor Color(1.0, 1.0, 0.0, 0.5); // RGBA in floating-point format (between 0 and 1)
    PDI->DrawTranslucentLine(Start, End, Color, SDPG_World, Thickness);
}

DrawFlatArrow

A screenshot of a yellow flat arrow drawn along the X axis.

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View,
    FPrimitiveDrawInterface* PDI)
{
    const UMyComponent* MyComponent = Cast<UMyComponent>(Component);
    if (!MyComponent)
    {
        return;
    }

    FVector ComponentLocation = MyComponent->GetComponentLocation();
    FRotationMatrix ComponentRotation = FRotationMatrix(MyComponent->GetComponentRotation());
    FColor Color = FColor::Yellow;
    float Length = 100.f;
    float Width = 20.f;
    float Thickness = 1.f;

    DrawFlatArrow(PDI, ComponentLocation,
        ComponentRotation.GetScaledAxis(EAxis::X),
        ComponentRotation.GetScaledAxis(EAxis::Y),
        Color,
        Length,
        Width,
        GEngine->GeomMaterial->GetRenderProxy(),
        SDPG_World,
        Thickness);
}

DrawDirectionalArrow

A screenshot of a yellow 3D arrow drawn along the X axis.

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View,
    FPrimitiveDrawInterface* PDI)
{
    const UMyComponent* MyComponent = Cast<UMyComponent>(Component);
    if (!MyComponent)
    {
        return;
    }

    FLinearColor Color = FLinearColor::Yellow;
    float Length = 100.f;
    float Width = 20.f;
    float Thickness = 1.f;

    FMatrix Matrix = FScaleRotationTranslationMatrix(
        MyComponent->GetComponentScale(),
        MyComponent->GetComponentRotation(),
        MyComponent->GetComponentLocation());
	
    DrawDirectionalArrow(PDI, Matrix, Color, Length, Width, SDPG_World, Thickness);
}

Drawing Functions Reference List

Primitive Drawing Interface

Primitive Utility Functions

To set the color for most of the geometry functions in this list, you’ll need to use FDynamicColoredMaterialRenderProxy. This will require adding RenderCore to your module’s dependencies.

auto* Proxy = new FDynamicColoredMaterialRenderProxy(GEngine->GeomMaterial->GetRenderProxy(), FLinearColor::Yellow);
PDI->RegisterDynamicResource(Proxy);

DrawPlane10x10(PDI, Plane, Radii, UVMin, UVMax, Proxy, SDPG_World);