Custom thumbnail on Blueprint

Hi,

I’m trying to create a buff system on the UE. My goal is to have a base class in C++, and then every buff is a blueprint extending this class. This part is quite easy, but I’m trying to have a custom thumbnail on each of theses buffs (the icon of the buff obviously).
I didn’t find any ways using the blueprints, and I tried to create a custom data type, but I need the blueprint editor on theses buffs.

How can I customize a blueprint thumbnail to put a custom image? If I can’t, how can I put the blueprint editor on a custom data type?

Thanks,
Tavon.

See the response to this question:

I had the exact same usecase as you. Using the old power of necromancy, I give you an answer to the first part of your question. I copy-pasted it from my post on the UE forums but I figured it would make sense to post it here as well since I have not found anything on this topic and this thread comes up here and there. Maybe people will find this thread and find my solution useful:

Turns out Unreal uses a caching system for its thumbnails so that it does not need to load an asset in the Content Browser just to be able to display its thumbnail. Instead, every time that asset is being loaded, its generated thumbnail will be saved to disk. The next time that asset should be previewed in the editor, instead of loading that object, it will load the cached thumbnail from disk.

That was just the first half of the problem though. I still wasn’t able to display a custom thumbnail for my UObject-derived type. I had to do some trickery for that. When one derives a class from UObject or another native class naively inherited from UObject, the editor will treat this new class as a Blueprint Class asset type, meaning, that it will open the Blueprint editor, can contain variables, graphs etc. like an Actor class. That also meant though, that my custom UThumbnailRenderer needed to take UBlueprint as a target type and not UBuff since the asset itself is not of my custom type. There is a UBlueprintThumbnailRenderer already in the engine source so what I did was to derive my custom renderer from that type and in the StartupModule function of my editor plugin I removed the default UBlueprintThumbnailRenderer und replaced it with my own:

UThumbnailManager::Get().UnregisterCustomRenderer(UBlueprint::StaticClass());
UThumbnailManager::Get().RegisterCustomRenderer(UBlueprint::StaticClass(), UCustomBlueprintThumbnailRenderer::StaticClass());

The actual custom renderer looks like this:

.h

UCLASS()
class UCustomBlueprintThumbnailRenderer : public UBlueprintThumbnailRenderer
{
GENERATED_BODY()
protected:

UCustomBlueprintThumbnailRenderer(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{}

// UThumbnailRenderer implementation
virtual void GetThumbnailSize(UObject* Object, float Zoom, uint32& OutWidth, uint32& OutHeight) const override;
virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget*, FCanvas* Canvas, bool bAdditionalViewFamily) override;
virtual bool CanVisualizeAsset(UObject* Object) override;
  protected:

UTexture2D* GetTextureFromGeneratedClass(UClass* Class) const;
 
};

.cpp:

void UCustomBlueprintThumbnailRenderer::GetThumbnailSize(UObject* Object, float Zoom, uint32& OutWidth, uint32& OutHeight) const
{
UBlueprint* Blueprint = Cast<UBlueprint>(Object);

if (Blueprint)
{
if (UTexture2D* Texture = GetTextureFromGeneratedClass(Blueprint->GeneratedClass))
{
OutWidth = FMath::TruncToInt(Zoom * (float)Texture->GetSurfaceWidth());
OutHeight = FMath::TruncToInt(Zoom * (float)Texture->GetSurfaceHeight());
}
}
Super::GetThumbnailSize(Object, Zoom, OutWidth, OutHeight);
 }

void UCustomBlueprintThumbnailRenderer::Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget* RenderTarget, FCanvas* Canvas, bool bAdditionalViewFamily)
{
UBlueprint* Blueprint = Cast<UBlueprint>(Object);
if (Blueprint)
{
if (UTexture2D* Texture2D = GetTextureFromGeneratedClass(Blueprint->GeneratedClass))
{
const bool bUseTranslucentBlend = Texture2D && Texture2D->HasAlphaChannel() && ((Texture2D->LODGroup == TEXTUREGROUP_UI) || (Texture2D->LODGroup == TEXTUREGROUP_Pixels2D));
TRefCountPtr<FBatchedElementParameters> BatchedElementParameters;
if (bUseTranslucentBlend)
{
// If using alpha, draw a checkerboard underneath first.
const int32 CheckerDensity = 8;
auto* Checker = UThumbnailManager::Get().CheckerboardTexture;
Canvas->DrawTile(
0.0f, 0.0f, Width, Height, // Dimensions
0.0f, 0.0f, CheckerDensity, CheckerDensity, // UVs
FLinearColor::White, Checker->Resource); // Tint & Texture
}
// Use A canvas tile item to draw
FCanvasTileItem CanvasTile(FVector2D(X, Y), Texture2D->Resource, FVector2D(Width, Height), FLinearColor::White);
CanvasTile.BlendMode = bUseTranslucentBlend ? SE_BLEND_Translucent : SE_BLEND_Opaque;
CanvasTile.BatchedElementParameters = BatchedElementParameters;
CanvasTile.Draw(Canvas);
if (Texture2D && Texture2D->IsCurrentlyVirtualTextured())
{
auto VTChars = TEXT("VT");
int32 VTWidth = 0;
int32 VTHeight = 0;
StringSize(GEngine->GetLargeFont(), VTWidth, VTHeight, VTChars);
float PaddingX = Width / 128.0f;
float PaddingY = Height / 128.0f;
float ScaleX = Width / 64.0f; //Text is 1/64'th of the size of the thumbnails
float ScaleY = Height / 64.0f;
// VT overlay
FCanvasTextItem TextItem(FVector2D(Width - PaddingX - VTWidth * ScaleX, Height - PaddingY - VTHeight * ScaleY), FText::FromString(VTChars), GEngine->GetLargeFont(), FLinearColor::White);
TextItem.EnableShadow(FLinearColor::Black);
TextItem.Scale = FVector2D(ScaleX, ScaleY);
TextItem.Draw(Canvas);
}
}
return;
}
Super::Draw(Object, X, Y, Width, Height, RenderTarget, Canvas, bAdditionalViewFamily);
 }

bool UCustomBlueprintThumbnailRenderer::CanVisualizeAsset(UObject* Object)
{
UBlueprint* Blueprint = Cast<UBlueprint>(Object);

if (Blueprint && GetTextureFromGeneratedClass(Blueprint->GeneratedClass) != nullptr)
{
return true;
}
return Super::CanVisualizeAsset(Object);
 }

UTexture2D* UCustomBlueprintThumbnailRenderer::GetTextureFromGeneratedClass(UClass* Class) const
{
if (Class)
{
if (Class->IsChildOf(USkillBase::StaticClass()))
{
if (USkillBase* CDO = Class->GetDefaultObject<USkillBase>())
{
return CDO->GetIcon();
}
}
else if (Class->IsChildOf(UBuff::StaticClass()))
{
if (UBuff* CDO = Class->GetDefaultObject<UBuff>())
{
return CDO->GetIcon();
}
}
}
return nullptr;
 }

You can also find the post on the forums with a nicer format:
Forum post

3 Likes