이번에는 만든 아이템을 랜덤으로 스폰하는 것이 목적이다.
아이템이 스폰되는 확률로 난이도를 조절해줄 것이다.
구현할 내용
- 랜덤 스폰 기능
- 스폰영역 지정
- 아이템의 스폰 확률 지정
맵 설정


기본 레벨을 만들고 메인 레벨로 설정해준다.
스폰 영역 지정
블루프린트로 구현


블루프린트로 새 액터 클래스를 만들고 박스 콜리전 컴포넌트를 추가한다.


레벨에 이렇게 콜리전 컴포넌트 액터를 배치하고, 크기를 조정해 스폰 영역을 지정할 수 있다. 콜리전 컴포넌트는 시각적으로 직관적이기 때문에 가벼우면서도 범위를 계산하기 편리하다.
C++로 구현



SpawnActor
게임 실행 중에 클래스(Blueprint 또는 C++)의 '설계도'를 바탕으로 실제 세상에 '실체(Instance)'를 만들어 배치하는 가장 핵심적인 함수.
AActor* SpawnedActor = GetWorld()->SpawnActor<T>(
UClass* ClassToSpawn, // 1. 무엇을 만들 것인가? (설계도)
FVector const& Location, // 2. 어디에 만들 것인가?
FRotator const& Rotation, // 3. 어떤 방향으로 만들 것인가?
FActorSpawnParameters const& SpawnParameters // 4. (선택) 상세 설정
);
FActorSpawnParameters
스폰할 때 "만약 그 자리에 이미 다른 물체가 있다면 어떻게 할지" 같은 세부 규칙을 정할 수 있다.
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this; // 이 액터를 소유할 주인 설정
SpawnParams.Instigator = GetInstigator(); // 데미지 가해자 등으로 기록될 존재
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
// ↑ 충돌하는 물체가 있어도 무조건 스폰할지 여부
new AAtor() 대신 SpawnActor를 써야하는 이유
- 월드 등록: SpawnActor를 써야만 엔진이 해당 액터를 월드의 리스트에 넣고, 매 프레임 Tick을 돌려주며, 물리 연산을 수행하게 한다.
- 메모리 관리: 엔진의 가비지 컬렉터(GC)가 해당 액터를 추적할 수 있게 된다다.
- 네트워크: 멀티플레이 게임이라면 서버에서 스폰했을 때 클라이언트들에게도 복제(Replication)되도록 처리해준다.
사용 예시
void APawnDrone::Fire()
{
if (ProjectileClass) // 에디터에서 할당한 블루프린트 클래스
{
FVector SpawnLocation = GetActorLocation() + GetActorForwardVector() * 100.0f;
FRotator SpawnRotation = GetActorRotation();
FActorSpawnParameters Params;
Params.Owner = this;
// 실제 생성
AProjectile* Bullet = GetWorld()->SpawnActor<AProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, Params);
if (Bullet)
{
UE_LOG(LogTemp, Log, TEXT("총알 생성 성공!"));
}
}
}
블루프린트 클래스를 스폰하려면
헤더에 변수를 선언하고, 에디터에서 선택해준다.
UPROPERTY(EditAnywhere, Category = "Spawning")
TSubclassOf<AActor> ActorToSpawn; // 에디터에서 블루프린트 선택 가



테스트하기



BP_SpawnVolume을 모든 대상 레벨에 배치한다.
아이템 스폰 확률 설정하기
데이터 테이블용 구조체 만들기
게임에서 사용하는 여러 데이터들을 외부에서 불러와서 적용해줄 수 있다.
우선, 구조체를 만들어준다. 구조체는 클래스가 아니기 때문에, C++ 클래스 추가하기에 None을 선택해서 파일만 만들어준다.




