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!
1 Like
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));
}
}
1 Like
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);
I’ve been looking for a way to create sprites at runtime for a while, and this answer was a godsend. But I’ll add to this ancient question in case anyone needs it, because it’s not all smooth sailing at least on UE 4.27.
-
First, the BakedRenderData parameters provided here will make your texture horizontally mirrored.
-
Second, as neofuturelabs noted, RenderGeometry and GetPivotPosition() will crash the package project because they are wrapped in the “#if WITH_EDITORONLY_DATA” macro. But the same macro also wraps the variables: SourceTexture, SourceUV and SourceDimension. Using any of these variables will also prevent you from packaging the project. You can get around this by using their Baked versions.
-
Thirdly, it is worth noting that many materials will work with the resulting sprites differently than with sprites made through the editor. In particular, materials with Blend Mode equal to Masked always lead to a complete blackening of the texture. I still haven’t figured out how to fix this, so I just started using only Blend Mode: Translucent.
Here is my modified version of the SetTexture function in DynamicPaperSprite with the ability to select SpritePivotMode (except Custom):
void UDynamicPaperSprite::SetTexture(UTexture2D* Texture, ESpritePivotMode::Type Pivot)
{
if (Texture){
float Width = Texture->GetSizeX();
float Height = Texture->GetSizeY();
BakedSourceTexture = Texture;
BakedSourceUV.Set(0, 0);
BakedSourceDimension.Set(Width, Height);
FVector2D RawPivot;
switch (Pivot)
{
case ESpritePivotMode::Top_Left:
RawPivot = FVector2D(BakedSourceUV.X, BakedSourceUV.Y);
break;
case ESpritePivotMode::Top_Center:
RawPivot = FVector2D(BakedSourceUV.X + BakedSourceDimension.X * 0.5f, BakedSourceUV.Y);
break;
case ESpritePivotMode::Top_Right:
RawPivot = FVector2D(BakedSourceUV.X + BakedSourceDimension.X, BakedSourceUV.Y);
break;
case ESpritePivotMode::Center_Left:
RawPivot = FVector2D(BakedSourceUV.X, BakedSourceUV.Y + BakedSourceDimension.Y * 0.5f);
break;
case ESpritePivotMode::Center_Center:
RawPivot = FVector2D(BakedSourceUV.X + BakedSourceDimension.X * 0.5f, BakedSourceUV.Y + BakedSourceDimension.Y * 0.5f);
break;
case ESpritePivotMode::Center_Right:
RawPivot = FVector2D(BakedSourceUV.X + BakedSourceDimension.X, BakedSourceUV.Y + BakedSourceDimension.Y * 0.5f);
break;
case ESpritePivotMode::Bottom_Left:
RawPivot = FVector2D(BakedSourceUV.X, BakedSourceUV.Y + BakedSourceDimension.Y);
break;
case ESpritePivotMode::Bottom_Center:
RawPivot = FVector2D(BakedSourceUV.X + BakedSourceDimension.X * 0.5f, BakedSourceUV.Y + BakedSourceDimension.Y);
break;
case ESpritePivotMode::Bottom_Right:
RawPivot = FVector2D(BakedSourceUV.X + BakedSourceDimension.X, BakedSourceUV.Y + BakedSourceDimension.Y);
break;
default:
RawPivot = FVector2D(BakedSourceUV.X + BakedSourceDimension.X * 0.5f, BakedSourceUV.Y + BakedSourceDimension.Y * 0.5f);
break;
}
FVector2D Pivot = RawPivot;
Pivot.X = FMath::RoundToFloat(Pivot.X);
Pivot.Y = FMath::RoundToFloat(Pivot.Y);
BakedRenderData.Empty(6);
BakedRenderData.Add(FVector4(0.0f - Pivot.X, Pivot.Y - 0.0f, 0.0f, 0.0f));
BakedRenderData.Add(FVector4(Width - Pivot.X, Pivot.Y - 0.0f, 1.0f, 0.0f));
BakedRenderData.Add(FVector4(Width - Pivot.X, Pivot.Y - Height, 1.0f, 1.0f));
BakedRenderData.Add(FVector4(0.0f - Pivot.X, Pivot.Y - 0.0f, 0.0f, 0.0f));
BakedRenderData.Add(FVector4(Width - Pivot.X, Pivot.Y - Height, 1.0f, 1.0f));
BakedRenderData.Add(FVector4(0.0f - Pivot.X, Pivot.Y - Height, 0.0f, 1.0f));
}
}
I hope this will be useful to someone.