LeeTaes 공부노트

[UE Team Project/Issac 3D] 3. 랜덤 맵 생성 - 2 (완) 본문

프로젝트/Isaac 3D

[UE Team Project/Issac 3D] 3. 랜덤 맵 생성 - 2 (완)

리태s 2024. 9. 6. 17:39
728x90
반응형

개요

이전 포스팅에서는 랜덤한 맵의 구조를 제작하는 방법에 대해 정리했습니다. 이번에는 정리된 구조를 토대로 스폰할 방의 컨셉과 구현 방법에 대해 정리해보도록 하겠습니다.

Room 구현 아이디어

기본적으로 하나의 Room 클래스를 만들어 블루프린트 클래스를 생성하여 방을 구현할 예정입니다. 이렇게 하면 전체 게임의 흐름을 관리하고, 몬스터 스폰 정보와 같은 중복되는 작업을 효율적으로 처리할 수 있습니다.

RoomBase 구현

방은 시작과 동시에 모든 문이 열려야 하며, 플레이 상황에 따라 몬스터를 스폰하고 방 문을 다시 여는 등의 로직을 수행해야 합니다. 이러한 상태를 명확하게 구분하기 위해 방 별로 Enum class를 새로 제작하여 상태에 맞는 행동을 수행하게 제작하였습니다.

 

또한 방 별로 스폰 가능한 몬스터와 수, Transform을 다르게 해줄 수 있도록 FMonsterSpawnInfos 구조체를 생성해 블루프린트에서 세팅할 수 있도록 구현하게 되었습니다.

 

Room 배치 결과

 

위 사진과 같이 Room Base를 상속받은 Room 블루프린트들을 제작하여 Dungeon Generator Component에 클래스 정보를 저장하고, 이전에 생성했던 방 정보를 토대로 모든 방들을 스폰하게 됩니다.

 

언제나 왼쪽 상단이 시작지점(0번 방), 오른쪽 하단(11번 방)이 보스 방으로 설정되어 있습니다.


정리

랜덤 맵 생성은 Dungeon Generator Component에서 처리하며, 동적으로 생성할 방의 크기(가로, 세로)와 사이즈(N)를 수정 가능하도록 구현하였습니다.

 

또한 시작 방에 사용될 Room Class와 보스 방에 사용될 Room Class, 랜덤으로 뽑힐 Room Class를 설정 가능하도록 구현이 완료되었습니다.

DungeonGeneratorComponent - 에디터

DungeonGenerator Component Class 코드

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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "RoomInfo.h"
#include "DungeonGeneratorComponent.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SHOOTINGGAME3D_API UDungeonGeneratorComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	UDungeonGeneratorComponent();

protected:
	virtual void BeginPlay() override;

public:	
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

private:
	void GenerateMaze();
	void SpawnMaze();
	EOpenDir CheckDir(int32 CurrentIdx);

// Member Variable
private:
	UPROPERTY(EditAnywhere, Category = "Dungeon")
	int32 Width;

	UPROPERTY(EditAnywhere, Category = "Dungeon")
	int32 Height;

	UPROPERTY(EditAnywhere, Category = "Dungeon")
	float RoomSize;

	UPROPERTY(VisibleAnywhere, Category = "Dungeon")
	TArray<FRoomInfo> DungeonMaps;

	UPROPERTY(EditAnywhere, Category = "Dungeon")
	TArray<TSubclassOf<class ARoomBase>> RoomClassArray;

	UPROPERTY(EditAnywhere, Category = "Dungeon")
	TSubclassOf<class ARoomBase> BossRoomClass;

	UPROPERTY(EditAnywhere, Category = "Dungeon")
	TSubclassOf<class ARoomBase> StartRoomClass;
};

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


#include "DungeonGeneratorComponent.h"

#include "RoomBase.h"

UDungeonGeneratorComponent::UDungeonGeneratorComponent()
{
	PrimaryComponentTick.bCanEverTick = true;

	// Default Value Set
	Width = 4;
	Height = 3;
	RoomSize = 2800.0f;
}



void UDungeonGeneratorComponent::BeginPlay()
{
	Super::BeginPlay();

	// Generate Dungeon Map Info
	GenerateMaze();

	SpawnMaze();
}

void UDungeonGeneratorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

}

