C++ The mesh created procedurally does not move.

Hi, every devs.
I’m writing a program that generates a mesh in Unreal based on vertex information.
When I press Play button in the editor, the mesh will be created normally.
During the execution process, actors are created in game mode.
The created actors each create a procedural mesh.

The problem is that the created meshes do not move at all even if the Transform value is changed. Since it is a Static Mesh, the situation is the same even if you change it to Movable.

If you look at the editor image, the objects checked with a red circle are the created procedural mesh. And you can tell that it is Static because the color of the Mesh_ label next to it is gold.

I’m a beginner so I don’t know why this happened.
Please let me know.
Thank you for your interest and reading.
I’ve posted the full working code below.

======= MyMeshActor.h ========

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "MyMeshActor.generated.h"

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

	//	프리미티브 큐브 생성
	UPROPERTY()
	UStaticMeshComponent* CubeComponent;
	//	프리미티브 실린더 생성
	UPROPERTY()
	UStaticMeshComponent* CylinderComponent;

	// 필요한 메시 생성 및 설정 함수
	void CreateCylinder(float Width, float Height, float Length, FVector Position, float Rotx, float Roty, float Rotz);
	void CreateCylinder(UStaticMeshComponent* InCubeComponent, float Width, float Height, float Length, FVector Position);
	void CreateCube(float Width, float Height, float Length, FVector Position, float Rotx, float Roty, float Rotz);
	void CreateCube(UStaticMeshComponent* InCubeComponent, float Width, float Height, float Length, FVector Position);
	void CreateMeshByVertex(TArray<FVector> Vertices, TArray<int32> Triangles);

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

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

private:
	UStaticMesh* CubeMesh;
	UStaticMesh* CylinderMesh;
	UStaticMeshComponent* MeshComponent;
	UPROPERTY(VisibleAnywhere, Category = "Mesh")
	UProceduralMeshComponent* ProceduralMesh;
};

========== MyMeshActor.cpp ==========

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyMeshActor.h"
#include "Components/StaticMeshComponent.h"
#include "KismetProceduralMeshLibrary.h"
#include "UObject/ConstructorHelpers.h"

// Sets default values
AMyMeshActor::AMyMeshActor()
{
 	// 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;

    MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
    ProceduralMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("GeneratedMesh"));
    RootComponent = MeshComponent;

    // 메시 로드 및 저장
    static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeMeshFinder(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
    if (CubeMeshFinder.Succeeded())
    {
        CubeMesh = CubeMeshFinder.Object;
    }

    static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderMeshFinder(TEXT("/Game/StarterContent/Shapes/Shape_Cylinder.Shape_Cylinder"));
    if (CylinderMeshFinder.Succeeded())
    {
        CylinderMesh = CylinderMeshFinder.Object;
    }
}

void AMyMeshActor::CreateCube(float Width, float Height, float Length, FVector Position, float RotX, float RotY, float RotZ)
{
    MeshComponent->SetStaticMesh(CubeMesh);
    MeshComponent->SetWorldScale3D(FVector(Width, Height, Length));
    MeshComponent->SetWorldLocation(Position);

    //  Y축 회전
    FRotator RotationHor(0.0f + RotX, 90.0f + RotZ, -90.0f + RotY); // Pitch(X축), Yaw(Z축), Roll(Y축)
    MeshComponent->SetRelativeRotation(RotationHor);
}

void AMyMeshActor::CreateCylinder(float Width, float Height, float Length, FVector Position, float RotX, float RotY, float RotZ)
{
    MeshComponent->SetStaticMesh(CylinderMesh);
    MeshComponent->SetWorldScale3D(FVector(Width, Height, Length));
    MeshComponent->SetWorldLocation(Position);

    //  Y축 회전
    FRotator RotationHor(0.0f + RotX, 90.0f + RotZ, -90.0f + RotY); // Pitch(X축), Yaw(Z축), Roll(Y축)
    MeshComponent->SetRelativeRotation(RotationHor);
}

