UE4 Editor Visualisation Helper

· Read in about 10 min · (2030 Words)

How to add editor visualisation to unselected objects in UE4?

I solved a particular problem of mine in Unreal Engine this week, which was this:

How do I visualise any custom information I want at edit time (not runtime), even when objects are not selected?

I’m talking about information that doesn’t come naturally from things like collision bounds, or other existing components. Custom things specific to my game that I want to see all the time as I pan over the level.

For example: here’s a cone with altogether far too much visualisation applied:

Example

While I wouldn’t recommend adding quite this much visual detritus in ordinary circumstances, it demonstrates a number of the shapes supported. You should note that only one of the objects is selected in the screenshot, but they’re both rendering that debug info. That’s important, because some other approaches don’t support this.

This visualisation can be useful for things that have game relevance but aren’t collision or other things UE includes visualisation for you: marker actors, spawn patterns, areas of effect etc.

This might seem like a simple problem, but the default tools that UE provides have gaps that mean you can’t do this out of the box easily enough.

Things that don’t quite do it

DrawDebugLine/Sphere etc

Some nice simple methods for displaying debug information: but it’s designed for use at run-time.

Visual Logger

Visual Logger is DrawDebugLine/Sphere etc and logging on steroids. Rather than just rendering transient debug info, it records everything as well (including logging and property snapshots).

Very useful, but again it’s a run-time tool rather than edit-time.

FComponentVisualizer

OK now we’re on to something designed for edit-time use. You can add debug rendering to any component by registering a FComponentVisualizer. This is quite nice, you can use convenient methods like DrawCircle, and it’s what I was using for a while.

But, it has one major drawback: FComponentVisualizer only renders when the object is selected. Therefore, while this can be useful for detailed debug visualisation which would be too cluttering if always displayed, it’s no use for giving you a more holistic overview of a level.

Also, you must have a custom component to register this FComponentVisualizer against. If all you have is a simple Actor with a property or two you want to visualise, you have to create 2 classes - the component to attach it to, and the visualiser, and then register it at module setup. It’s inconvenient and doesn’t even do what we need.

Enter: Scene Proxy objects

So, what we actually need to display information at edit time regardless of selection state is FPrimitiveSceneProxy. All UPrimitiveComponent subclasses (more on them below) return one of these from their CreateSceneProxy method to instruct the renderer what to do. So, it’s not a “Proxy” in the sense of a stand-in for the component, it’s the actual rendering for that component in its entirety. This is done because the renderer runs in its own thread, and the data needs to be encapsulated. Implementations of FPrimitiveSceneProxy need to contain all the data they need to set up render state and vertex buffers etc.

This sounds pretty low-level, but there’s an existing subclass of this called FDebugRenderSceneProxy. This acts as a kind of intermediary between the simpler debug methods like DrawLine and the renderer. It’s actually used by the Visual Logger, Gameplay Debugger and other systems to render debug shapes, so it’s ideal for what we need.

But, how do we use it?

I’ve published my code in the Steve’s UE Helpers repository on GitHub. The 2 main classes are:

Creating a UPrimitiveComponent visualiser

UPrimitiveComponent is for components that contain some kind of geometry, usually for either rendering or collision. They’re the only components which are prompted to create the Scene Proxy objects we need to render the debug shapes all the time in the editor.

Now, you may or may not have a UPrimitiveComponent in your actor already - if you have a UStaticMeshComponent for example, this is a UPrimitiveComponent. However, for our purposes we’re going to create a completely separate component which is only there for the visualisation. This is because:

  • It’s trivial to mark this component as editor-only so it will be excluded from game builds
  • It keeps a clean separation between real rendering and editor visualisation
  • We can design this component to be re-usable in many circumstances

This component’s whole job is to:

  1. Correctly determine the bounds of its debug rendering
  2. Create a subclass of FPrimitiveSceneProxy when asked and populate it with all the info required to render the visualisation on the render thread

This component must exclude itself from game builds and not render in play mode.

Option 1: Completely generic, manual debug rendering

So the first implementation I made was a component which would let me just manually add a list of lines, circles, spheres etc to a blueprint. This has the advantage that I can add editor visualisation to absolutely anything; although I do have to specify all the shapes manually.

The full code for this is StevesEditorVisComponent I’ve edited it a bit here for brevity

UCLASS(...)
class STEVESUEHELPERS_API UStevesEditorVisComponent : public UPrimitiveComponent
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FStevesEditorVisLine> Lines;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FStevesEditorVisLine> Arrows;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FStevesEditorVisCircle> Circles;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FStevesEditorVisArc> Arcs;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FStevesEditorVisSphere> Spheres;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FStevesEditorVisBox> Boxes;

	UStevesEditorVisComponent(const FObjectInitializer& ObjectInitializer);

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

