본문 바로가기

[C++과 Unreal Engine으로 3D 게임 개발] 아이템 스폰 및 레벨 데이터 관리

@iamrain2025. 9. 30. 14:55

랜덤 위치에 아이템 스폰

콜리전 컴포넌트로 스폰 영역 지정

콜리전 컴포넌트 내부에 임의의 좌표를 뽑아 액터를 스폰할 수 있다.

 

Actor 클래스를 상속한 `SpawnVolume` 클래스를 생성

#pragma once

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

class UBoxComponent;

UCLASS()
class SPARTAPROJECT_API ASpawnVolume : public AActor
{
	GENERATED_BODY()
	
public:	
	ASpawnVolume();

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
	USceneComponent* Scene;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
	UBoxComponent* SpawningBox;

	UFUNCTION(BlueprintCallable, Category = "Spawning")
	FVector GetRandomPointInVolume() const;
	UFUNCTION(BlueprintCallable, Category = "Spawning")
	void SpawnItem(TSubclassOf<AActor> ItemClass);
};
#include "SpawnVolume.h"
#include "Components/BoxComponent.h"
#include "Engine/World.h"
#include "GameFramework/Actor.h"

ASpawnVolume::ASpawnVolume()
{
	PrimaryActorTick.bCanEverTick = false;

	Scene = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
	SetRootComponent(Scene);

	SpawningBox = CreateDefaultSubobject<UBoxComponent>(TEXT("SpawningBox"));
	SpawningBox->SetupAttachment(Scene);
}

FVector ASpawnVolume::GetRandomPointInVolume() const
{
	FVector BoxExtent = SpawningBox->GetScaledBoxExtent();
	FVector BoxOrigin = SpawningBox->GetComponentLocation();

	return BoxOrigin + FVector(
		FMath::FRandRange(-BoxExtent.X, BoxExtent.X),
		FMath::FRandRange(-BoxExtent.Y, BoxExtent.Y),
		FMath::FRandRange(-BoxExtent.Z, BoxExtent.Z)
	);
}

void ASpawnVolume::SpawnItem(TSubclassOf<AActor> ItemClass)
{
	if (!ItemClass) return;

	GetWorld()->SpawnActor<AActor>(
		ItemClass,
		GetRandomPointInVolume(),
		FRotator::ZeroRotator
	);
}

랜덤 스폰

`BP_SpawnVolume` 생성 후 Level에 배치하고 위치와 스케일을 조정한다.

아이템 스폰을 위해서 노드 추가.

지속적인 스폰은 Tick 이벤트나 타이머를 사용

 

아이템 스폰 확률 데이터 테이블 생성

Item Data 구조체 생성

스폰 확률을 하드코딩하면 수정할 때마다 빌드를 다시 해야 한다.

데이터 테이블을 사용해서 이를 관리하기 위해 구조체 파일을 생성한다.

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "ItemSpawnRow.generated.h"

USTRUCT(BlueprintType)
struct FItemSpawnRow : public FTableRowBase
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FName ItemName;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TSubclassOf<AActor> ItemClass;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float SpawnChance;
};

 

언리얼 에디터를 켜서 Content Browser에서 아래와 같이 Data Table을 만든다.

생성한 Data Table에서 아래와 같이 Row를 추가하고 편집한다.

 

데이터 테이블을 사용해 스폰 확률 적용

...
#include "ItemSpawnRow.h"
...

UCLASS()
class SPARTAPROJECT_API ASpawnVolume : public AActor
{
	...
	
public:	
	...

	UFUNCTION(BlueprintCallable, Category = "Spawning")
	void SpawnRandomItem();

protected:
	...

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Spawning")
	UDataTable* ItemDataTable;

	...
	FItemSpawnRow* GetRandomItem() const;
	...
};
...

void ASpawnVolume::SpawnRandomItem()
{
	if (FItemSpawnRow* SelectedRow = GetRandomItem())
	{
		if (UClass* ActualClass = SelectedRow->ItemClass.Get())
		{
			SpawnItem(ActualClass);
		}
	}
}

...

FItemSpawnRow* ASpawnVolume::GetRandomItem() const
{
	if (!ItemDataTable) return nullptr;

	TArray<FItemSpawnRow*> AllRows;
	static const FString ContextString(TEXT("ItemSpawnContext"));
	ItemDataTable->GetAllRows(ContextString, AllRows);

	if (AllRows.IsEmpty()) return nullptr;

	float TotalChance = 0.0f;
	for (const FItemSpawnRow* Row : AllRows)
	{
		if (Row)
		{
			TotalChance += Row->SpawnChance;
		}
	}

	const float RandValue = FMath::FRandRange(0.0f, TotalChance);
	float AccumulateChance = 0.0f;

	for (FItemSpawnRow* Row : AllRows)
	{
		AccumulateChance += Row->SpawnChance;
		if (RandValue <= AccumulateChance)
		{
			return Row;
		}
	}

	return nullptr;
}

...

`BP_SpawnVolume`에서 아래와 같이 세팅한다

레벨 별 다른 확률

레벨 별 다른 확률은 2가지 방법이 있다.

  • 레벨 별 Data Table 적용
  • Data Table에 레벨 구분 컬럼 추가
iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차