void UDungeonGeneratorComponent::GenerateMaze()
{
	// 1. Height * Width 만큼 배열 초기화
	//    = Room 숫자 및 열리는 위치 초기화
	for (int32 y = 0; y < Height; y++)
	{
		for (int32 x = 0; x < Width; x++)
		{
			FRoomInfo Info;
			Info.RoomNum = (y * Width) + x;
			Info.OpenDir = static_cast<uint8>(EOpenDir::EOD_NONE);
			Info.bIsVisited = false;

			DungeonMaps.Add(Info);
		}
	}

	// 다음으로 체크 가능한 방 목록
	TArray<int32> TempRooms;

	// 시작 위치(0,0)의 아래, 오른쪽 체크해서 탐색 가능한 방 배열에 추가하기
	DungeonMaps[0].bIsVisited = true;
	TempRooms.Add(Width);
	TempRooms.Add(1);

	int32 CheckCnt = 1;

	// 2. 모든 방이 체크가 될 때까지 반복해서 미로 생성하기
	while (CheckCnt != DungeonMaps.Num())
	{
		// 랜덤한 방 뽑기
		int32 RandIdx = TempRooms[FMath::RandRange(0, TempRooms.Num() - 1)];

		// 해당 방 방문처리
		DungeonMaps[RandIdx].bIsVisited = true;
		// 카운트 증가
		CheckCnt++;
		// 해당 방 임시 방 목록에서 제거
		TempRooms.Remove(RandIdx);

		// 해당 방으로부터 상하좌우 체크하여 연결해줄 방을 구해줍니다.
		EOpenDir ConnectDir = CheckDir(RandIdx);

		// 만약 연결할 수 있는 방이 존재한다면?
		if (ConnectDir != EOpenDir::EOD_NONE)
		{
			// 해당 방향과 이어주도록 합니다.
			// * 현재 방
			DungeonMaps[RandIdx].OpenDir |= static_cast<uint8>(ConnectDir);

			// * 연결한 방
			switch (ConnectDir)
			{
			case EOpenDir::EOD_LEFT:
				DungeonMaps[RandIdx - 1].OpenDir |= static_cast<uint8>(EOpenDir::EOD_RIGHT);
				break;

			case EOpenDir::EOD_RIGHT:
				DungeonMaps[RandIdx + 1].OpenDir |= static_cast<uint8>(EOpenDir::EOD_LEFT);
				break;

			case EOpenDir::EOD_UP:
				DungeonMaps[RandIdx - Width].OpenDir |= static_cast<uint8>(EOpenDir::EOD_DOWN);
				break;

			case EOpenDir::EOD_DOWN:
				DungeonMaps[RandIdx + Width].OpenDir |= static_cast<uint8>(EOpenDir::EOD_UP);
				break;
			}
		}

		// 해당 방의 상하좌우를 체크하여 방문되지 않은 방들을 배열에 추가합니다.
		if (DungeonMaps.IsValidIndex(RandIdx - Width) && DungeonMaps[RandIdx - Width].bIsVisited == false) TempRooms.Add(RandIdx - Width);
		if (DungeonMaps.IsValidIndex(RandIdx + Width) && DungeonMaps[RandIdx + Width].bIsVisited == false) TempRooms.Add(RandIdx + Width);
		if ((RandIdx / Width) == ((RandIdx + 1) / Width) && DungeonMaps.IsValidIndex(RandIdx + 1) && DungeonMaps[RandIdx + 1].bIsVisited == false) TempRooms.Add(RandIdx + 1);
		if ((RandIdx / Width) == ((RandIdx - 1) / Width) && DungeonMaps.IsValidIndex(RandIdx - 1) && DungeonMaps[RandIdx - 1].bIsVisited == false) TempRooms.Add(RandIdx - 1);
	}
}

