Download

How to construct in C++ and have the Blueprint display properly, thumbnails?

I’m working on a Tetris style game to learn UE4 C++.

The first thing I’ve created is a ShapeActor. I’ve attached my code below.

My questions are:

  • I have an array of static mesh which are used to construct each shape. I want to be able to be in the editor and select these static mesh inside of it. I can do that by extending this class to a Blueprint and picking them there. But what I would like to do is to have this array be a global somewhere, so that I can share the same array of static mesh (the size, and indexing) to other classes, like the game board, so that when I construct shapes from indexing, they will appear correctly. The way this is now I have to make the same static mesh array inside this actor, in the pawn, and in the game board. There has to be a right way to do that, but I’m not sure what it is.

  • If I open the blueprint of a correctly created shape, I can see it in the viewport. But the thumbnail does not update. Also the blueprint doesn’t show the components in the component view on the side, I guess because they are created in C++. Is there a way to get the thumbnail to update and show the static mesh?

  • I also have an array of materials. I’d like to have that array as a global as well to share with the pawn and game board. I don’t want to have to make the array on each component and make sure it’s made the same way each time. But since I have to pick the contents of the array inside the editor, and thus from a blueprint, I don’t know how to do that.

  • Maybe I’m supposed to pull those objects directly from the engine using hard coded pathing so that things are created and displayed from C++ correctly?

Thanks for any suggestions!


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FBlock.h"
#include "ShapeActor.generated.h"

UCLASS()
class TETRIS_API AShapeActor : public AActor
{

    GENERATED_BODY()

public:    

    AShapeActor();

    void OnConstruction(const FTransform& Transform) override;

    virtual void Tick(float DeltaTime) override;

protected:

    virtual void BeginPlay() override;

    // Get an element from the block array.
    FBlock GetBlock(uint32 row, uint32 column);

    // Array of blocks that represent the shape.
    UPROPERTY(EditAnywhere, Category = "ShapeActor")
    TArray<FBlock> BlockArray;

    // Array of materials to choose from for the shape.
    UPROPERTY(EditAnywhere, Category = "ShapeActor")
    TArray<class UMaterial*> MaterialArray;

    // The number of columns in the shape.
    UPROPERTY(EditAnywhere, Category = "ShapeActor")
    int32 NumberOfColumns = 0;

    // The number of rows in the shape.
    UPROPERTY(EditAnywhere, Category = "ShapeActor")
    int32 NumberOfRows = 0;

    // Root component to use for locating the stack actor board.
    USceneComponent* SceneComponent;

    // Array of static mesh objects to choose from for the shape.
    UPROPERTY(EditAnywhere, Category = "ShapeActor")
    TArray<UStaticMesh*> StaticMeshArray;

    // Array of static mesh component objects that visualize the shape.
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "ShapeActor")
    TArray<UStaticMeshComponent*> StaticMeshComponentArray;

};

#include "ShapeActor.h"

#include "Classes/Materials/Material.h"
#include "StackActor.h"
#include "Components/StaticMeshComponent.h"

AShapeActor::AShapeActor()
{
    PrimaryActorTick.bCanEverTick = false;

    SceneComponent = CreateDefaultSubobject<USceneComponent>("SceneComponent");
    SetRootComponent(SceneComponent);
}

void AShapeActor::BeginPlay()
{
    Super::BeginPlay();
}

FBlock AShapeActor::GetBlock(uint32 row, uint32 column)
{
    const int32 index = row * NumberOfColumns + column;

    if (index < BlockArray.Num()) return BlockArray[index];

    return FBlock(0, 0, 0);
}

void AShapeActor::OnConstruction(const FTransform& Transform)
{
    // delete old components
    for (auto component = StaticMeshComponentArray.CreateIterator(); component; component++)
    {
        if (*component != nullptr) (*component)->DestroyComponent();
    }
    StaticMeshComponentArray.Empty();

    // update registration to remove them
    RegisterAllComponents();

    // create new ones
    uint32 index = 1;
    for (int32 row = 0; row < NumberOfRows; row++)
    {
        for (int32 column = 0; column < NumberOfColumns; column++)
        {
            UStaticMeshComponent* newComponent = NewObject<UStaticMeshComponent>(this, FName(*FString::Printf(TEXT("Block%d"), index++)));
            if (newComponent != nullptr)
            {
                StaticMeshComponentArray.Add(newComponent);
                newComponent->CreationMethod = EComponentCreationMethod::UserConstructionScript;
                newComponent->SetRelativeLocation(FVector(column * STACK_BLOCK_WIDTH, 0.0f, row * STACK_BLOCK_WIDTH * -1.0f));
                newComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
                newComponent->RegisterComponent();

                const FBlock block = GetBlock(row, column);
                if (block.MeshIndex > 0 && block.MeshIndex <= StaticMeshArray.Num())
                {
                    newComponent->SetStaticMesh(StaticMeshArray[block.MeshIndex - 1]);
                    if (block.MaterialIndex < MaterialArray.Num())
                    {
                        newComponent->SetMaterial(0, MaterialArray[block.MaterialIndex]);
                    }
                }
                newComponent->SetRelativeRotation(FRotator(block.RotationMult90Deg * 90.0f, 0.0f, 0.0f));
            }
        }
    }
}

void AShapeActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}





I would probably hold the data that you need to access in several places inside a single GameState class.

Got ya. In that case, you simply make the arrays in the game state C++ file, create a BP extension of it so that you can specify the array and the mesh/materials within it, and then make sure to set it in your game mode. That does work.

However, the game state only exists once on the game is running. So you can’t see anything in the editor. I’m not sure if there’s a way to create them in C++ so that you can see them in the editor similar to BP - any ideas on that?



#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "TetrisGameStateBase.generated.h"
/** Stores the array of static mesh and materials to be used to construct the
* game board and pawn.
*/
UCLASS()
class TETRIS_API ATetrisGameStateBase : public AGameStateBase
{
    GENERATED_BODY()
public:
    // Array of static mesh used to create game board and pawn.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameState")
    TArray<UStaticMesh*> StaticMeshArray;
    // Array of static mesh used to create game board and pawn.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameState")
    TArray<UMaterialInterface*> MaterialArray;
};


ok, I’m not exactly sure what you’re going for here, but it sounds like you might be doing something a little bit similar to the current project that i’m working with. In this project, we have a bunch of scenes that we ultimately want to populate with user-selected objects… however, we need to have something placed in the world to show where various types of objects should be created at. We also want to be able to start a scene with a specific set of objects.

So, what we do, is we create the objects that we want in the world, give them a “default” tag name. This allows us to put together a scene that looks like what we want, and has an initial representation of the objects. When the game starts up, gamestate is spawned, and gamestate notes all the objects with the “default” tag, records various information about them into some UStructs, and then destroys the originals (which we will replace with whatever the user says to, or a saved state from disk, or just spawned copies of the defaults)

Does that sound like it would fit what you’re trying to do?

In this case in particular, because it’s a Tetris game, I would create a UObject class and set it as “Game Singleton Class” on project settings to store all tetris pieces.

Then each actor on spawn would borrow a mesh from that global list of meshes.

Eblade - good ideas!

Bruno - I’m not sure how you do that. Where in the Project Settings do you create a singleton class? I guess you mean a Game Instance?

No, the Singleton class is GameInstance’s big sister.
Almost nobody use it nowadays, but it’s still useful.