C++와 Unreal Engine으로 3D 게임 개발 3-2,3
지난 글에 이어집니다,,,
[TIL] 2025-01-16 | UE 아이템 시스템 구현하기 (1), 아이템 인터페이
C++와 Unreal Engine으로 3D 게임 개발 3-1,21. WHAT인터페이스란?반드시 구현해야 할 함수 목록만을 미리 정의해두고 실제 함수의 동작은 해당 클래스를 상속받거나 구현하는 쪽에서 자유롭게 작성할
zzageuli.tistory.com
1. WHAT
- Timer
타이머(Timer) 는 Tick()을 사용하지 않고도 일정 시간 후 함수 실행 또는 주기적인 로직 처리를 가능하게 해주는 시스템
TimerManager
- 타이머를 실제로 관리하는 객체
- UWorld가 소유
GetWorld()->GetTimerManager()

FTimerHandle
- 타이머를 식별하기 위한 핸들
- 중지, 재시작, 상태 확인 시 사용
FTimerHandle ExplosionTimerHandle;
SetTimer() 기본 문법
GetWorld()->GetTimerManager().SetTimer(
TimerHandle, // FTimerHandle
Object, // this 포인터
&Class::Function, // 호출할 함수
Delay, // 지연 시간 (초)
bLoop // 반복 여부
);
- SpawnActor
게임 실행 중 월드(World)에 새로운 액터를 생성하는 함수
기본 구조
GetWorld()->SpawnActor<AActor>(
ItemClass, // 생성할 액터 클래스
SpawnLocation, // 위치 (FVector)
SpawnRotation // 회전 (FRotator)
);
2. HOW
- 지뢰 아이템 충돌 처리 (MineItem)
1. 플레이어가 지뢰와 충돌
2. 타이머 실행
3. 5초 후 폭발 범위 내 액터를 확인
4. 범위 내 액터에게 데미지를 입힘
5. 폭발 후 지뢰는 사라짐
먼저 폭발 범위를 지정하기 위해 충돌 스피어 컴포넌트를 추가해주고 반경을 저장할 변수를 선언한다.
public:
USphereComponent* ExplosionCollision;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
float ExplosionRadius;
AMineItem::AMineItem()
{
ItemType = "Mine";
ExplosionDelay = 5.0f;
ExplosionDamage = 30;
ExplosionRadius = 300.0f;
ExplosionCollision = CreateDefaultSubobject<USphereComponent>(TEXT("ExplosionCollision"));
ExplosionCollision->InitSphereRadius(ExplosionRadius);
ExplosionCollision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
ExplosionCollision->SetupAttachment(Scene);
}
이제 타이머를 통해 5초 뒤 폭발을 제어한다.
public:
FTimerHandle ExplosionTimerHandle;
virtual void ActivateItem(AActor* Activator) override;
void Explode();
void AMineItem::ActivateItem(AActor* Activator)
{
// 타이머 설정, 폭발 함수 스케줄링
GetWorld()->GetTimerManager().SetTimer(
ExplosionTimerHandle,
this,
&AMineItem::Explode,
ExplosionDelay,
false
);
}
void AMineItem::Explode()
{
// 실제 폭발 처리 담당
TArray<AActor*> OverlappingActors;
ExplosionCollision->GetOverlappingActors(OverlappingActors);
for (AActor* Actor : OverlappingActors)
{
if (Actor && Actor->ActorHasTag("Player"))
{
// 데미지
}
}
DestroyItem();
}
- 아이템 랜덤 스폰
박스 컴포넌트를 생성하여 박스 컴포넌트 영역 안에서 위치를 랜덤으로 지정해 아이템을 스폰한다.
SpawnVolume 클래스
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SpawnVolume.generated.h"
class UBoxComponent;
UCLASS()
class SPARTAPJ_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);
};
#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
{
// 1) 박스 컴포넌트의 스케일된 Extent, 즉 x/y/z 방향으로 반지름(절반 길이)을 구함
FVector BoxExtent = SpawningBox->GetScaledBoxExtent();
// 2) 박스 중심 위치
FVector BoxOrigin = SpawningBox->GetComponentLocation();
// 3) 각 축별로 -Extent ~ +Extent 범위의 무작위 값 생성
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
);
}
- 데이터 테이블을 사용하여 스폰 로직 구현
None으로 클래스를 생성해서 구조체를 만든다.
#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;
};