void UDungeonGeneratorComponent::SpawnMaze()
{
	// 한 방의 크기 4000 * 4000

	// 모든 방의 정보를 순회하며 알맞은 위치에 스폰합니다.
	for (int32 y = 0; y < Height; y++)
	{
		for (int32 x = 0; x < Width; x++)
		{
			// 현재 인덱스가 올바른지 확인
			if (DungeonMaps.IsValidIndex((y * Width) + x))
			{
				// 스폰 위치 구하기
				FTransform SpawnTransform;

				SpawnTransform.SetLocation(FVector(-RoomSize * y, RoomSize * x, 0.0f));

				// 스폰할 클래스 랜덤으로 구하기
				int32 RandIdx = FMath::RandRange(0, RoomClassArray.Num() - 1);

				// 해당 위치에 스폰하기

				ARoomBase* Room;
				// * 시작 방
				if ((y * Width) + x == 0)
				{
					Room = GetWorld()->SpawnActorDeferred<ARoomBase>(StartRoomClass, SpawnTransform);
				}
				// * 보스방
				else if ((y * Width) + x == Width * Height - 1)
				{
					Room = GetWorld()->SpawnActorDeferred<ARoomBase>(BossRoomClass, SpawnTransform);
				}
				// * 일반 방
				else
				{
					Room = GetWorld()->SpawnActorDeferred<ARoomBase>(RoomClassArray[RandIdx], SpawnTransform);
				}
				
				if (Room)
				{
					Room->InitRoom(DungeonMaps[(y * Width) + x].OpenDir, DungeonMaps[(y * Width) + x].RoomNum);
					Room->FinishSpawning(SpawnTransform);
				}	
			}
		}
	}
}

EOpenDir UDungeonGeneratorComponent::CheckDir(int32 CurrentIdx)
{
	// 여기서는 현재 인덱스에서 인접한 이미 방문한 방 하나를 랜덤으로 찾아서 반환해야 합니다.
	// * 상하좌우 체크를 진행합니다.
	TArray<EOpenDir> Indexs;

	// 위쪽
	if (DungeonMaps.IsValidIndex(CurrentIdx - Width) && DungeonMaps[CurrentIdx - Width].bIsVisited)
	{ 
		Indexs.Add(EOpenDir::EOD_UP);
	}
	// 아래쪽
	if (DungeonMaps.IsValidIndex(CurrentIdx + Width) && DungeonMaps[CurrentIdx + Width].bIsVisited)
	{
		Indexs.Add(EOpenDir::EOD_DOWN);
	}
	// 오른쪽
	if ((CurrentIdx / Width) == ((CurrentIdx + 1) / Width) && DungeonMaps.IsValidIndex(CurrentIdx + 1) && DungeonMaps[CurrentIdx + 1].bIsVisited)
	{
		Indexs.Add(EOpenDir::EOD_RIGHT);
	}
	// 왼쪽
	if ((CurrentIdx / Width) == ((CurrentIdx - 1) / Width) && DungeonMaps.IsValidIndex(CurrentIdx - 1) && DungeonMaps[CurrentIdx - 1].bIsVisited)
	{
		Indexs.Add(EOpenDir::EOD_LEFT);
	}

	// 만약 찾은 인덱스가 존재한다면?
	if (Indexs.Num())
	{
		// 랜덤으로 값을 추출합니다.
		return Indexs[FMath::RandRange(0, Indexs.Num() - 1)];
	}
	else
	{
		// 모두 연결이 불가능한 경우 -1을 반환합니다.
		return EOpenDir::EOD_NONE;
	}
}

Room Base Class 코드

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "RoomInterface.h"
#include "RoomEnums.h"
#include "RoomBase.generated.h"

UENUM(BlueprintType)
enum class ERoomState : uint8
{
	RS_None,	// None
	RS_Ready,	// Init
	RS_Start,	// Player Enter
	RS_End,		// Room Clear
};

USTRUCT(BlueprintType)
struct FMonsterSpawnInfo
{
	GENERATED_BODY()
public:	
	UPROPERTY(EditAnywhere)
	TSubclassOf<class AEnemy> MonsterClass;

	UPROPERTY(EditAnywhere)
	FVector SpawnRelativeLocation;

	UPROPERTY(EditAnywhere)
	FRotator SpawnRelativeRotation;
};

UCLASS()
class SHOOTINGGAME3D_API ARoomBase : public AActor, public IRoomInterface
{
	GENERATED_BODY()
	
public:
	ARoomBase();

protected:
	virtual void BeginPlay() override;

public:
	virtual void Tick(float DeltaTime) override;

// Init
public:
	void InitRoom(uint8 OpenDirFlag, int32 RoomCount);

// Create
private:
	void CreateMeshComponent();
	void InitRoomTransform();
	void SetDoor(EOpenDir OpenDir);

// Collision
protected:
	UFUNCTION()
	void OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

// RoomState
protected:
	// Current State
	ERoomState CurrentRoomState;

	// Change Room State
	void SetState(ERoomState InState);

