Pass parameter to constructor somehow?

I have a problem with the Constructor Helpers. I MUST create the UTexture and UMaterialInterface inside the constructor. But the UTexture must be set through a variable. So how can I set this variable prior to construction? I tried BeginDeferredActorSpawnFromClass() but that didn’t help at all. SpawnActorDeferred() also did not work. It seems that those only work for Blueprint constructors but not for the C++ ones.

Please tell me there is a way to do this without me having to initialize ALL possible textures in the actor. I also tried a static TArray as a workaround but the moment I used it I get a LNK1120.

Spawning it:

JobMarker = Cast<AJobMarker>(UGameplayStatics::BeginDeferredActorSpawnFromClass(this, AJobMarker::StaticClass(), GetTransform()));
		if (JobMarker != nullptr)
		{
			JobMarker->Init(MarkerTexturePath);
			UGameplayStatics::FinishSpawningActor(JobMarker, GetTransform());
		}
		else
		{
			LOG_ERROR("Failed to spawn JobMarker.")
		}

The actor that I’m trying to spawn:

AJobMarker::AJobMarker()
{
	PrimaryActorTick.bCanEverTick = true;
	<snip>
	Material = ULib::CreateObject<UMaterialInterface>("/Game/Materials/MI_MarkResource");
	Texture = ULib::CreateObject<UTexture>(TexturePath); // <<< This line is the problem
}

void AJobMarker::Init(FString texturePath) // This Init is too late!
{
	TexturePath = texturePath;
}

// Called when the game starts or when spawned
void AJobMarker::BeginPlay()
{
	Super::BeginPlay();
	SetMarkerMaterial();
}

void AJobMarker::SetMarkerMaterial()
{
	FMaterialSpriteElement element;
	element.bSizeIsInScreenSpace = false;
	element.BaseSizeX = 128.0f;
	element.BaseSizeY = 128.0f;
	MBB_Comp->Elements.Add(element);
	
	if (Material != nullptr)
	{
		UMaterialInstanceDynamic* newMaterialInstance = MBB_Comp->CreateDynamicMaterialInstance(0, Material);
		if (newMaterialInstance != nullptr)
		{
			newMaterialInstance->SetTextureParameterValue(FName("Texture"), Texture); // This line requires the GEngine so do NOT call this from a constructor.
			MBB_Comp->SetMaterial(0, newMaterialInstance);
		}
		else
		{
			LOG_ERROR("MaterialInstance is NULL.")
		}
	}
	else
	{
		LOG_ERROR("Material is NULL.")
	}
}

It does not seem to be possible to pass parameters into a constructor, and the general consensus is to have an initialization function that you call immediately after spawning that takes the necessary parameters.

Is there any particular reason that you MUST create these in the constructor?

1 Like

Yes the UTexture MUST be initialized there (limitation from UE4) and this is a variable (FString). So the general consensus (to which I mostly agree) does sadly not apply here.

I solved it the static way. It seemed the best solution given the limitations while also being forced to do it inside the constructor.

Header:

static TArray<UTexture*> JobMarkerTextures;

Cpp:

// below the #includes (this can't be assigned in the header file!)
TArray<UTexture*> AJobMarker::JobMarkerTextures = TArray<UTexture*>();

// In constructor method
if (AJobMarker::JobMarkerTextures.Num() == 0) // or use a static boolean here.
{
	AJobMarker::JobMarkerTextures.Add(ULib::CreateObject<UTexture>(P_MARKER_WOOD_TEX));
	AJobMarker::JobMarkerTextures.Add(ULib::CreateObject<UTexture>(P_MARKER_STONE_TEX));
}

Now they all share the same materials while still having different material instances and without having to dump a variable to the constructor.

Note that the UTexture and UMaterial MUST be created in the constructor but the UMaterialInstanceDynamic MUST be created after BeginPlay() was called while also not being able to use parameters in the constructor in c++.

There’s one little feature for that: CustomPreSpawnInitalization.

FActorSpawnParameters ActorSpawnParameters;
ActorSpawnParameters.CustomPreSpawnInitalization.CustomPreSpawnInitalization = [&](AMyActor* MyActor)
			{
				MyActor.Health = 100;
			};
GetWorld()->SpawnActor (MyActor::StaticClass(), &Location, &Rotation, ActorSpawnParameters);
1 Like

Nice one!

One note, for me the compiler complains about the assignment when using anything else than AActor*, but using AActor you can do a Cast(…) on the parameter.