내배캠/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