UE5: Custom billboard for USceneComponent

Hi there :slight_smile:

How do I use USceneComponent::CreateSpriteComponent(UTexture2D*) correctly?
My code below “works” as you see in the screenshot. However, after restarting the editor I get the Actor default icon instead of my custom one for Actors already in the Level. It changes to the correct icon after I move the Actor around.

Context: I’m working on a component derived from USceneComponent that will add a bit of meta data to my Blueprint. In order to see it, properly select and move it around, I like it to have it’s own billboard/icon. Users will place a number of these in their Blueprint as these are snap/docking points helping with alignment of other Actors, think of parts of a construction kit or 3D tiles that need to align nicely together. Using the grid or vertex snap is too limiting in my case, so I want full custom snapping :blush:

My current code is quoted below. I tried to call this from various places, like the constructor, OnRegister(), PostLoad(). Also tried PostInitProperties(), but it causes the editor to crash :thinking:

Basically it boils down to the question where to put initialize code for something like this. I have to admit I’m a bit confused when exactly what is called. E.g. OnRegister/PostLoad are both called every frame when an Actor moves in the editor, not just after “loading”. I’m aware of the Actor Lifecycle diagram, but it does not help me in this case as it focuses on the play lifecycle, not editor lifecycle?

#if WITH_EDITORONLY_DATA
	bVisualizeComponent = true;

	UObject* Object = UEditorAssetLibrary::LoadAsset("Texture2D'/Game/Unreal2Foundry/Resources/SnapPoint.SnapPoint'");
	UTexture2D* Sprite = dynamic_cast<UTexture2D*>(Object);
	if (SpriteComponent)
	{
		// possibly not needed as the sprite should be serialized? But even with this I get the wrong sprite after load :(
		SpriteComponent->SetSprite(Sprite);
	}
	else
	{
		CreateSpriteComponent(Sprite);
	}

	if (SpriteComponent)
	{
		SpriteComponent->SetDepthPriorityGroup(ESceneDepthPriorityGroup::SDPG_Foreground);
	}
#endif

This is how it’s supposed to look:
image

Actually it seems CreateSpriteComponent(nullptr) is called from USceneComponent::OnRegister(), so possibly I don’t need it to call myself at all.

I tried to replicate what for example UForceFeedbackComponent is doing, but then I get no icon at all on load. Only when I move the actor or freshly spawn it, the icon appears. Using UForceFeedbackComponent itself works as expected when attached to the same actor, though. The only things related I see is setting bVisualizeComponent in the constructor and setting the sprite from OnRegister as well as PostEditChangeProperty. Doing that in my own component does not help, there must be something else :roll_eyes:

Ah…figured it out, or 90% of it. I was using UEditorAssetLibrary::LoadAsset, need to use LoadObject<UTexture2D> instead.

Working code below. Remaining issue is that the icon is not visible while on the backside of things, only after the actor was moved. Selecting the actor makes it temporarily visible. Setting DepthPriorityGroup should take care of that, but apparently it’s only part of it.

//  =================== Header ===================
#pragma once

#include "CoreMinimal.h"
#include "Components/SceneComponent.h"
#include "U2FSnapPointComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class TEST01_API UU2FSnapPointComponent : public USceneComponent
{
	GENERATED_BODY()

public:
	UU2FSnapPointComponent();

	virtual void OnRegister() override;

private:
	void UpdateSpriteTexture();
};


//  =================== CPP ===================

#include "U2FSnapPointComponent.h"

#include "Components/BillboardComponent.h"

UU2FSnapPointComponent::UU2FSnapPointComponent()
{
#if WITH_EDITORONLY_DATA
	bVisualizeComponent = true;
#endif
}

void UU2FSnapPointComponent::OnRegister()
{
	Super::OnRegister();
	UpdateSpriteTexture();
}

void UU2FSnapPointComponent::UpdateSpriteTexture()
{	
#if WITH_EDITORONLY_DATA
	if (SpriteComponent)
	{
		SpriteComponent->SetSprite(LoadObject<UTexture2D>(nullptr, TEXT("/Game/Unreal2Foundry/Resources/SnapPoint.SnapPoint")));		
		SpriteComponent->SetDepthPriorityGroup(ESceneDepthPriorityGroup::SDPG_Foreground);
	}
#endif
}