Dynamically create sprite from texture or update sprite's texture on runtime

I’m sorry if this question is dumb, but I’ve been googling for 2 days now and wasn’t able to get it to work. I’m using the HTTP module to download an image and create a texture dynamically at runtime, using UTexture2D::CreateTransient(). I’d then like to update a PaperSpriteActor to show this texture. After a lot of messing around, I first created a DynamicPaperSprite that simply adds one method SetTexture to PaperSprite:

void UDynamicPaperSprite::SetTexture(UTexture2D* Texture)
{
	SourceTexture = Texture;
	SourceDimension.Set(Texture->GetSizeX(), Texture->GetSizeY());
	SourceUV.Set(0, 0);
	PixelsPerUnrealUnit = 1.0f;
	PostLoad();
	GEngine->AddOnScreenDebugMessage(-1, 20.0f, FColor::Red, FString(TEXT("Done DynamicPaperSprite->SetTexture")));
}

Then, on my DynamicPaperSpriteActor, I do this:

void ADynamicPaperSpriteActor::UpdateTexture(UTexture2D* Texture)
{
	UPaperRenderComponent* Render = Super::GetRenderComponent();
	Render->SetMobility(EComponentMobility::Stationary);
	UDynamicPaperSprite* Sprite = dynamic_cast<UDynamicPaperSprite*>(Render->GetSprite());
	if (!Sprite)
		Sprite = NewObject<UDynamicPaperSprite>();
	Sprite->SetTexture(Texture);
	Render->SetSprite(Sprite);
}

When I run this, the game successfully sets up the new sprite on my actor. However, the created sprite is completely blank. I’m sure the image I’m downloading is not blank, I’ve tested this. Any ideas?

Thanks in advance!

OK, so I figured it out. I also had to update the BakedRenderData internal field. So my final DynamicPaperSprite code looks like this (fixed pivot, full source image, no transformations possible):

UDynamicPaperSprite::UDynamicPaperSprite(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SpriteCollisionDomain = ESpriteCollisionMode::None;
	PixelsPerUnrealUnit = 1.0f;
	RenderGeometry.GeometryType = ESpritePolygonMode::SourceBoundingBox;
}

void UDynamicPaperSprite::SetTexture(UTexture2D* Texture)
{
	if (Texture)
	{
		float Width = Texture->GetSizeX();
		float Height = Texture->GetSizeY();

		SourceTexture = Texture;
		SourceUV.Set(0, 0);
		SourceDimension.Set(Width, Height);

		const FVector2D Pivot = GetPivotPosition();
		BakedRenderData.Empty(6);

		// Uses 2 triangles to describe the mesh.
		// Each vertex is in the format: [ImageX relative to Pivot, ImageY relative to Pivot, U, V]
		BakedRenderData.Add(FVector4(0.0f - Pivot.X, Pivot.Y - 0.0f, 1.0f, 0.0f));
		BakedRenderData.Add(FVector4(Width - Pivot.X, Pivot.Y - 0.0f, 0.0f, 0.0f));
		BakedRenderData.Add(FVector4(Width - Pivot.X, Pivot.Y - Height, 0.0f, 1.0f));

		BakedRenderData.Add(FVector4(0.0f - Pivot.X, Pivot.Y - 0.0f, 1.0f, 0.0f));
		BakedRenderData.Add(FVector4(Width - Pivot.X, Pivot.Y - Height, 0.0f, 1.0f));
		BakedRenderData.Add(FVector4(0.0f - Pivot.X, Pivot.Y - Height, 1.0f, 1.0f));
	}
}

Thanks for posting how you solved this!

I just had to implement something similar (engine ver. 4.17), but I wanted to note that the Sprite’s visuals wouldn’t update after changing the texture without also calling MarkRenderStateDirty() on the UPaperSpriteComponent containing the Sprite. I suppose that SetSprite would do the same thing internally, but in my situation I don’t need the physics state or bounds etc to update, so this is a bit lighter weight. This way, the mobility also doesn’t need to be fiddled with.

One more thing, the following variables/functions are contained within a “#if WITH_EDITORONLY_DATA” macro in the UPaperSprite base class, so build errors occur when packaging (as the editor is not included in packaged builds):

  • RenderGeometry

  • GetPivotPosition()

These errors can be resolved as follows:

  • “RenderGeometry.GeometryType = ESpritePolygonMode::SourceBoundingBox;” can be safely removed without any issues

  • “const FVector2D Pivot = GetPivotPosition();” can be replaced by the following (this is basically a copy of the GetPivotPosition() code for the Center_Center PivotMode, which is the only type I am using)


const FVector2D RawPivot = FVector2D(SourceUV.X + SourceDimension.X * 0.5f, SourceUV.Y + SourceDimension.Y * 0.5f);

FVector2D Pivot = RawPivot;

Pivot.X = FMath::RoundToFloat(Pivot.X);

Pivot.Y = FMath::RoundToFloat(Pivot.Y);