티스토리 뷰

🟩 오늘의 목표

  • UBoxComponent를 활용해 특정 영역 내 랜덤 좌표에 아이템을 스폰하는 SpawnVolume을 제작한다.
  • 언리얼 엔진의 DataTable 기능을 활용해 아이템별 스폰 확률 데이터를 외부에서 관리하는 법을 익힌다.
  • 누적 확률 알고리즘을 코드로 구현하여 설정된 확률에 따라 아이템이 스폰되는 실전 로직을 완성한다.

🟧 1. 랜덤 위치에 아이템 스폰하기

🟦 레벨 세팅 및 콜리전 컴포넌트 활용

  • Edit - Project Settings - Maps & Modes에서 Default Maps를 BasicLevel로 변경하여 실습 환경을 준비함.
  • UBoxComponent는 박스 형태의 충돌 범위를 제공하며, 실제 눈에 보이는 메시는 아니지만 내부의 오버랩이나 충돌을 감지하고 좌표를 계산하는 용도로 활용 가능함.

🟦 SpawnVolume 클래스 설계

새로운 액터 클래스인 ASpawnVolume을 생성하여 스폰 영역과 로직을 정의함.

// SpawnVolume.h
#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);
};

🟦 랜덤 좌표 계산 및 스폰 구현

GetScaledBoxExtent와 FMath::FRandRange를 조합하여 박스 범위 내의 임의의 위치를 리턴함.

// SpawnVolume.cpp
#include "SpawnVolume.h"
#include "Components/BoxComponent.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
    );
}

🟧 2. 아이템 스폰 확률 데이터 테이블

🟦 ItemSpawnRow 구조체 정의

데이터 테이블의 각 행을 매핑하기 위해 FTableRowBase를 상속받는 FItemSpawnRow 구조체를 선언함.

// ItemSpawnRow.h
#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;
};

🟦 CSV 임포트 및 관리

  • RowName을 첫 번째 열로 둔 CSV 파일을 작성하여 데이터를 엔진으로 임포트함.
  • 블루프린트 클래스 경로를 사용할 때는 반드시 끝에 _C 접미사를 붙여야 UClass로 인식됨.
  • 기획 데이터 수정 시 코드를 빌드할 필요 없이 DataTableReimport 하거나 에디터에서 직접 수정하면 즉시 반영됨.

🟧 3. 스폰 로직에 확률 적용

🟦 확률 기반 아이템 선택 로직

DataTable에 등록된 모든 아이템의 확률 합을 구하고, 누적 확률 알고리즘을 통해 하나의 아이템을 선정함.

// SpawnVolume.cpp 확률 관련 함수 구현
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;

    // 1) 전체 확률 합 구하기
    float TotalChance = 0.0f;
    for (const FItemSpawnRow* Row : AllRows)
    {
        if (Row) TotalChance += Row->SpawnChance;
    }

    // 2) 0 ~ TotalChance 사이의 랜덤 값 생성
    const float RandValue = FMath::FRandRange(0.0f, TotalChance);
    float AccumulateChance = 0.0f;

    // 3) 누적 확률로 아이템 선택
    for (FItemSpawnRow* Row : AllRows)
    {
        AccumulateChance += Row->SpawnChance;
        if (RandValue <= AccumulateChance)
        {
            return Row;
        }
    }

    return nullptr;
}

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

🟧 핵심 요약 및 주의사항

  • UFUNCTION(BlueprintCallable) : C++에서 작성한 스폰 로직을 블루프린트의 BeginPlay나 Tick에서 호출하기 위해 필수적임.
  • FTableRowBase : 데이터 테이블 기능을 사용하기 위한 필수 상속 기반임.
  • 누적 확률 로직 : 단순 랜덤보다 기획 의도를 정확히 반영할 수 있으며, SpawnRandomItem 함수 하나로 간편하게 제어 가능함.
  • 레벨별 관리 : SpawnVolume의 ItemDataTable 변수에 레벨마다 다른 데이터 테이블 에셋을 할당하여 난이도 조절이 용이함.

'내일배움캠프 Unreal_7기 > 본캠프' 카테고리의 다른 글

TIL - 46일  (0) 2026.02.02
TIL - 45일차  (0) 2026.01.30
TIL - 43일차  (1) 2026.01.28
TIL - 42일차  (0) 2026.01.27
TIL - 41일차  (0) 2026.01.26