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);
}