void AMyMeshActor::CreateMeshByVertex(TArray<FVector> Vertices, TArray<int32> Triangles)
{
    TArray<FVector> Normals;
    TArray<FVector2D> UV0;
    TArray<FColor> VertexColors; // FColor 대신 FLinearColor 사용
    TArray<FProcMeshTangent> Tangents;

    // 메쉬 생성
    ProceduralMesh->CreateMeshSection(0, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, true);
    // 이동,회전 허용
    ProceduralMesh->SetMobility(EComponentMobility::Movable);

    // 법선 및 탄젠트 계산
    UKismetProceduralMeshLibrary::CalculateTangentsForMesh(Vertices, Triangles, UV0, Normals, Tangents);
}

// Called when the game starts or when spawned
void AMyMeshActor::BeginPlay()
{
	Super::BeginPlay();
	
    // 현재 액터의 위치를 가져옴
    FVector CurrentLocation = GetActorLocation();

    // 로그로 현재 위치를 출력
    UE_LOG(LogTemp, Warning, TEXT("Actor's Initial Location: X: %f, Y: %f, Z: %f"),
        CurrentLocation.X, CurrentLocation.Y, CurrentLocation.Z);
}

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

    //// 이동 간격(초) 및 이동할 거리를 정의
    //const float MoveInterval = 2.0f; // 2초마다
    //const float MoveDistance = 10.0f; // X축으로 10씩 이동

    //// 현재까지 누적된 시간을 저장할 static 변수
    //static float AccumulatedTime = 0.0f;

    //// 누적 시간 업데이트
    //AccumulatedTime += DeltaTime;

    //// 누적 시간이 이동 간격을 초과했는지 확인
    //if (AccumulatedTime >= MoveInterval)
    //{
    //    // 현재 위치 가져오기
    //    FVector CurrentLocation = GetActorLocation();

    //    // X축으로 MoveDistance만큼 이동
    //    FVector NewLocation = CurrentLocation + FVector(MoveDistance, 0.0f, 0.0f);

    //    // 액터의 위치를 새 위치로 설정
    //    SetActorLocation(NewLocation, false, nullptr, ETeleportType::TeleportPhysics);

    //    // 누적 시간을 0으로 리셋 (또는 MoveInterval만큼 감소시키기)
    //    AccumulatedTime -= MoveInterval;

    //    // 로그로 새 위치를 출력
    //    UE_LOG(LogTemp, Warning, TEXT("Actor's New Location: X: %f, Y: %f, Z: %f"),
    //        NewLocation.X, NewLocation.Y, NewLocation.Z);
    //}
}

//void AMyMeshActor::CreateCube(float XSize, float YSize, float ZSize, FVector Position)
//{
//    // Load the cube mesh. Replace 'StaticMesh'/.../Cube.Cube' with the correct path to the cube mesh asset in your project
//    static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeMesh(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
//    if (CubeMesh.Succeeded())
//    {
//        CubeComponent->SetStaticMesh(CubeMesh.Object);
//        CubeComponent->SetWorldScale3D(FVector(XSize, YSize, ZSize));
//        CubeComponent->SetWorldLocation(Position);
//    }
//}
//
//void AMyMeshActor::CreateCube(UStaticMeshComponent* InCubeComponent, float XSize, float YSize, float ZSize, FVector Position)
//{
//    // 여기서 CubeMesh는 프로젝트 내에 있는 특정 Cube 메시를 찾도록 설정되어 있음
//    static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeMesh(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
//
//    if (CubeMesh.Succeeded())
//    {
//        // 새 컴포넌트에 메시 설정
//        InCubeComponent->SetStaticMesh(CubeMesh.Object);
//
//        // 새 컴포넌트의 크기 설정
//        InCubeComponent->SetWorldScale3D(FVector(XSize, YSize, ZSize));
//
//        // 위치
//        InCubeComponent->SetWorldLocation(Position);
//    }
//}
//
//void AMyMeshActor::CreateCylinder(float XSize, float YSize, float ZSize, FVector Position)
//{
//    // 여기서 CubeMesh는 프로젝트 내에 있는 특정 Cube 메시를 찾도록 설정되어 있음
//    static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderMesh(TEXT("/Game/StarterContent/Shapes/Shape_Cylinder.Shape_Cylinder"));
//
//    if (CylinderMesh.Succeeded())
//    {
//        // 새 컴포넌트에 메시 설정
//        CylinderComponent->SetStaticMesh(CylinderMesh.Object);
//
//        // 새 컴포넌트의 크기 설정
//        CylinderComponent->SetWorldScale3D(FVector(XSize, YSize, ZSize));
//
//        //  Y축 회전
//        FRotator Rotation(0.0f, 0.0f, 90.0f); // Pitch(X축), Yaw(Z축), Roll(Y축)
//        CylinderComponent->SetRelativeRotation(Rotation);
//
//        // 위치
//        CylinderComponent->SetWorldLocation(Position);
//    }
//}
//void AMyMeshActor::CreateCylinder(UStaticMeshComponent* InCylinderComponent, float XSize, float YSize, float ZSize, FVector Position)
//{
//    // 여기서 CubeMesh는 프로젝트 내에 있는 특정 Cube 메시를 찾도록 설정되어 있음
//    static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderMesh(TEXT("/Game/StarterContent/Shapes/Shape_Cylinder.Shape_Cylinder"));
//
//    if (CylinderMesh.Succeeded())
//    {
//        // 새 컴포넌트에 메시 설정
//        InCylinderComponent->SetStaticMesh(CylinderMesh.Object);
//
//        // 새 컴포넌트의 크기 설정
//        InCylinderComponent->SetWorldScale3D(FVector(XSize, YSize, ZSize));
//
//        //  Y축 회전
//        FRotator Rotation(0.0f, 0.0f, 90.0f); // Pitch(X축), Yaw(Z축), Roll(Y축)
//        InCylinderComponent->SetRelativeRotation(Rotation);
//
//        // 위치
//        InCylinderComponent->SetWorldLocation(Position);
//    }
//}

========== MyGameMode.h ==========

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyPlayerController.h"
#include "StackMeshVertexInfo.h"
#include "MyGameMode.generated.h"

/**
 * 
 */
UCLASS()
class SQLITETEST_API AMyGameMode : public AGameModeBase
{
	GENERATED_BODY()
	
public:
	AMyGameMode();
	virtual void StartPlay() override;

private:
	TArray<StackMeshVertexInfo> ParseMEPMeshes(const FString& FilePath);
};

========== MyGameMode.cpp ==========

#include "MyGameMode.h"
#include "Engine/World.h"
#include "MyActor.h"
#include "MyMeshActor.h"

AMyGameMode::AMyGameMode()
{
	PlayerControllerClass = AMyPlayerController::StaticClass();
}

void AMyGameMode::StartPlay()
{
	Super::StartPlay();

	FString FilePath = TEXT("C:\\sqlite\\rvtobjs.txt"); // 적절한 파일 경로
	TArray<StackMeshVertexInfo> LoadedData = ParseMEPMeshes(FilePath);

	if (GetWorld())
	{
        int idx = 0;
        for (const StackMeshVertexInfo& MeshInfo : LoadedData)
        {
            // 위치 및 회전값 설정
            FVector Location(0.0f, 0.0f, 0.0f); // 예제에서는 임시값 사용, 실제 사용시 MeshInfo에서 적절한 값을 가져와야 함
            FRotator Rotation(0.0f, 0.0f, 0.0f);

            // 스폰 파라미터 설정
            FActorSpawnParameters SpawnParams;
            SpawnParams.Owner = this;

            // AMyMeshActor 스폰
            AMyMeshActor* SpawnedMeshActor = GetWorld()->SpawnActor<AMyMeshActor>(AMyMeshActor::StaticClass(), Location, Rotation, SpawnParams);

            if (SpawnedMeshActor != nullptr)
            {
                FString LabelName = FString::Printf(TEXT("MESH_%d"), idx++);
                SpawnedMeshActor->SetActorLabel(LabelName);
                // 스폰된 메쉬 액터에 대한 추가 설정 (예: 메쉬 데이터 설정)
                SpawnedMeshActor->CreateMeshByVertex(MeshInfo.Vertex, MeshInfo.Indices);
                ////  정적으로 생성 된 메쉬를 동적 메쉬로 변환
                //UStaticMeshComponent* MeshComponent = SpawnedMeshActor->FindComponentByClass<UStaticMeshComponent>();
                //if (MeshComponent)
                //{
                //    // 메쉬 컴포넌트를 동적으로 설정
                //    MeshComponent->SetMobility(EComponentMobility::Movable);
                //    UE_LOG(LogTemp, Warning, TEXT("Mobility set to Movable for: %s"), *LabelName);
                //}
                //  새로운 위치로 이동
                FVector NewLocation = FVector(100.0f, 100.0f, 100.0f);
                bool bSuccess = SpawnedMeshActor->SetActorLocation(Location);
                UE_LOG(LogTemp, Warning, TEXT("Actor moved: %s"), bSuccess ? TEXT("True") : TEXT("False"));
                idx++;
            }
        }
	}
}

This seems like the issue that might be related to how you’re updating the transform of the procedural meshes during runtime. Here are some steps you can take to troubleshoot and resolve the problem:

  1. Check if Transform is Actually Changing: Ensure that the transform values (location, rotation, scale) of the procedural meshes are being updated correctly during runtime. Print or log the transform values to confirm that they are changing as expected.
  2. Ensure Meshes are Set to Movable: While you mentioned changing to Movable, ensure that you are setting the mobility of the procedural mesh components to “Movable” in the code. Here’s an example of how you can set the mobility:

cppCopy code

ProceduralMeshComponent->SetMobility(EComponentMobility::Movable);
  1. Update During Tick or Animation Blueprint: If you are updating the transform values in the actor’s Tick function or through an Animation Blueprint, make sure that you are doing it correctly. Ensure that the changes are happening continuously and not being overridden elsewhere.
  2. Collision and Physics Settings: Check the collision and physics settings of your procedural mesh components. If you are using physics simulation, make sure the mesh has a proper collision setup and the physics simulation is not causing the objects to be stuck.
  3. Check for Potential Parent/Child Relationship Issues: If your procedural meshes are attached to other actors or components, ensure that the parent-child relationship is not causing unexpected behavior. If the parent is static, it might affect the movement of the child.
  4. Post-Initialization Transform: Confirm that you are not setting the transform only during the actor’s construction or initialization. Make sure that transform updates are ongoing during the game loop.
  5. Verify Engine Version Compatibility: Ensure that your Unreal Engine version is compatible with the code and plugins you are using. Sometimes, updates to the engine might introduce changes that affect behavior.
  6. Debugging Tools: Utilize Unreal Engine’s debugging tools. You can use breakpoints, print statements, or the debugger to step through your code and identify where the issue might be occurring.

By carefully checking these aspects, you should be able to pinpoint the problem and make the necessary adjustments to ensure your procedural meshes respond to the transform changes during runtime.

Try this

  • MyActor.h
UPROPERTY(VisibleAnywhere, Category = "Mesh")
USceneComponent* MyActorRoot;


UPROPERTY(VisibleAnywhere, Category = "Mesh")
UStaticMeshComponent* MyActorMesh;
  • MyActor.cpp
MyActor::MyActor()
{
  MyActorRoot= CreateDefaultSubobject<USceneComponent>(TEXT("MyActorMesh"));
  check(MyActorRoot)
  SetRootComponent(MyActorRoot);

  MyActorMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyActorMesh "));
  check(MyActorMesh)
  MyActorMesh->SetupAttachment(MyActorRoot);
}