How to Arrange Multiple Meshes Inside C++ Actor Class

I am trying to crate a combination puzzle that once solved will open a door. The movable puzzle pieces are embedded in the door model. Once the code is solved I want the two doors to open. An example of the model I am trying to create is below (this was created using the level editor).

I have code that randomizes the solution and I have already coded the movable puzzle pieces. I am having trouble understanding how I am supposed to spawn all the individual actor components and arrange their positions in the AComboDoor actor class.

My original idea was to write the C++ code spawning the actors and then arrange their positions in a Blueprint Sub-class, however I have not been able to get that to work. The door positions in the Blueprint are not used when the doors are spawned into the world.

I want this door puzzle to be a “prefab” so that I can just spawn it in the world procedurally eventually. What is the generally accepted way of arranging multiple components in a C++ class? Do I really have to set all the rotations/positions in the C++ code or is there a visual way that I can do that?

Here is the combo door code for what its worth:

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

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

	virtual void OnConstruction(const FTransform& Transform) override;

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

	// Register callback for puzzle solved
	void RegisterSolvedListener(ISolvedPuzzleListener& listener);

	// Puzzle Piece Components
	UPROPERTY(EditDefaultsOnly)
	TArray<AHexPuzzlePiece*> Pieces;

	// Wall Components
	UPROPERTY(EditDefaultsOnly)
	UStaticMeshComponent* RightDoor;

	UPROPERTY(EditDefaultsOnly)
	UStaticMeshComponent* LeftDoor;

	UPROPERTY(BlueprintReadOnly)
	uint8 NumberOfPieces;

protected:
	TArray<HexPuzzlePieceState> Solution;

private:
	TArray<ISolvedPuzzleListener*> Listeners;
};
// Sets default values
AComboDoor::AComboDoor() : NumberOfPieces(4)
{
 	// 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;

	/* Create a random solution for our pieces */
	for (int i = 0; i < this->NumberOfPieces; i++) {
		Solution.Add(static_cast<HexPuzzlePieceState>(rand() % PIECE_STATE_ROTATING));
	}

	/* Create the static mesh components for both of the doors */
	RightDoor = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RightDoor"));
	this->RootComponent = this->RightDoor;

	LeftDoor = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LeftDoor"));
	LeftDoor->SetupAttachment(RootComponent);

	/* Load the assets for the doors */
	static ConstructorHelpers::FObjectFinder<UStaticMesh> DoorAsset(TEXT("/Game/BlenderModels/test_wall_v2.test_wall_v2"));
	if (DoorAsset.Succeeded())
	{
		/* Set the right door first */
		RightDoor->SetStaticMesh(DoorAsset.Object);
		RightDoor->SetWorldScale3D(FVector(0.5f));

		/* Set the left door next, since they use the same mesh we can re-use */
		LeftDoor->SetStaticMesh(DoorAsset.Object);
	}
}

void AComboDoor::OnConstruction(const FTransform& Transform)
{
	Super::OnConstruction(Transform);

	if (this->LeftDoor) {
		this->LeftDoor->SetRelativeLocation(FVector(0.0f, 100.0f, 0.0f));
		this->LeftDoor->SetWorldScale3D(FVector(0.5f));
	}	
}

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

	UWorld* World = this->GetWorld();
	if (World) {
		/* Add our puzzle pieces to the object */
		for (int i = 0; i < this->NumberOfPieces; i++) {
			ue_printf("Spawning actor %d", i);
			Pieces.Add(World->SpawnActor<AHexPuzzlePiece>());
		}
	}
}

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

}

void AComboDoor::RegisterSolvedListener(ISolvedPuzzleListener& listener)
{
	this->Listeners.Add(&listener);
}

Dear @WTBFriends,

Hi there!

:star2: Welcome to the Unreal Engine Community!!! :star2:

My personal recommendation is to avoid trying to create the components in C++ constructor, as Blueprints makes setting all the initial values so much easier and more intuitive.

If you do indeed want to manipulate the components in C++ rather than BP, I would do something like

class .h

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FrogTurret")
TArray<UStaticMeshComponent*> Parts;

This is an empty array, until you then, in the Blueprint of this class, Add to the Array in the Constructor Script.

The advantage of using the Construction Script is that when you make changes and compile, the changes become immediately evident in your viewport, without even running the game.

And with the randomization you want, :zap: you could just keep hitting the compile button :zap: and watch all the generations occur, seeing your logic play out with every click rather than every compile → load editor → run game cycle which takes a lot longer
~

Then, you can add 4 Static Mesh Components to the Blueprint version of the C++ Class, and have full access in C++ to these easily-created components, by adding each of them to the C++ array.

With the array, you also have easy C++ identification of your 4 parts, because they are now indexed:

Parts[0] goes to the far left,

Parts[1] goes next to Parts[0]

etc.

So the C++ code is highly readible.

~

I personally would do a rough draft of your desired positions of your parts in the BP Constructor script.

Once you have the logic and the math working the way you want, then you could move the logic to C++ for performance, though if it is a one time positioning, there’s not really a performance benefit to using C++.

C++ would be the ideal way to arrange these parts if the logic involved was heavily math intensive, over time / the logic has to run many times.

Unless you really find vector math clunky to do in BP,

a BP implementation, with C++ option for access via the Array I mentioned above,

will result in you having the :zap: fastest and cleanest and least-crash-prone development cycle. :zap:

If you want to vary the number of parts, you can add the static mesh components dynamically, which is also much faster to do in BP than C++

~

Below you can see I’ve very quickly added dynamic static mesh components in constructor script that vary in count, stacking vertically from an offset (ZeroVector at the moment).

This is how simple your final code can look, if you then add all the components to an array like I mentioned, now you can access them in C++ and you’re done

~

All that said, of course :sparkling_heart: I Love UE C++ :sparkling_heart:, and this is the kind of setup I myself use to have:

  1. Easy Access to the components in C++ and in BP
  2. Easiest and fastest creation of the components and setting of default values

Just make sure to always :zap: check your pointers :zap:, and your array access itself!


if(Parts.Num() < 1)
{
   //parts not added yet
   return;
}

if(Parts[0])
{
   //do things to the first part
}

So translating to your current C++ Code now,

just change the UE4 macros of “pieces” and move all your creation logic to BP Constructor,

and you will have an easy, maintainable solution,


:zap: That you can test very quickly ( compile button → click click click ), :zap:


and yes, if you put the positioning logic in BP Constructor Script,

each time you create one of these actors, you will get a new version of your puzzle, just like you want, with the least amount of C++ code to have to keep track of and keep stable from crashing.

Especially in multiplayer games, things don’t always become stable as new clients join/leave until around the time of the BP Constructor Script, so the less you do in the C++ constructor, the faster you will get to a :rose: stable release. :rose:

:sparkling_heart:

Rama

Hi @Rama !

Thank you for the incredibly detailed post!

The combination of blueprints and c++ has been a bit of a mystery so far. So thanks for the explanation, things are much clearer now :slightly_smiling_face:

Thanks again for going into so much detail!

You’re welcome & Victory to You @WTBFriends !!!

:rose: I can’t wait to see more of what you are Creating as you get further along! :rose:

We need more games with physically-aware puzzles in them!

:heart:

Rama