헤더파일에 USTRUCT로 구조체를 선언해준다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#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; // 스폰 확률
};
USTRUCT
언리얼의 리플렉션 구조체
USTRUCT(BlueprintType) // 블루프린트에서 변수로 쓸 수 있게 함
struct FDroneStats // 이름은 반드시 'F'로 시작해야 함
{
GENERATED_BODY() // 필수: 엔진 시스템을 위한 코드를 자동 생성함
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float MaxSpeed = 500.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 BatteryLife = 100;
};
- USTRCUCT 인자
- BlueprintType: 이 구조체를 블루프린트에서 변수 타입으로 사용할 수 있게 한다. 이게 없으면 C++ 전용 구조체가 된다. 가장 많이 쓴다.
- Atomic: 구조체 내부의 모든 변수를 한꺼번에 직렬화(Serialization)하도록 지정한다. 데이터의 무결성이 중요한 경우에 쓴다.
- Immutable: 이 구조체의 프로퍼티가 절대 변경되지 않음을 최적화 시스템에 알린다. (자주 쓰이진 않는다.)
TSubclassOf<T>
언리얼 엔진에서 특정 클래스나 그 하위 클래스들만 선택할 수 있도록 제한하는 안전한 클래스 타입.
- 클래스가 항상 메모리에 로드된 상태에서 바로 접근하는 하드레퍼런스 방식.
- 이와 달리 TSoftClassPtr 은 소프트 레퍼런스로, 클래스의 경로만 유지하고 해당 클래스가 필요한 상황이 되면 메모리에 로드.
- 데이터에 클래스가 많을 수 있어 성능상으로는 소프트 레퍼런스가 낫지만, 에러 방지 조치가 필요하기 때문에 지금 실습에서는 TSubclassOf를 사용한다.
데이터 테이블 만들기
아래와 같이 에디터에서 데이터 테이블을 만든다.



RowName은 인덱스와 같은 개념이며, 나머지 열은 구조체의 멤버 목록인 것을 알 수 있다.
데이터 추가하기






데이터 테이블 적용하기
SpawnVolume에 SpawnRandomItem, GetRandomItemRow를 추가한다.
UFUNCTION(BlueprintCallable, Category = "Spawning")
void SpawnRandomItem(); // 랜덤으로 아이템 스폰
// 확률에 따라 아이템 선택
FItemSpawnRow* GetRandomItemRow() const; // 아이템 랜덤 뽑기
void SpawnItem(TSubclassOf<AActor> ItemClass); // TSubclassof<T>는 T 클래스의 하위 클래스가 아니면 오류를 발생시킨다.
FVector GetRandomPointInVolume() const;
void ASpawnVolume::SpawnRandomItem()
{
if (FItemSpawnRow* SelectedRow = GetRandomItemRow()) // 랜덤 아이템 뽑기
{
if (UClass* ActualClass = SelectedRow->ItemClass.Get()) // 구조체 포인터에서 가져오는 데이터는 열 이름.Get()으로 가져온다. 반환형은 TSoftClassPtr이고 UClass로 받아진다.
{
SpawnItem(ActualClass);
}
}
}
FItemSpawnRow* ASpawnVolume::GetRandomItemRow() const
{
if (!ItemDataTable) return nullptr; // 아이템 데이터 데이블이 유효하지않으면 nullptr 반환
TArray<FItemSpawnRow*> AllRows; // 아이템을 담을 배열 선언
static const FString ContextString(TEXT("ItemSpawnContext")); // 데이터 테이블에서 행을 가져올 때 디버깅 정보 저장
ItemDataTable->GetAllRows(ContextString, AllRows); // 데이터 테이블의 모든 데이터를 배열 변수에 저장
if (AllRows.IsEmpty())
{
return nullptr; // 데이터가 비어있으면 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;
}






'게임 개발 > 학습 일지' 카테고리의 다른 글
| 언리얼 C++ - State Machine 설계를 통한 캐릭터 동작 애니메이션 적용하기 (0) | 2026.04.24 |
|---|---|
| 언리얼 C++ - 캐릭터 체력 및 점수 관리 시스템 구현하기 (0) | 2026.04.23 |
| 언리얼 C++ - 인터페이스 기반 아이템 클래스 설계하기 (1) | 2026.04.21 |
| 언리얼 엔진 심화 2주차 - 컨테이너와 포인터 (0) | 2026.04.19 |
| 언리얼 게임 개발 - 차량 에셋에 바퀴 굴러가는 애니메이션 추가하기 (0) | 2026.04.17 |