언리얼 절차적 던전 생성

250x250

던전 생성

언리얼에서 절차적으로 던전을 생성하기 위해 작성한 코드입니다.

작동은 하지만 미완성입니다.

간단한 설명.

1. Main Room을 생성하고, Arrow List(ChildrenArrow)를 가져옵니다.

2. 'ChildrenArrow'에서 랜덤으로 Arrow를 선택합니다.

3. 선택한 Arrow의 위치에 Room을 스폰합니다.

4 스폰된 방이 겹치는지 확인합니다:

4.1 충돌이 발생하면 방을 삭제하고 다른 Arrow로 시도합니다.

4.2 충돌이 발생하지 않으면 방을 유지합니다.

5. Room생성에 성공하면 Room의 Arrow를 'ChildrenArrow'에 추가합니다.

 

이렇게 Room Num을 만족하거나 Arrow없어질 때까지 3-5의 과정을 반복합니다.

실행

실행한 결과는 다음과 같습니다.

방은 두개만 넣어서 실행했습니다.

https://youtu.be/xYDS74XW1Aw

 

주요 코드.

Generator Room.h

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

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EscapeMapGenerator.generated.h"

class ARoom;
class ACorridor;

UCLASS()
class ESCAPE_API AEscapeMapGenerator : public AActor
{
	GENERATED_BODY()
public:	
    UPROPERTY(EditAnywhere, Category = "Dungeon Settings")
    int32 RoomNum = 30;
protected:
	AEscapeMapGenerator();
	virtual void BeginPlay() override;
    TArray<USceneComponent*> ChildrenArrow;
    void GenerateDungeon();
    void SpawnNextRoom(FVector SpawnLoc, FRotator SpawnRot, const TSubclassOf<ARoom>& InRoomClass);
    UPROPERTY(EditAnywhere, Category = "Dungeon Settings")
    TArray<ARoom*> Rooms;
    UPROPERTY(EditAnywhere, Category = "Dungeon Settings")
    TArray<ACorridor*> Corridors;
    UPROPERTY(EditAnywhere, Category = "Dungeon Settings")
	TSubclassOf<ARoom> MainRoomClass;
    UPROPERTY(EditAnywhere, Category = "Dungeon Settings")
    TArray<TSubclassOf<ARoom>> RoomClasses;
private:
    FTimerHandle RoomSpawnDelay;
    int32 CurrentRoomCount=0;
};

Generator Room.cpp

// Fill out your copyright notice in the Description page of Project Settings.
#include "MapGen/EscapeMapGenerator.h"
//구성요소.
#include "MapGen/Room.h"
#include "MapGen/Corridor.h"

