Post

UE - 랜덤 아이템 스폰

UnrealEngine

UE - 랜덤 아이템 스폰

아이템

아이템은 아이템 인터페이스를 만들어서 생성했다.

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();  
}
This post is licensed under CC BY 4.0 by the author.