As you can see, mostly what I’m doing here is exposing lines, circles etc as properties so they can be easily defined. Then all you have to do is add this component to any Actor blueprint:

Adding the vis component

And then configure these properties, by adding entries to the lines, circles etc lists:

Visualiser component properties

Alternatively, you can also set this up in C++ instead of Blueprints, by constructing the component in code, and adding whatever shapes you want. In your Actor’s constructor:

#if WITH_EDITOR
    VisComponent = CreateDefaultSubobject<UStevesEditorVisComponent>("EditorVis");
    VisComponent->SetupAttachment(GetRootComponent());
    VisComponent->Lines.Add(
        FStevesEditorVisLine(
            FVector(0, -Radius, 0),
            FVector(0, Radius, 0),
            FColor::White));
    VisComponent->Arcs.Add(
        FStevesEditorVisArc(
            FVector::ZeroVector,
            FRotator::ZeroRotator,
            0, 180, Radius,
            16, FColor::White));
#endif

You could also update the shapes in VisComponent if you wanted in response to property changes. Here’s how you might do that:

#if WITH_EDITOR

void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& Evt)
{
    Super::PostEditChangeProperty(Evt);

    if (Evt.Property && Evt.Property->GetNameCPP() == "Radius")
    {
        UpdateVis();
    }
}

void AMyActor::PostEditUndo()
{
    Super::PostEditUndo();
    UpdateVis();
}

void AMyActor::UpdateVis()
{
    const FVector RadiusOffset(0, Radius, 0);
    auto& Line = VisComponent->Lines[0];
    Line.Start = -RadiusOffset;
    Line.End = RadiusOffset;

    auto& Arc = VisComponent->Arcs[0];
    Arc.Radius = Radius;

    // This is important to ensure scene proxy is regenerated
    VisComponent->MarkRenderStateDirty();
}
#endif

So that’s how to use it; but: how does it work?

UStevesEditorVisComponent internals

Firstly we do a little work in the constructor to ensure that things are set up correctly:

UStevesEditorVisComponent::UStevesEditorVisComponent(const FObjectInitializer& ObjectInitializer)
	: UPrimitiveComponent(ObjectInitializer)
{
	// set up some constants
	PrimaryComponentTick.bCanEverTick = false;
	SetCastShadow(false);
#if WITH_EDITORONLY_DATA
	// Note: this makes this component invisible on level instances, not sure why
	SetIsVisualizationComponent(true);
#endif
	SetHiddenInGame(true);
	bVisibleInReflectionCaptures = false;
	bVisibleInRayTracing = false;
	bVisibleInRealTimeSkyCaptures = false;
	AlwaysLoadOnClient = false;
	bIsEditorOnly = true;
	
}

This just makes sure that we’re being a good citizen; we’re editor-only, don’t tick, don’t render shadows or into reflections etc.

Note: SetIsVisualizationComponent(true); has a curious effect of making this component invisible in the component tree of level instances; as such you can only set it up in a Blueprint, not dynamically on level instances. That’s probably how you’re going to want to do it anyway so 🤷‍♂️

Then, we of course need to set up the rendering, which as we’ve already mentioned is via a FPrimitiveSceneProxy. Here’s our implementation (cut down a little, see the git repo for the full thing)

FPrimitiveSceneProxy* UStevesEditorVisComponent::CreateSceneProxy()
{
	auto Ret = new FStevesDebugRenderSceneProxy(this);

	const FTransform& XForm = GetComponentTransform();
	for (auto& L : Lines)
	{
		Ret->Lines.Add(FDebugRenderSceneProxy::FDebugLine(XForm.TransformPosition(L.Start),
		                                                  XForm.TransformPosition(L.End), L.Colour));
	}

    // ... 
    // Other shaped omitted for Brevity
    // ...

	for (auto& Box : Boxes)
	{
		FVector HalfSize = Box.Size * 0.5f;
		FBox DBox(-HalfSize, HalfSize);
		// Apply local rotation first then parent transform
		FTransform CombinedXForm = FTransform(Box.Rotation, Box.Location) * XForm;
		Ret->Boxes.Add(FStevesDebugRenderSceneProxy::FDebugBox(
			DBox, Box.Colour, CombinedXForm));
	}

	return Ret;
	
}

