프로그래밍/언리얼
언리얼 절차적 던전 생성
추향
2024. 11. 18. 18:26
던전 생성
언리얼에서 절차적으로 던전을 생성하기 위해 작성한 코드입니다.
작동은 하지만 미완성입니다.
간단한 설명.
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의 과정을 반복합니다.
실행
실행한 결과는 다음과 같습니다.
방은 두개만 넣어서 실행했습니다.
주요 코드.
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를 이용해 충돌체크를 하기 때문에 방 크기에 맞춰 사용합니다.