UE - 랜덤 아이템 스폰
UnrealEngine
아이템
아이템은 아이템 인터페이스를 만들어서 생성했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
UINTERFACE(MinimalAPI)
class UC_ItemInterface : public UInterface
{
GENERATED_BODY()
};
class OPENWORLDRPG_API IC_ItemInterface
{
GENERATED_BODY()
public:
UFUNCTION()
virtual void OnItemOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OverlapActor,
UPrimitiveComponent* OverlapComp,
int32 OtherActorIndex,
bool bFromSweep,
const FHitResult& SweepResult
) = 0;
UFUNCTION()
virtual void OnItemEndOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OverlapActor,
UPrimitiveComponent* OverlapComp,
int32 OtherActorIndex
) = 0;
virtual void ActivateItem(AActor* Activator) = 0;
virtual FName GetItemType() const = 0; //string 보다 가볍다
};
사용하거나 장착하는 아이템이 아니라 맵에 배치되는 아이템을 설계할 것이므로, 필수 기능들을 인터페이스에 넣어줬다.
위의 오버랩 이벤트들은 콜리전 컴포넌트의 델리게이트에 바인딩될 것이다.
따라서, 바인딩할 때 요구하는 함수 시그니처에 맞게 함수를 작성했다.
1
2
SphereComponent->OnComponentBeginOverlap.AddDynamic(this, &AC_ItemBase::OnItemOverlap);
SphereComponent->OnComponentEndOverlap.AddDynamic(this, &AC_ItemBase::OnItemEndOverlap);
위처럼 나중에 충돌 컴포넌트를 생성한 후, 충돌 컴포넌트의 델리게이트에 함수를 바인딩해 준다.
1
2
UPROPERTY(BlueprintAssignable, Category="Collision")
FComponentBeginOverlapSignature OnComponentBeginOverlap;
이 델리게이트는 다음과 같이 생겼다.
1
2
3
4
5
6
7
8
void AC_ItemBase::OnItemOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OverlapActor,
UPrimitiveComponent* OverlapComp, int32 OtherActorIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OverlapActor && OverlapActor->ActorHasTag("Player"))
{
ActivateItem(OverlapActor);
}
}
랜덤 스폰
랜덤 스폰은 액터에 박스 컴포넌트를 추가해, 박스 영역 내 무작위 좌표에 스폰하는 방식을 사용했다.
이처럼 특정 영역을 지정하고, 영역 내에서 랜덤하게 스폰하는 방식을 Spawn Volume이라고 한다.
이 방식을 사용하면, 개발자가 아니어도 박스 컴포넌트의 크기를 조정해 스폰 영역을 쉽게 설정할 수 있어 편리하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
UCLASS()
class OPENWORLDRPG_API AC_SpawnVolume : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AC_SpawnVolume();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
USceneComponent* Scene;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning")
UBoxComponent* SpawningBox;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Spawning")
UDataTable* ItemDataTable;
UFUNCTION(BlueprintCallable, Category = "Spawning")
void SpawnRandomItem();
FVector GetRandomPointVolume() const;
void SpawnItem(TSubclassOf<AActor> ItemClass);
FItemSpawnRow* GetRandomItem() const;
};
스포너도 배치되어야 하므로 Scene
이 필요하며, SpawningBox
를 사용해 스폰 영역을 설정해 준다.
아이템 생성에 필요한 데이터는 데이터 테이블에서 가져온다.
스폰 과정은 블루프린트에서 SpawnRandomItem
을 호출하면, 그 아래 3개의 함수가 실행되면서 랜덤 아이템을 생성하는 구조다.
에디터에서 블루프린트 클래스로 스포너를 생성한 다음, 이벤트 그래프에서 SpawnRandomItem
을 호출한다.
데이터 테이블
라이라에서는 데이터를 불러올 때 프라이머리 데이터 에셋만 사용하는 것을 봤기 때문에, 데이터 테이블에 대해서는 몰랐다.
데이터 테이블의 장점은 JSON 편집을 사용할 수 있으며, 빠른 접근이 가능하다는 점이다.
반면, **동적으로 값을 변경할 수 없으며, 동적 로딩이 가능하지는 않다.
데이터 테이블 생성 방법
FTableRowBase
를 상속받는 구조체를 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
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;
};
데이터는 아이템 이름, 아이템 클래스, 스폰 확률 3가지를 저장하도록 했다.
그다음, 에디터에서 데이터 테이블을 생성한다.
(우클릭 → 기타 → 데이터 테이블)
맵마다 아이템의 등장 확률이 다르므로, 각 맵마다 개별적인 데이터 테이블을 생성해 줬다.
데이터 값을 입력하고 저장한 후,
BP_ItemSpawningVolume
의 컴포넌트에서 필요한 데이터 테이블을 연결하면 된다.
각 스포너마다 다르게 적용할 것이므로, 에디터 창의 디테일 패널에서 개별적으로 설정하면 끝이다.
게임 스테이트
GameState
는 전역적인 게임 데이터를 저장하는 클래스다.
하지만 단순한 데이터 저장소가 아니라, 게임의 진행 상태를 관리하는 것이 핵심 역할이다.
때문에 싱글플에이에서도 사용 가능하지만, 멀티플레이에서 더욱 중요한 역할을 한다.
📌 주요 기능
✔ 게임 진행 상태 (진행 중, 일시정지, 종료 등) 저장
✔ 점수, 남은 시간, 라운드 정보 등 저장
✔ 모든 플레이어가 공유해야 하는 데이터 관리
멀티플레이어선 GameState
가 서버에서 관리되며, 클라이언트와 자동으로 동기화된다.
이번에는 아이템 중 코인 아이템이 점수를 획득하는 효과를 가지도록 만들었으며, 이 점수 데이터를 GameState
에서 관리하게 했다.
언리얼에서는 World
를 통해 GameState
를 가져올 수 있다.
예제 코드:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void AC_CoinItem::ActivateItem(AActor* Activator)
{
Super::ActivateItem(Activator);
if (UWorld* World = GetWorld())
{
if (AC_GameStateBase* GameState = World->GetGameState<AC_GameStateBase>())
{
GameState->AddScore(PointValue);
}
}
DestroyItem();
}