I’m creating an instance of FStevesDebugRenderSceneProxy to return; this is just an extension of the core UE FDebugRenderSceneProxy because it’s missing a couple of shapes that I like (circles and arcs).

In addition, FStevesDebugRenderSceneProxy overrides GetViewRelevance, which is used to filter out whether something should render in a given view. If you wanted to make the visualisation more context-specific, e.g. only render when certain view modes are enabled, you could expand this.

CreateSceneProxy is mostly just a data copying routine; we have to do this because the scene proxy that we return has to operate independently in the render thread. When asked to render, the FStevesDebugRenderSceneProxy instance calls functions like DrawCircle etc to queue up geometry for rendering. Because we’ve copied all the data across that it needs, it can do this in the render thread on its own from now on.

That’s all well and good, but our component won’t render unless it also reports its bounds. So we implement CalcBounds as well using the same info (full details left to the source, it’s pretty straight forward).

OK, so that’s our completely manual visualiser component. As we’re flying around our level every instance of the blueprint will render that visualisation whether it’s selected or not. I really like this as a quick way to add more useful manual visual info to actors which have information that doesn’t automatically have visualisation generated for it.

Option 2: More specific, automatically generated visualisation

Manual shapes are fine, but pretty soon you’re going to want to automatically generate them from some other information in your actor or its components.

I still prefer to keep this as a separate visualisation component and not add it to an existing one, for the reasons listed above. You could, if you already had a UPrimitiveComponent, make it do combined rendering for both visualisation and its usual behaviour, and block out the vis part with the preprocessor, but personally I don’t like that.

So for this case, I’ll create a component very similar to UStevesEditorVisComponent. It’ll have exactly the same constructor setup as shown above, which excludes it from game builds:

UMyCustomVisComponent::UMyCustomVisComponent(const FObjectInitializer& ObjectInitializer)
	: UPrimitiveComponent(ObjectInitializer)
{
	// set up some constants
	PrimaryComponentTick.bCanEverTick = false;
	SetCastShadow(false);
#if WITH_EDITORONLY_DATA
	// Note: this makes this component invisible on level instances, not sure why
	SetIsVisualizationComponent(true);
#endif
	SetHiddenInGame(true);
	bVisibleInReflectionCaptures = false;
	bVisibleInRayTracing = false;
	bVisibleInRealTimeSkyCaptures = false;
	AlwaysLoadOnClient = false;
	bIsEditorOnly = true;
}

But this time, CreateSceneProxy will generate the shapes we want to render, instead of using a manually entered list. For example, here’s an implementation based on a real one I use, that finds all instances of a different kind of component, gets a bunch of info from them and renders circles in pertinent locations.

FPrimitiveSceneProxy* UMyCustomVisComponent::CreateSceneProxy()
{
	auto Ret = new FStevesDebugRenderSceneProxy(this);

	TArray<USomeOtherComponent*> Comps;
	GetOwner()->GetComponents<USomeOtherComponent>(Comps);
	TArray<FSubThing> SubThings;
	for (auto Component : Comps)
	{
		if (!Component)
			continue;
		
		SubThings.Empty();
		Component->GetSubThings(SubThings);
		
		const FTransform& XForm = Component->GetComponentTransform();
		for (auto& S : SubThings)
		{
			FColor Colour = FColor::White;
			switch (S.ThingColour)
			{
			case EThingColour::White:
				Colour = FColor::White;
				break;
			case EThingColour::Red: 
				Colour = FColor::Red;
				break;
			case EThingColour::Yellow:
				Colour = FColor::Yellow;
				break;
			default:
				break;
			};
			Ret->Circles.Add(FStevesDebugRenderSceneProxy::FDebugCircle(
				S.WorldPosition,
				Component->GetForwardVector(), Component->GetRightVector(),
				XForm.GetMaximumAxisScale() * S.Radius,
				12, Colour
				));
		}
	}
	return Ret;	
}

It’s not the most efficient approach, but it’s only for editor usage so it doesn’t have to be. Now all I have to do is add a UMyCustomVisComponent to my actors that contain one or more of USomeOtherComponent, and it will visualise this automatically for me, adapting to changing settings dynamically and importantly always displaying everywhere in the level, regardless of what object I have selected.

UMyCustomVisComponent won’t be rendered when playing in the editor, and game builds will exclude it entirely.

Fin

That’s it! I hope this is useful for someone, I spent a couple of days trying to figure out the best way to do this, with very little help from the documentation; once again it was a case of “the source is the documentation” 🙄

Now that I’ve done it, I have another useful tool in my toolbox, and so can you if you grab it from the Steve’s UE Helpers repository on GitHub.

Have fun!