	// Init Room (Start : Open Door)
	void ReadyRoom();
	// Start Room (Player Enter : Close Door, Spawn Monster)
	virtual void StartRoom();
	// End Room (Room Clear : Open Door)
	virtual void EndRoom();

// Game Logic
private:
	void OpenDoor(bool bIsOpen);
	void SpawnMonster();

public:
	virtual void DecreaseCount() override;
	FORCEINLINE virtual int32 GetRoomNum() override { return RoomNum; }

// Components
private:
	// Collision
	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UBoxComponent> BoxComp;

	// Mesh
	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> FloorMesh;

	UPROPERTY(VisibleAnywhere, Category = "Room")
	TArray<TObjectPtr<class UStaticMeshComponent>> WallMeshes;

	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> Left_Door;
	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> Left_Wall;

	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> Right_Door;
	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> Right_Wall;

	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> Up_Door;
	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> Up_Wall;

	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> Down_Door;
	UPROPERTY(VisibleAnywhere, Category = "Room")
	TObjectPtr<class UStaticMeshComponent> Down_Wall;

private:
	UPROPERTY(EditAnywhere, Category = "Room")
	TArray<FMonsterSpawnInfo> MonsterSpawnInfos;

	FTimerHandle SpawnTimerHandle;

	int32 SpawnCount;

	UPROPERTY(VisibleAnywhere)
	int32 RoomNum;

	UPROPERTY(VisibleAnywhere)
	uint8 RoomOpenFlag;
};

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


#include "RoomBase.h"
#include "ShootingGame3D/Player/PlayerCharacter.h"
#include "ShootingGame3D/Enemy/Enemy.h"
#include "ShootingGame3D/Public/ShootingGameModeBase.h"

#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Kismet/GameplayStatics.h"

ARoomBase::ARoomBase()
{
	PrimaryActorTick.bCanEverTick = true;

	// Create Sub Object
	CreateMeshComponent();

	// Set Transform
	InitRoomTransform();

	BoxComp->OnComponentBeginOverlap.AddDynamic(this, &ARoomBase::OnComponentBeginOverlap);

	// Member Init
	CurrentRoomState = ERoomState::RS_None;
	SpawnCount = 0;
}

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

void ARoomBase::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void ARoomBase::InitRoom(uint8 OpenDirFlag, int32 RoomCount)
{
	// 방 정보 저장
	RoomNum = RoomCount;
	RoomOpenFlag = OpenDirFlag;

	// 방 문 초기화
	if (OpenDirFlag & static_cast<uint8>(EOpenDir::EOD_LEFT)) SetDoor(EOpenDir::EOD_LEFT);
	if (OpenDirFlag & static_cast<uint8>(EOpenDir::EOD_RIGHT)) SetDoor(EOpenDir::EOD_RIGHT);
	if (OpenDirFlag & static_cast<uint8>(EOpenDir::EOD_UP)) SetDoor(EOpenDir::EOD_UP);
	if (OpenDirFlag & static_cast<uint8>(EOpenDir::EOD_DOWN)) SetDoor(EOpenDir::EOD_DOWN);

	// 준비 상태로 변경
	SetState(ERoomState::RS_Ready);
}

void ARoomBase::SetDoor(EOpenDir OpenDir)
{
	// 연결된 방들에 따라 문 설정하기
	switch (OpenDir)
	{
	case EOpenDir::EOD_LEFT:
		Left_Door->SetVisibility(true);
		Left_Wall->SetVisibility(false);
		Left_Wall->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		break;

	case EOpenDir::EOD_RIGHT:
		Right_Door->SetVisibility(true);
		Right_Wall->SetVisibility(false);
		Right_Wall->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		break;

	case EOpenDir::EOD_UP:
		Up_Door->SetVisibility(true);
		Up_Wall->SetVisibility(false);
		Up_Wall->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		break;

	case EOpenDir::EOD_DOWN:
		Down_Door->SetVisibility(true);
		Down_Wall->SetVisibility(false);
		Down_Wall->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		break;
	}
}