#include "Kismet\KismetMathLibrary.h"
#include "Kismet/KismetSystemLibrary.h"
//Component
#include "Components/BoxComponent.h"
//Debug용 커스텀 코드.
#include "DrawDebugHelpers.h"
#include "CustomDebugHelper.h"
// Sets default values
AEscapeMapGenerator::AEscapeMapGenerator()
{
	PrimaryActorTick.bCanEverTick = false;
}
void AEscapeMapGenerator::BeginPlay()
{
	Super::BeginPlay();
	GenerateDungeon();
}
void AEscapeMapGenerator::GenerateDungeon()
{
	//Spawn MainRoom
	if (RoomNum>0) {
		SpawnNextRoom(FVector::ZeroVector, FRotator::ZeroRotator, MainRoomClass);
		CurrentRoomCount = 1;
		//Spawn Other Room
		GetWorld()->GetTimerManager().ClearTimer(RoomSpawnDelay);
		GetWorld()->GetTimerManager().SetTimer(RoomSpawnDelay, FTimerDelegate::CreateLambda([this]() {
			if (CurrentRoomCount < RoomNum && ChildrenArrow.Num() > 0) {
				int32 RandomClassIndex = FMath::RandRange(0, RoomClasses.Num() - 1);
				int32 RandomIndex = FMath::RandRange(0, ChildrenArrow.Num() - 1);
				USceneComponent* SelectArrow = ChildrenArrow[RandomIndex];
				ChildrenArrow.RemoveAt(RandomIndex); // 사용된 Arrow를 제거
				SpawnNextRoom(SelectArrow->GetComponentLocation(), SelectArrow->GetComponentRotation(), RoomClasses[RandomClassIndex]);
			}
			else {
				Debug::Print("End_Generator");
				Debug::Print(FString::FormatAsNumber(ChildrenArrow.Num()));

				GetWorld()->GetTimerManager().ClearTimer(RoomSpawnDelay);
			}
		}), 0.1f, true);
	}
}
void AEscapeMapGenerator::SpawnNextRoom(FVector SpawnLoc,FRotator SpawnRot,const TSubclassOf<ARoom>& InRoomClass)
{
	FActorSpawnParameters SpawnParams;
	SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
	//Spawn
	ARoom* RoomActor = GetWorld()->SpawnActor<ARoom>(InRoomClass, SpawnLoc, SpawnRot, SpawnParams);
	TArray<AActor*> OverlappingActors;
	FCollisionQueryParams QueryParams;
	QueryParams.AddIgnoredActor(RoomActor);  // RoomActor는 검사에서 제외됨
	RoomActor->BoxComponent->GetOverlappingActors(OverlappingActors, ARoom::StaticClass());
	OverlappingActors.Remove(RoomActor);
	if (OverlappingActors.Num() > 0)
	{
		Debug::DrawDebugBox(RoomActor->BoxComponent,true);
		RoomActor->Destroy(); return;
	}
	else
	{
		Debug::DrawDebugBox(RoomActor->BoxComponent);
		// 겹침이 없는 경우: Children 업데이트 및 후속 작업 수행
		CurrentRoomCount++;
		TArray<USceneComponent*> NewChildrenArrow;
		RoomActor->ArrowFolder->GetChildrenComponents(false, NewChildrenArrow);
		ChildrenArrow.Append(NewChildrenArrow); return;
	}
}

Room.h

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

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Room.generated.h"

class UBoxComponent;
class UArrowComponent;
UCLASS()
class ESCAPE_API ARoom : public AActor
{
	GENERATED_BODY()
public:	
	UPROPERTY(EditDefaultsOnly,BlueprintReadOnly)
	USceneComponent* ArrowFolder;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	UBoxComponent* BoxComponent;
protected:
	ARoom();
	virtual void BeginPlay() override;
	UPROPERTY(EditDefaultsOnly)
	USceneComponent* GeometryFolder;
	USceneComponent* RootSceneComponent;
	UPROPERTY(EditDefaultsOnly)
	UArrowComponent* ActorArrowComponent;
};

Room.cpp

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

#include "MapGen/Room.h"
#include "Components/BoxComponent.h"
#include "Components/ArrowComponent.h"
// Sets default values
ARoom::ARoom()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	RootSceneComponent =CreateDefaultSubobject<USceneComponent>(TEXT("RootSceneComponent"));
	SetRootComponent(RootSceneComponent);
	ActorArrowComponent =CreateDefaultSubobject<UArrowComponent>(TEXT("ActorArrowComponent"));
	ActorArrowComponent->SetupAttachment(GetRootComponent());
	GeometryFolder=CreateDefaultSubobject<USceneComponent>(TEXT("GeometryFolder"));
	GeometryFolder->SetupAttachment(GetRootComponent());
	ArrowFolder =CreateDefaultSubobject<USceneComponent>(TEXT("ArrowFolder"));
	ArrowFolder->SetupAttachment(GetRootComponent());
	BoxComponent=CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComponent"));
	BoxComponent->SetupAttachment(GetRootComponent());
}
void ARoom::BeginPlay()
{
	Super::BeginPlay();
}

Blueprint

이런식으로 Room을 구성합니다.

Arrow의 위치를 기반으로 Room을 생성하기 때문에 문이 위치한 지점에 Arrow를 배치합니다.

Room을 생성하면 0,0,0의 위치에서 생성되기 때문에 이것을 생각해서 Room을 구현합니다.

boxComponent를 이용해 충돌체크를 하기 때문에 방 크기에 맞춰 사용합니다.

Designed by JB FACTORY