스폰
만든 아이템들을 레벨 상에서 자연스럽게 랜덤 배치하기
콜리전 컴포넌트로 스폰 영역 지정하기
콜리전 컴포넌트 내부의 임의 좌표를 뽑아서 아이템을 스폰한다.
// SpawnVolume.h
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
USceneComponent* Scene;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
UBoxComponent* SpawningBox; // 스폰 영역을 담당할 박스 컴포넌트
// 스폰 볼륨 내부에서 무작위 좌표를 얻어오는 함수
FVector GetRandomPointInVolume() const;
// 특정 아이템 클래스를 스폰하는 함수
void SpawnItem(TSubclassOf<AActor> ItemClass);
- UBoxComponent* SpawningBox;
- 박스 형태의 콜리전 영역, 실제로 눈에 보이는 3D 메시는 아니며, “박스 형태의 충돌 범위”만 제공
- GetRandomPointInVolume()
- SpawningBox 범위 내부에서 랜덤 좌표(FVector)를 리턴
- SpawnItem()
- 파라미터로 받은 아이템 클래스를 SpawnVolume 내부의 랜덤 위치에 생성(SpawnActor)하는 함수.
루트컴포넌트와 박스컴포넌트 설정하고 함수 로직을 구현한다.
// SpawnVolume.cpp
// 생성자
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(), // 위치 FVector
FRotator::ZeroRotator // 회전 //FRotator
);
}
- GetScaledBoxExtent()
- 박스 컴포넌트의 실제 “가로/세로/높이 절반 길이(Extent)”를 반환합니다. 에디터에서 Scale을 조절하면 여기에도 반영이 된다.
GetRandomPointInVolume 함수는 박스의 중심(BoxOrigin)을 기준으로 축 별로 - 절반 길이, + 절반 길이(BoxExtent) 사이의 랜덤한 좌표를 반환하는 함수다.
SpawnItem은 파라미터로 받은 아이템 클래스를 SpawnActor 함수로 스폰하는 함수이다.
데이터 테이블
어떤 아이템이 몇 % 확률로 스폰되는지를 코드로 직접 하드코딩하면, 매번 수정할 때마다 빌드를 해줘야 해서 번거롭다.
언리얼 엔진에는 데이터 테이블이 있는데 이를 엑셀(CSV)이나 JSON 파일로 관리해서 엔진 안으로 임포트하고, 코드나 블루프린트에서 쉽게 불러 쓸 수 있다. 수정이 필요할 때 엑셀에서 숫자만 바꾸면 되므로 매우 편리하다.
언리얼 엔진은 FTableRowBase라는 기본 구조체를 제공하며, 이를 상속한 구조체를 만들면, 각 CSV (또는 JSON) 행을 만든 구조체에서 정해준 형태로 받아올 수 있게 됩니다.
구조체는 클래스가 아니기 때문에, 부모 클래스를 선택할 수 있는 옵션에 나타나지 않습니다.
따라서 None을 선택해서 C++ 파일을 만든다.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h" // FTableRowBase 정의가 들어있는 헤더
#include "ItemSpawnRow.generated.h"
USTRUCT(BlueprintType)
struct FItemSpawnRow : public FTableRowBase // 언리얼에서 제공하는 FTableRowBase 상속
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName ItemName;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSubclassOf<AActor> ItemClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Spawnchance;
};
- FTableRowBase
- 언리얼 엔진에서 “이 구조체는 데이터 테이블로 쓸 수 있다”라고 인식하게 해주는 베이스 구조체입니다.
현재 코드에서는 TSubclassOf<AAcotr>를 사용했는데 이는 하드 레퍼런스로 클래스가 항상 메모리에 로드된 상태에서 바로 접근을 하는 방법인데 아이템의 개수가 많으면 모든 아이템을 항상 메모리에 로드를 해놔야 되기 때문에 성능상 좋지 않다. 실제로 권장하지 않는 방법이다.
보통 소프트 레퍼런스를 사용하는게 권장되지만 소프트 레퍼런스 같은 경우 여러가지 다른 조치가 필요하다.(복잡함)
현재 단계에서는 이 복잡한 과정을 거치지 않고 아이템 개수도 적기 때문에 하드 레퍼런스로 진행했다.
TSoftClassPtr<AActor> : 소프트 레퍼런스, 클래스를 바로 로드하지 않고 경로만 기억해둘 수 있다.
필요할 때 Get()을 통해 실제 UClass*를 얻어 인스턴스를 생성할 수 있습니다.
데이터 테이블 수정하기
이렇게 구조체를 만들었으면 이를 데이터 테이블을 만들어서 수정해야 한다.
1. 엑셀로 작성해서 불러오는 방법
2. 언리얼 에디터에서 직접 수정하는 방법
이 있는데 에디터에서 수정하는 방법을 실습했다. (엑셀은 적용하는 방법만)
언리얼 에디터에서 직접 데이터 테이블 수정하기
우클릭을 해서 Miscellaneous->Data Table을 선택해서 생성하고 만들어둔 ItemSpawnRow를 적용한다.
처음 키면 원래는 다 비어있고 구조체에서 만든 Item Name, Item Class, Spawnchance만 있다.
Add를 누르면 행이 하나씩 추가되며 Row Name을 더블 클릭해서 입력하고 아래에 Row Editor 창에서 변수마다 알맞게 입력, 지정해주면 된다.
RowName과 ItemName
RowName은 데이터 베이스를 만들면 기본적으로 있는 열이며 Key 값으로 중복된 값이 들어갈 수 없다.
테이블에서 실제로 값을 찾을 때 RowName을 통해 찾게 된다.
ItemName은 사용자가 구조체에서 만든 FName 변수로 참고하기 위한 이름일 뿐이다. 서로 다른 의미다.
엑셀(CSV)파일 작성 및 임포트 하기(실습은 안했음)
- 엑셀을 열고 2열부터 Column 이름을 작성합니다.
- 1열은 “RowName”으로 인식됩니다. Key 값으로 사용할 값을 입력해줘야 합니다.
- 2열부터는 각 아이템 (데이터) 정보를 한 줄씩 기록합니다.
- 경로는 각 BP 클래스 우클릭 → Copy Reference 로 정확히 가져오고 블루프린트의 경우 뒤에 "_C" 가 붙어야 BP 클래스로 인식된다.
- CSV로 저장
- 언리얼 콘텐츠 폴더가 아니라 본인이 편한 로컬 위치에 파일명 ItemSpawnTable.csv 로 **CSV UTF-8 (쉼표로 분리)**로 저장합니다.
- 언리얼 에디터로 돌아와, Content Browser에서 Blueprints 폴더에서 우클릭 → Import to /Game/Blueprints 를 선택, 아래와 같이 옵션을 설정하고 Apply를 클릭해줍니다.
- 그러면 다음과 같이 성공적으로 DatTable이 만들어진 것을 볼 수 있습니다.
- 만약 CSV 파일에서 데이터가 업데이트되었을 경우 Reimport를 해주면 됩니다.
확률
만든 데이터 테이블을 사용해서 스폰 로직을 구현해야한다.
// SpawnVolume.h
#include "ItemSpawnRow.h" // 정의한 구조체 포함
UFUNCTION(BlueprintCallable, Category = "Spawning")
void SpawnRandomItem(); // 랜덤한 아이템 스폰하는 함수
FItemSpawnRow* GetRandomItem() const; // 랜덤한 아이템 선택하는 함수
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Spawning")
UDataTable* ItemDataTable; // 만든 데이터 테이블을 담을 변수
// SpawnVolume.cpp
// 생성자
ItemDataTable = nullptr;
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;
}
void ASpawnVolume::SpawnRandomItem()
{
if (FItemSpawnRow* SelectedRow = GetRandomItem())
{
if (UClass* ActualClass = SelectedRow->ItemClass.Get())
{
SpawnItem(ActualClass);
}
}
}
- GetRandomItem()
- 테이블의 모든 Row를 가져와 확률합을 구하고, 확률에 따라 한 개를 뽑아 리턴합니다.
- SpawnRandomItem()
- “데이터 테이블에서 확률 계산으로 Row 하나 선택” + “스폰”을 분리해놓아 코드가 깔끔해집니다.
GetRandomItem 함수 위에서 부터 한줄씩 이해하기
1. TArray<FItemSpawnRow*> AllRows;
DataTable 안에 있는 행(row)들을 담아둘 배열.
2. static const FString ContextString(TEXT("ItemSpawnContext"));
DataTable에서 행을 가져올 때, 내부적으로 에러나 경고 메시지를 띄울 때 쓰는 문맥(context) 문자열.
아무 문자열이나 넣어도 동작은 같다(보통 코드 위치를 알아보기 쉽게 의미 있는 이름을 줌)
3. ItemDataTable->GetAllRows(ContextString, AllRows);
ItemDataTable은 UDataTable* 타입.
GetAllRows 함수는 해당 데이터테이블에 들어 있는 모든 행(row)을 찾아서 두 번째 파라미터(AllRows)에 채워 넣어 줌.
첫 번째 인자 ContextString은 로그 출력용.
뒤의 내용은 이해가 돼서 생략.
에디터의 BP_SpawnVolume에 비어있는 ItemSpawnTable 변수에 만들어둔 데이터 테이블을 넣는다.
그림과 같이 노드를 생성하여 연결하고 테스트를 해보면 실행마다 랜덤한 아이템이 랜덤한 좌표에 생성된다.
'언리얼 + cpp' 카테고리의 다른 글
언리얼C++ 9일차(게임 흐름 로직(게임 루프), Game Instance) (0) | 2025.09.24 |
---|---|
언리얼C++ 8일차(TakeDamage와 ApplyDamage, GameState) (0) | 2025.09.23 |
언리얼C++ 8일차(Collision, 이벤트 바인딩, 타이머 핸들) (0) | 2025.09.23 |
언리얼C++ 7일차(인터페이스) (0) | 2025.09.22 |
언리얼C++ 6일차(애니메이션 블루프린트, 스테이트 머신, Control Rig) (0) | 2025.09.19 |