void ARoomBase::OpenDoor(bool bIsOpen)
{
	if (bIsOpen)
	{
		if (Left_Door->IsVisible())
		{
			FVector NewLocation = Left_Door->GetRelativeLocation() - FVector(0.0f, 0.0f, 1800.0f);
			Left_Door->SetRelativeLocation(NewLocation);
		}

		if (Right_Door->IsVisible())
		{
			FVector NewLocation = Right_Door->GetRelativeLocation() - FVector(0.0f, 0.0f, 1800.0f);
			Right_Door->SetRelativeLocation(NewLocation);
		}

		if (Up_Door->IsVisible())
		{
			FVector NewLocation = Up_Door->GetRelativeLocation() - FVector(0.0f, 0.0f, 1800.0f);
			Up_Door->SetRelativeLocation(NewLocation);
		}

		if (Down_Door->IsVisible())
		{
			FVector NewLocation = Down_Door->GetRelativeLocation() - FVector(0.0f, 0.0f, 1800.0f);
			Down_Door->SetRelativeLocation(NewLocation);
		}
	}
	else
	{
		if (Left_Door->IsVisible())
		{
			FVector NewLocation = Left_Door->GetRelativeLocation() + FVector(0.0f, 0.0f, 1800.0f);
			Left_Door->SetRelativeLocation(NewLocation);
		}

		if (Right_Door->IsVisible())
		{
			FVector NewLocation = Right_Door->GetRelativeLocation() + FVector(0.0f, 0.0f, 1800.0f);
			Right_Door->SetRelativeLocation(NewLocation);
		}

		if (Up_Door->IsVisible())
		{
			FVector NewLocation = Up_Door->GetRelativeLocation() + FVector(0.0f, 0.0f, 1800.0f);
			Up_Door->SetRelativeLocation(NewLocation);
		}

		if (Down_Door->IsVisible())
		{
			FVector NewLocation = Down_Door->GetRelativeLocation() + FVector(0.0f, 0.0f, 1800.0f);
			Down_Door->SetRelativeLocation(NewLocation);
		}
	}
}

void ARoomBase::SpawnMonster()
{
	UWorld* World = GetWorld();
	if (!World) return;

	for (int32 i = 0; i < MonsterSpawnInfos.Num(); i++)
	{
		FVector RelativeSpawnLocation = FVector(MonsterSpawnInfos[i].SpawnRelativeLocation.X * 0.7f, MonsterSpawnInfos[i].SpawnRelativeLocation.Y * 0.7f, MonsterSpawnInfos[i].SpawnRelativeLocation.Z);
		FVector SpawnLocation = RelativeSpawnLocation + GetActorLocation();
		FRotator SpawnRotation = MonsterSpawnInfos[i].SpawnRelativeRotation  + GetActorRotation();

		AActor* Monster = World->SpawnActor<AEnemy>(MonsterSpawnInfos[i].MonsterClass, SpawnLocation, SpawnRotation);
		if (Monster)
		{
			// 몬스터의 카운트를 관리하기 위해 오너를 방으로 설정하기
			Monster->SetOwner(this);
			SpawnCount++;
		}
	}

	if (SpawnCount == 0)
	{
		// 만약 스폰된 몬스터가 없으면 종료 상태로 변경하기
		SetState(ERoomState::RS_End);
	}
}

void ARoomBase::DecreaseCount()
{
	// 몬스터에서 호출해주는 함수 (사망 시)
	SpawnCount--;

	// 스폰 카운트가 0이 되면 종료 상태로 전이
	if (SpawnCount == 0)
	{
		SetState(ERoomState::RS_End);
	}
}

void ARoomBase::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// 플레이어와 방 입장 콜라이더가 충돌한 경우
	if (Cast<APlayerCharacter>(OtherActor))
	{
		// 준비 상태일 때만 시작 상태로 변경
		if (CurrentRoomState == ERoomState::RS_Ready)
		{
			SetState(ERoomState::RS_Start);
		}
		
		AShootingGameModeBase* GM = Cast<AShootingGameModeBase>(UGameplayStatics::GetGameMode(GetWorld()));
		if (GM)
		{
			// 미니맵 세팅
			GM->MiniMapSet(RoomNum, RoomOpenFlag);
		}
	}
}

void ARoomBase::SetState(ERoomState InState)
{
	if (CurrentRoomState == InState) return;

	CurrentRoomState = InState;

	switch (CurrentRoomState)
	{
	case ERoomState::RS_Ready:
		ReadyRoom();
		break;

	case ERoomState::RS_Start:
		StartRoom();
		break;

	case ERoomState::RS_End:
		EndRoom();
		break;
	}
}

void ARoomBase::ReadyRoom()
{
	// 문 열기
	OpenDoor(true);
}