데이터 테이블을 생성하고 값을 입력한다.

데이터 테이블로 쉽게 확률을 제어할 수 있다!
SpawnVolume 클래스를 수정하여 확률 로직을 구현한다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemSpawnRow.h" // 우리가 정의한 구조체
#include "SpawnVolume.generated.h"
class UBoxComponent;
UCLASS()
class SPARTAPROJECT_API ASpawnVolume : public AActor
{
GENERATED_BODY()
public:
ASpawnVolume();
UFUNCTION(BlueprintCallable, Category = "Spawning")
void SpawnRandomItem();
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Spawning")
USceneComponent* Scene;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
UBoxComponent* SpawningBox;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Spawning")
UDataTable* ItemDataTable;
FVector GetRandomPointInVolume() const;
FItemSpawnRow* GetRandomItem() const;
void SpawnItem(TSubclassOf<AActor> ItemClass);
};
#include "SpawnVolume.h"
#include "Components/BoxComponent.h"
#include "Engine/World.h"
ASpawnVolume::ASpawnVolume()
{
PrimaryActorTick.bCanEverTick = false;
Scene = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
SetRootComponent(Scene);
SpawningBox = CreateDefaultSubobject<UBoxComponent>(TEXT("SpawningBox"));
SpawningBox->SetupAttachment(Scene);
ItemDataTable = nullptr;
}
void ASpawnVolume::SpawnRandomItem()
{
if (FItemSpawnRow* SelectedRow = GetRandomItem())
{
if (UClass* ActualClass = SelectedRow->ItemClass.Get())
{
SpawnItem(ActualClass);
}
}
}
FVector ASpawnVolume::GetRandomPointInVolume() const
{
const FVector BoxExtent = SpawningBox->GetScaledBoxExtent();
const 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)
);
}
FItemSpawnRow* ASpawnVolume::GetRandomItem() const
{
if (!ItemDataTable) return nullptr;
// 1) 모든 Row(행) 가져오기
TArray<FItemSpawnRow*> AllRows;
static const FString ContextString(TEXT("ItemSpawnContext"));
ItemDataTable->GetAllRows(ContextString, AllRows);
if (AllRows.IsEmpty()) return nullptr;
// 2) 전체 확률 합 구하기
float TotalChance = 0.0f; // 초기화
for (const FItemSpawnRow* Row : AllRows) // AllRows 배열의 각 Row를 순회
{
if (Row) // Row가 유효한지 확인
{
TotalChance += Row->SpawnChance; // SpawnChance 값을 TotalChance에 더하기
}
}
// 3) 0 ~ TotalChance 사이 랜덤 값
const float RandValue = FMath::FRandRange(0.0f, TotalChance);
float AccumulateChance = 0.0f;
// 4) 누적 확률로 아이템 선택
for (FItemSpawnRow* Row : AllRows)
{
AccumulateChance += Row->SpawnChance;
if (RandValue <= AccumulateChance)
{
return Row;
}
}
return nullptr;
}
void ASpawnVolume::SpawnItem(TSubclassOf<AActor> ItemClass)
{
if (!ItemClass) return;
GetWorld()->SpawnActor<AActor>(
ItemClass,
GetRandomPointInVolume(),
FRotator::ZeroRotator
);
}

BP에서 for loop 돌려 SpawnRandomItem 함수 호출로 테스트!

랜덤으로 잘 생성된다 bb
'내배캠Unreal_TIL > UE' 카테고리의 다른 글
| [TIL] 2026-01-21 | UE 게임 기초 시스템 구현 (0) | 2026.01.21 |
|---|---|
| [TIL] 2026-01-20 | 과제 6에 타이머로 자동문 추가 (2) | 2026.01.20 |
| [TIL] 2026-01-16 | UE 아이템 시스템 구현하기 (1), 아이템 인터페이스 (1) | 2026.01.16 |
| [TIL] 2026-01-16 | UE 애니메이션 리타겟팅 (0) | 2026.01.16 |
| [TIL] 2026-01-15 | UE 회전 발판과 움직이는 장애물 (0) | 2026.01.15 |