내배캠/Unreal Engine

인터페이스 기반 아이템 클래스 구현_2_아이템 스폰 및 레벨 데이터 관리

동그래님 2025. 1. 27. 20:18

DataTable 만들기

https://dong-grae.tistory.com/134

 

DataTable 만들기(CSV, Unreal)

DataTable에 사용할 구조체 만들기https://dong-grae.tistory.com/135 데이터 테이블에 사용할 구조체 만들기Item 클래스의 데이터를 정리한다고 했을 때,어떤 아이템이 몇 %확률로 스폰되는지를 코드로 직

dong-grae.tistory.com

 

 

 


 

 

 

 

아이템 스폰 및 레벨 데이터 관리

// ItemSpawnVolume.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemSpawnRow.h"
#include "ItemSpawnVolume.generated.h"

class UBoxComponent;

UCLASS()
class STRIKEZONE_API AItemSpawnVolume : public AActor
{
	GENERATED_BODY()
	
public:	
	AItemSpawnVolume();

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
	TObjectPtr<USceneComponent> Scene;
    // 스폰 영역
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
	TObjectPtr<UBoxComponent> SpawningBox;
    // 아이템 데이터 테이블
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Spawning")
	UDataTable* ItemDataTable;
	
    // 랜덤하게 아이템 스폰 메서드
	UFUNCTION(BlueprintCallable, Category = "Spawning")
	void SpawnRandomItem();
    // ItemDataTable에서 랜덤으로 아이템을 선택해 Row 데이터를 반환 메서드
	FItemSpawnRow* GetRandomItem() const;
    // 아이템 스폰 메서드
	void SpawnItem(TSubclassOf<AActor> ItemClass);
    // SpawningBox의 크기 안에서 랜덤 좌표 생성 및 반환 메서드
	FVector GetRandomPointInVolume() const;
};
  • 현재는 SpawnRandomItem으로 BP에서 해당 함수를 호출 할 수 있게 Blueprintcallable로 설정하였음.
    만약 월드에 ItemSpawnVolume 액터를 배치하고 자동으로 스폰되게 하려면 BeginPlay 함수에 SpawnRandomItem 함수 호출

 


   

 

*cpp 전체 코드

더보기
// ItemSpawnVolume.cpp
#include "ItemSpawnVolume.h"
#include "Components/BoxComponent.h"

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

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

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

	ItemDataTable = nullptr;
}

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

FItemSpawnRow* AItemSpawnVolume::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;
}

FVector AItemSpawnVolume::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 AItemSpawnVolume::SpawnItem(TSubclassOf<AActor> ItemClass)
{
	if (!ItemClass) return;

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



 

 

// Constructor
#include "ItemSpawnVolume.h"
#include "Components/BoxComponent.h"

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

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

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

	ItemDataTable = nullptr;
}
  • Component 초기화

 

 

void AItemSpawnVolume::SpawnRandomItem()
{
	if (FItemSpawnRow* SelectedRow = GetRandomItem())
	{
		if (UClass* ActualClass = SelectedRow->ItemClass.Get())
		{
			SpawnItem(ActualClass);
		}
	}
}
  • 랜덤하게 가져온 Row데이터로 클래스를 가져와, 직접적으로 스폰을 하는 SpawnItem 메서드에 스폰할 클래스를 전달하며 호출

 

 

FItemSpawnRow* AItemSpawnVolume::GetRandomItem() const
{
	if (!ItemDataTable) return nullptr;
	
    // DataTable의 모든 Row를 저장
	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;
		}
	}
	
    // 누적 랜덤 확률 방식으로 Row 데이터 1개 반환
	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;
}
  • DataTable의 GetAllRows 함수를 통해 TArray 배열에 모든 Row데이터를 저장
    이때 함수의 첫 번째 인자로 전달한 ContextString은 혹시나 데이터 테이블에서 오류가 발생했을 때, 이 문자열이 오류 메세지에 포함되게 된다. 오류가 난 문제를 추적할 때 도움이 될 수 있게 함수 시그니처에 포함되어있다.
더보기
// 함수 원형
template <class T>
void GetAllRows(const FString& ContextString, OUT TArray<T*>& OutRowArray) const
{
	GetAllRows<T>(*ContextString, OutRowArray);
}
  • AccumulateChance를 누적해가며 RanValue의 값보다 커지면 해당 Row데이터를 반환한다.

    예를 들어 사과 5개, 배 3개, 키위 2개 이렇게 있다고 가정하고  RandValue가 0.0 ~ 1.0사이의 값 중 하나라고 가정한다면,
    RandValue가 사과는 0.0 ~ 0.5, 배는 0.6 ~ 0.8, 키위는 0.9 ~ 1.0 의 값일 때 해당 과일이 선택되게 된다.

 

 

FVector AItemSpawnVolume::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)
		);
}
  • GetScaleBoxExtent( ): 박스의 중심점으로 부터 각 축 방향 크기(X, Y, Z)를 반환한다. 박스의 반지름과 같은 느낌
  • 박스 크기 안에서 랜덤한 좌표를 반환

 

 

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

	GetWorld()->SpawnActor<AActor>(
		ItemClass,
		GetRandomPointInVolume(),
		FRotator::ZeroRotator
	);
}
  • ItemClass를 SpawActor 함수를 통해 월드에 스폰
  • 위치는 GetRandomPointInVolume 함수를 사용해 박스 크기 안에서의 랜덤 좌표
  • 회전 값은 ZeroRotator