void ARoomBase::StartRoom()
{
	// 문 닫기
	OpenDoor(false);
	// 1초 후 몬스터 스폰
	FTimerDelegate TimerDelegate;
	TimerDelegate.BindUObject(this, &ARoomBase::SpawnMonster);

	SpawnTimerHandle.Invalidate();
	GetWorld()->GetTimerManager().SetTimer(SpawnTimerHandle, TimerDelegate, 2.0f, false);
}

void ARoomBase::EndRoom()
{
	// 문 열기
	OpenDoor(true);
}

void ARoomBase::CreateMeshComponent()
{
	// 초기 세팅
	BoxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComp"));
	SetRootComponent(BoxComp);

	BoxComp->SetBoxExtent(FVector(1900.0f, 1900.0f, 100.0f));
	BoxComp->SetCollisionProfileName(TEXT("Room"));

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

	ConstructorHelpers::FObjectFinder<UStaticMesh>FloorMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/Assets/Dungeon_Bundle/DepthsBelow/Geometry/Floor/SM_FloorTiles02.SM_FloorTiles02'"));
	if (FloorMeshRef.Object)
	{
		FloorMesh->SetStaticMesh(FloorMeshRef.Object);
	}

	ConstructorHelpers::FObjectFinder<UStaticMesh>WallBaseMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/Assets/DungeonTomb/Meshes/Construction/Wall/SM_Wall_Base_01_Flat_01_Straight_600_400_A.SM_Wall_Base_01_Flat_01_Straight_600_400_A'"));
	for (int i = 0; i < 8; i++)
	{
		FName DisplayName = *FString::Printf(TEXT("Wall%d"), i);
		WallMeshes.Add(CreateDefaultSubobject<UStaticMeshComponent>(DisplayName));
		WallMeshes[i]->SetupAttachment(RootComponent);

		if (WallBaseMeshRef.Object)
		{
			WallMeshes[i]->SetStaticMesh(WallBaseMeshRef.Object);
		}
	}

	Left_Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Left_Door"));
	Left_Door->SetupAttachment(RootComponent);
	Right_Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Right_Door"));
	Right_Door->SetupAttachment(RootComponent);
	Up_Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Up_Door"));
	Up_Door->SetupAttachment(RootComponent);
	Down_Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Down_Door"));
	Down_Door->SetupAttachment(RootComponent);

	Left_Wall = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Left_Wall"));
	Left_Wall->SetupAttachment(RootComponent);
	Right_Wall = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Right_Wall"));
	Right_Wall->SetupAttachment(RootComponent);
	Up_Wall = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Up_Wall"));
	Up_Wall->SetupAttachment(RootComponent);
	Down_Wall = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Down_Wall"));
	Down_Wall->SetupAttachment(RootComponent);

	ConstructorHelpers::FObjectFinder<UStaticMesh>HiddenWallMeshRef(TEXT("/Script/Engine.StaticMesh'/Engine/BasicShapes/Cube.Cube'"));
	if (HiddenWallMeshRef.Object)
	{
		Left_Wall->SetStaticMesh(HiddenWallMeshRef.Object);
		Right_Wall->SetStaticMesh(HiddenWallMeshRef.Object);
		Up_Wall->SetStaticMesh(HiddenWallMeshRef.Object);
		Down_Wall->SetStaticMesh(HiddenWallMeshRef.Object);
	}

	ConstructorHelpers::FObjectFinder<UStaticMesh>HiddenDoorMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/Assets/Dungeon_Bundle/DepthsBelow/Geometry/Props/Fence/SM_FenceDoor01.SM_FenceDoor01'"));
	if (HiddenDoorMeshRef.Object)
	{
		Left_Door->SetStaticMesh(HiddenDoorMeshRef.Object);
		Right_Door->SetStaticMesh(HiddenDoorMeshRef.Object);
		Up_Door->SetStaticMesh(HiddenDoorMeshRef.Object);
		Down_Door->SetStaticMesh(HiddenDoorMeshRef.Object);
	}
}

