Accessing Variables From a TSubclassOf Variable that are object specific

Hey guys, I am new to Unreal and using c++ with it and am finding it hard to achieve what I want to.
I have made a Prefab class, I want to create a blueprint from that class, add in render components and child actors, set variables to be the location of the child actors, and then spawn them from another LevelGenerator class, using TSubclassOf, and accessing their variables (location of child actors) to then spawn them next to each other. The problem I am having is that so far I can only access their default values but I want to access the values of the object.
How I have done this so far (I don’t know if this is the best way, please suggest improvements) is to have a function “CalledFromCpp” that the blueprint defines as setting the variables to the location of the child actors relative to the root, and this is called OnConstruction so it changes. In the editor when I create the PrefabBP, the variables do not update, but when I spawn one into the world they are listed correctly, however because the LevelGenerator only accesses defaults it does not correctly get this data.
Sorry if I have written too much I found it hard to explain, perhaps it is better to just show the code:
Prefab.h:

UCLASS()
class CIPROJECT_API APrefab : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	APrefab();

	virtual void OnConstruction(const FTransform& transform) override;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(EditAnywhere)
	FVector leftConnector;
	
	UFUNCTION(BlueprintCallable)
	void setLeftConnector(FVector v);

	UPROPERTY(EditAnywhere)
	FVector rightConnector;

	UFUNCTION(BlueprintCallable)
	void setRightConnector(FVector v);

	UFUNCTION(BlueprintImplementableEvent, Category = "Set")
	void CalledFromCpp();
};

Prefab.cpp:

// Sets default values
APrefab::APrefab()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
}

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

void APrefab::OnConstruction(const FTransform& transform)
{
	CalledFromCpp();
}

// Called every frame
void APrefab::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void APrefab::setLeftConnector(FVector v)
{
	leftConnector = v;
}

void APrefab::setRightConnector(FVector v)
{
	rightConnector = v;
}

LevelGenerator.h:

UCLASS()
class CIPROJECT_API ALevelGenerator : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ALevelGenerator();

	UPROPERTY(EditAnywhere)
	TArray<TSubclassOf<class APrefab>> Prefabs;

	UPROPERTY(EditAnywhere)
	int32 NumberOfRooms;

	UFUNCTION(BlueprintCallable)
	void Spawn();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	
};

LevelGenerator.cpp:

// Sets default values
ALevelGenerator::ALevelGenerator()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	
}

// Called when the game starts or when spawned
void ALevelGenerator::BeginPlay()
{
	
	ALevelGenerator::Spawn();
}

void ALevelGenerator::Spawn()
{
	FVector Shifter = Prefabs[0]->GetDefaultObject<APrefab>()->rightConnector - Prefabs[0]->GetDefaultObject<APrefab>()->leftConnector;
	//FVector Shifter(580.0f, 0.0f, 0.0f);
	if (Prefabs[0]!=nullptr)
	{
		UWorld* world = GetWorld();
		if (world)
		{
			FVector Location(0.0f, 0.0f, 0.0f);
			FRotator Rotation(0.0f, 0.0f, 0.0f);
			FActorSpawnParameters SpawnInfo;
			for (int i = 0; i < NumberOfRooms; i++)
			{
				FVector Shift = Shifter * (i);
				world->SpawnActor<APrefab>(Prefabs[0], Location + (Shift), Rotation, SpawnInfo);
			}
		}
	}
}

Any help would be appreciated, thanks a bunch.

Hi Tommy,

Not 100% sure I’ve understood what you want to do, but here’s how I’ve taken it.

You want to procedurally generate levels from a number of different blueprints which all inherit from a common class (your Prefab class). And for the Prefab class itself, you want the level generator to be able to rearrange the child objects within it.

There are a couple of things going on, that I’m not sure whether you’ve got the right mental model for blueprints, so if you have, my apologies for going over fundamentals, but I’m hoping this will help.

The term blueprint is kinda overloaded in unreal, at least how it’s colloquially used. Both those uses reflect C++ programming, but people are more familiar with the separation. A blueprint can be (very much like it’s name), the template for the design of a class. The same way your declaration and definition are in C++. While you’re writing those, you’re not talking about any particular instance of that class, just how they all operate. This is what the blueprints you see in your content browser are.

The other meaning for blueprint, is what we’d term an instance of a class in c++. This is what a blueprint within your scene is (or within another blueprint in blueprint editor - in that case the one you’re editing is still the definition, but any blueprints you add within it are instances of their respective type). So, armed with this knowledge, approach your blueprint design, the same way you’d approach your class design in c++.

For your Prefab blueprint, define all the functions you want to be able to call that will do the work you required. These can be implemented in either C++ or blueprints (through the same BlueprintImplementableEvent you’ve used already). Bits of what you’ve described so far, kinda break encapsulation (having LevelGenerator know too much about the internals of Prefab, but I figure you can address that as you revisit this).

Let’s assume that you’ve come up with an interface you like, and you want to be able to instruct an instance of a prefab to do what you wish. Your call to world->SpawnActor is creating an instance of your blueprint (as if you’d called new in standard C++). I’m not sure where SpawnActor sits in the recommended workflows these days, but I believe the usual preferred function for instantiating is NewObject instead. This has both templated and non-templated versions, but is very similar to SpawnActor. Now, messing with an actual instance is as easy as getting a reference to it.

APrefab * currentPrefab = NewObject<APrefab>(this/*the outer/UObject*/, Prefabs[0]/*optional, but you'll just get the base template type, APrefab, without a specific one*/, FName("instanceName")/*optional*/);

Now just call functions etc as you normally would. If you do want to mess with internal pieces directly, you can either do so by having a function you define in the base class that passes references, do the work inside the Prefab cpp, or have blueprint implementable functions that do the work in blueprint itself on your leaf types. Just like c++ coding, you’ll want to define the interface at the top level, given you can’t really get to type specific functions from the end blueprint, you may want a hierarchy in C++ so that you can type cast to type specific functions and inherit from a lower level c++ class in your blueprint.

class ABossRoom: public APrefab
{
// blueprint implementable functions
}

ABossRoom * currentBossRoom = Cast<ABossRoom>(currentPrefab);
if (currentBossRoom != nullptr)
{
    currentBossRoom->BossRoomSpecificFunction();
}

Hope this helps.

Cheers,

Alan.

Thank you so much for the detailed and quick response, this has definitely helped my understanding. You were right; I was somewhere in between thinking of Blueprints as a visual representation of a class and as an object/instance. The compromised solution I came up with was just to set the blueprint (that inherits from my cpp Prefab class) data values manually in the blueprint editor, this would however quickly get tedious when adding more versions of prefabs. I have implemented your suggestions and think they will help, more and more as I add to the project, especially as now I can access functions. Thanks again for the response Alan, it has helped a lot.

You’re most welcome :slight_smile: