2025-03-22 17:04:17

이제 함정이 설치되는 통로를 만들어보자

우선 액터를 하나 만들어준다.

//액터 헤더
class USkyLightComponent;

UCLASS()
class TALES_API ADungeonPath : public AActor
{
	GENERATED_BODY()
	
public:	
	ADungeonPath();
	void InitWallMesh();
	virtual void Tick(float DeltaTime) override;
protected:

	virtual void BeginPlay() override;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	TArray<UStaticMeshComponent*> WallMesh;
	UPROPERTY(VisibleAnywhere)
	USkyLightComponent* SkyLight;
private:

};
// 액터 cpp
ADungeonPath::ADungeonPath()
{
	PrimaryActorTick.bCanEverTick = true;

	InitWallMesh();

}

void ADungeonPath::InitWallMesh()
{
	WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("FloorMeshComponent")));
	WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LeftMeshComponent")));
	WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RightrMeshComponent")));
	WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ForwardMeshComponent")));
	WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BackWardMeshComponent")));
	WallMesh.Add(CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CeilMeshComponent")));

	RootComponent = WallMesh[0];
	for (int i = 1; i < WallMesh.Num(); i++)
	{
		WallMesh[i]->SetupAttachment(RootComponent);
	}
	SkyLight = CreateDefaultSubobject<USkyLightComponent>(TEXT("SkyLightComponent"));
	SkyLight->SetupAttachment(RootComponent);
}

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

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

}

SkyLight를 넣어서 밝기를 조절해줬다.

(완성후 뺄 예정)

일단 하드코딩하기전에 staticmesh를 배치해서 원하는대로 되는지 확인해보자

 

 

 

됐다.

 

이제 스태틱메시와 마테리얼을 제외하고 좌표와 크기같은건 전부 cpp에서 작업해주자.

왜 이렇게 작업을 하냐면

바닥을 이렇게 격자공간으로 나눠주고

해당 공간마다 함정이 배치될지 말지를 랜덤하게 정해주고 싶기때문이다.

(벽에는 안만들거임)

 

이렇게 하려고 좀 봐봤는데

 

나는 루트컴포넌트의 스케일을 10,10,0.5로 했었는데

이게 다른 자식 요소에도 전부 적용이 된다.

 

따라서 차라리 BeginPlay같은곳에서 액터의 x,y크기를 구한다음 해당 크기에 맞춰서 활성화 시키는 방식이 더 올바른듯하다.

 

https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/GameFramework/AActor/GetActorBounds?application_version=5.3

 

그래서 찾은 getactorbounds

참고로

BoxExtent에서 절반크기를 주기때문에 *2를 해야 진짜 크기를 알수있다.

 

되긴했는데 뭔가 이상하다.

높이도 안맞고 액터 자체의 크기를 가져왔기때문에 바닥보다 살짝 넓은 범위까지 쓰려한다.

 

그래서 좀 더 찾아보니

https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/Kismet/UKismetSystemLibrary/GetComponentBounds

 

컴포넌트의 크기를 가져오는 함수를 찾았다.

근데 원하는대로 동작을 안해서 더 찾아보니

WallMesh[0]->Bounds.BoxExtent;

이렇게 하면 된다는걸 알았다.

 

void ADungeonPath::SpawnTraps()
{
    // 액터의 바운드를 구합니다.
    FVector Origin, BoxExtent;

    BoxExtent=WallMesh[0]->Bounds.BoxExtent;
    Origin = WallMesh[0]->Bounds.Origin;
    // 바운드 최소/최대 좌표 계산 (가정: 액터가 월드 축에 맞게 정렬되어 있다고 가정)
    FVector Min = Origin - BoxExtent;
    // (여기서는 Min과 Origin의 Z값을 바닥 높이로 사용할 수 있습니다.)

    // Trap 액터의 크기 (사전에 정의된 값 또는 다른 방법으로 얻은 값)
    const float TrapWidth = 250.f; // 예시 값
    const float TrapDepth = 250.f; // 예시 값

    // 통로(바닥)의 전체 크기 (X, Y)
    float TotalWidth = BoxExtent.X * 2.0f;
    float TotalDepth = BoxExtent.Y * 2.0f;

    // 완전히 맞아떨어지는 셀의 개수 (남는 영역은 무시)
    int32 NumCellsX = FMath::FloorToInt(TotalWidth / TrapWidth);
    int32 NumCellsY = FMath::FloorToInt(TotalDepth / TrapDepth);

    // 배치 확률 (원하는 확률로 설정, 예: 50% 확률)
    const float SpawnProbability = 0.5f;

    // Trap 액터 클래스들을 변수로 보관 (블루프린트에서 설정하거나 코드에서 초기화)
    // 예: TrapActorTypeA, TrapActorTypeB
    for (int32 i = 0; i < NumCellsX; ++i)
    {
        for (int32 j = 0; j < NumCellsY; ++j)
        {
            // 각 셀의 중심 위치 계산
            float CellCenterX = Min.X + TrapWidth * (i + 0.5f);
            float CellCenterY = Min.Y + TrapDepth * (j + 0.5f);
            // 바닥에 배치하므로 Z는 바닥 높이(예: Origin.Z)로 설정
            FVector SpawnLocation(CellCenterX, CellCenterY, Origin.Z);

            // 랜덤으로 이 셀에 Trap을 배치할지 결정
            if (FMath::FRand() < SpawnProbability)
            {
                if (TrapActorA && TrapActorB)
                {
                    // 랜덤으로 두 트랩 중 하나 선택
                    TSubclassOf<ATrap> ChosenTrapClass = FMath::RandBool() ? TrapActorA->GetClass() : TrapActorB->GetClass();

                    // 원하는 위치와 회전값으로 스폰 (SpawnLocation은 계산된 위치)
                    FRotator SpawnRotation = FRotator::ZeroRotator;
                    GetWorld()->SpawnActor<ATrap>(ChosenTrapClass, SpawnLocation, SpawnRotation);
                }
                
            }
        }
    }
}

그래서 이렇게 작성하고 실행했는데

 

발판이 안나와서 한참을 헤맸다.

근데 높이가 딱맞게 설정되어버려서

 

이렇게 바닥 블럭안에 있는 상태였다.

 

그래서 스폰할 Z위치를 살짝 수정했다.

FVector SpawnLocation(CellCenterX, CellCenterY, Origin.Z+BoxExtent.Z);

이렇게 수정했다.

 

바닥과 딱붙어서 반짝거린다. 조금만 위로 올려주면

 

 

완성이다.

 

이제 확률만 조금 조정해주자.

void ADungeonPath::SpawnTraps()
{
    // 액터의 바운드를 구합니다.
    FVector Origin, BoxExtent;

    BoxExtent=WallMesh[0]->Bounds.BoxExtent;
    Origin = WallMesh[0]->Bounds.Origin;
    // 바운드 최소/최대 좌표 계산 (가정: 액터가 월드 축에 맞게 정렬되어 있다고 가정)
    FVector Min = Origin - BoxExtent;
    // (여기서는 Min과 Origin의 Z값을 바닥 높이로 사용할 수 있습니다.)

    // Trap 액터의 크기 (사전에 정의된 값 또는 다른 방법으로 얻은 값)
    const float TrapWidth = 250.f; // 예시 값
    const float TrapDepth = 250.f; // 예시 값

    // 통로(바닥)의 전체 크기 (X, Y)
    float TotalWidth = BoxExtent.X * 2.0f;
    float TotalDepth = BoxExtent.Y * 2.0f;

    // 완전히 맞아떨어지는 셀의 개수 (남는 영역은 무시)
    int32 NumCellsX = FMath::FloorToInt(TotalWidth / TrapWidth);
    int32 NumCellsY = FMath::FloorToInt(TotalDepth / TrapDepth);

    // 배치 확률 (원하는 확률로 설정, 예: 50% 확률)
    const float SpawnProbability = 0.1f;

    // Trap 액터 클래스들을 변수로 보관 (블루프린트에서 설정하거나 코드에서 초기화)
    // 예: TrapActorTypeA, TrapActorTypeB
    for (int32 i = 0; i < NumCellsX; ++i)
    {
        for (int32 j = 0; j < NumCellsY; ++j)
        {
            // 각 셀의 중심 위치 계산
            float CellCenterX = Min.X + TrapWidth * (i + 0.5f);
            float CellCenterY = Min.Y + TrapDepth * (j + 0.5f);
            // 바닥에 배치하므로 Z는 바닥 높이(예: Origin.Z)로 설정
            FVector SpawnLocation(CellCenterX, CellCenterY, Origin.Z+BoxExtent.Z+1.f);

            // 랜덤으로 이 셀에 Trap을 배치할지 결정
            if (FMath::FRand() < SpawnProbability)
            {
                if (TrapActorA && TrapActorB)
                {
                    // 랜덤으로 두 트랩 중 하나 선택
                    TSubclassOf<ATrap> ChosenTrapClass = FMath::RandBool() ? TrapActorA->GetClass() : TrapActorB->GetClass();

                    // 원하는 위치와 회전값으로 스폰 (SpawnLocation은 계산된 위치)
                    FRotator SpawnRotation = FRotator::ZeroRotator;
                    GetWorld()->SpawnActor<ATrap>(ChosenTrapClass, SpawnLocation, SpawnRotation);
                }
                
            }
        }
    }
}

일단 10%로 작성했는데 이수치는 나중에 다시 조정해주자.

 

결과

 

참고로 함수는 BeginPlay에서 시작해주어야한다.