void ARoomBase::InitRoomTransform()
{
	// 초기 위치 세팅
	BoxComp->SetRelativeScale3D(FVector(0.7f, 0.7f, 0.7f));
	FloorMesh->SetRelativeScale3D(FVector(10.0f, 10.0f, 1.0f));

	Left_Wall->SetRelativeLocationAndRotation(FVector(0.0f, -2000.0f, 850.0f), FRotator(0.0f, 90.0f, 0.0f));
	Left_Wall->SetRelativeScale3D(FVector(1.0f, 8.0f, 18.0f));
	Right_Wall->SetRelativeLocationAndRotation(FVector(0.0f, 2000.0f, 850.0f), FRotator(0.0f, -90.0f, 0.0f));
	Right_Wall->SetRelativeScale3D(FVector(1.0f, 8.0f, 18.0f));
	Up_Wall->SetRelativeLocation(FVector(2000.0f, 0.0f, 850.0f));
	Up_Wall->SetRelativeScale3D(FVector(1.0f, 8.0f, 18.0f));
	Down_Wall->SetRelativeLocation(FVector(-2000.0f, 0.0f, 850.0f));
	Down_Wall->SetRelativeScale3D(FVector(1.0f, 8.0f, 18.0f));

	Left_Wall->SetVisibility(true);
	Right_Wall->SetVisibility(true);
	Up_Wall->SetVisibility(true);
	Down_Wall->SetVisibility(true);

	Left_Door->SetRelativeLocation(FVector(-425.0f, -2000.0f, -50.0f));
	Left_Door->SetRelativeScale3D(FVector(5.0f, 1.0f, 5.0f));
	Right_Door->SetRelativeLocationAndRotation(FVector(425.0f, 2000.0f, -50.0f), FRotator(0.0f, 180.0f, 0.0f));
	Right_Door->SetRelativeScale3D(FVector(5.0f, 1.0f, 5.0f));
	Up_Door->SetRelativeLocationAndRotation(FVector(2000.0f, -425.0f, -50.0f), FRotator(0.0f, 90.0f, 0.0f));
	Up_Door->SetRelativeScale3D(FVector(5.0f, 1.0f, 5.0f));
	Down_Door->SetRelativeLocationAndRotation(FVector(-2000.0f, 425.0f, -50.0f), FRotator(0.0f, -90.0f, 0.0f));
	Down_Door->SetRelativeScale3D(FVector(5.0f, 1.0f, 5.0f));

	Left_Door->SetVisibility(false);
	Right_Door->SetVisibility(false);
	Up_Door->SetVisibility(false);
	Down_Door->SetVisibility(false);

	WallMeshes[0]->SetRelativeLocationAndRotation(FVector(2000.0f, 400.0f, -40.0f), FRotator(0.0f, 90.0f, 0.0f));
	WallMeshes[0]->SetRelativeScale3D(FVector(4.0f, 1.0f, 3.0f));
	WallMeshes[1]->SetRelativeLocationAndRotation(FVector(2000.0f, -2000.0f, -40.0f), FRotator(0.0f, 90.0f, 0.0f));
	WallMeshes[1]->SetRelativeScale3D(FVector(4.0f, 1.0f, 3.0f));
	WallMeshes[2]->SetRelativeLocationAndRotation(FVector(2000.0f, 2000.0f, -40.0f), FRotator(0.0f, 180.0f, 0.0f));
	WallMeshes[2]->SetRelativeScale3D(FVector(4.0f, 1.0f, 3.0f));
	WallMeshes[3]->SetRelativeLocationAndRotation(FVector(-400.0f, 2000.0f, -40.0f), FRotator(0.0f, 180.0f, 0.0f));
	WallMeshes[3]->SetRelativeScale3D(FVector(4.0f, 1.0f, 3.0f));
	WallMeshes[4]->SetRelativeLocationAndRotation(FVector(-2000.0f, -400.0f, -40.0f), FRotator(0.0f, -90.0f, 0.0f));
	WallMeshes[4]->SetRelativeScale3D(FVector(4.0f, 1.0f, 3.0f));
	WallMeshes[5]->SetRelativeLocationAndRotation(FVector(-2000.0f, 2000.0f, -40.0f), FRotator(0.0f, -90.0f, 0.0f));
	WallMeshes[5]->SetRelativeScale3D(FVector(4.0f, 1.0f, 3.0f));
	WallMeshes[6]->SetRelativeLocation(FVector(-2000.0f, -2000.0f, -40.0f));
	WallMeshes[6]->SetRelativeScale3D(FVector(4.0f, 1.0f, 3.0f));
	WallMeshes[7]->SetRelativeLocation(FVector(400.0f, -2000.0f, -40.0f));
	WallMeshes[7]->SetRelativeScale3D(FVector(4.0f, 1.0f, 3.0f));
}

랜덤 방 생성 결과

위 사진으로는 구분이 잘 안될 수도 있기에, 빨간색 타일의 방과 일반 방으로 랜덤하게 생성한 결과물을 추가하였습니다.

 

728x90
반응형