티스토리 뷰

🟩 오늘의 목표

  • GameState를 활용해 레벨별 시간 제한, 코인 수집 개수 확인 등 전역 게임 루프를 설계한다.
  • SpawnVolume이 스폰된 액터를 반환하도록 수정하여 생성된 아이템의 타입을 추적한다.
  • 레벨 전환 시에도 데이터가 유지될 수 있도록 Game Instance의 개념과 활용법을 익힌다.

🟧 1. GameState를 이용한 게임 루프 구현

🟦 GameState와 GameMode의 역할 분담

  • GameMode : 서버 전용 로직과 게임의 규칙(팀 배정, 승패 조건 등)을 정의한다. 클라이언트는 접근할 수 없다.
  • GameState : 모든 플레이어가 공유해야 하는 상태(남은 시간, 점수 등)를 관리한다. 서버에서 생성되어 클라이언트로 복제된다.
  • 이번 프로젝트에서는 멀티플레이 확장성을 고려하여 클라이언트도 알아야 할 정보가 많으므로 GameState에 루프 로직을 구현한다.

🟦 SpawnVolume 스폰 데이터 반환 수정

스폰된 아이템이 코인인지 확인하여 카운팅하기 위해 SpawnRandomItem 함수의 리턴 타입을 AActor*로 변경한다.

// SpawnVolume.cpp 핵심 수정
AActor* ASpawnVolume::SpawnRandomItem()
{
    if (FItemSpawnRow* SelectedRow = GetRandomItem())
    {
        if (UClass* ActualClass = SelectedRow->ItemClass.Get())
        {
            // 스폰된 액터 포인터를 리턴하여 외부에서 타입을 확인할 수 있게 함
            return SpawnItem(ActualClass);
        }
    }
    return nullptr;
}

AActor* ASpawnVolume::SpawnItem(TSubclassOf<AActor> ItemClass)
{
    if (!ItemClass) return nullptr;
    
    AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(
        ItemClass,
        GetRandomPointInVolume(),
        FRotator::ZeroRotator
    );
    return SpawnedActor;
}

🟦 SpartaGameState를 통한 게임 루프 설계

레벨 시작 시 아이템을 스폰하고, 타이머를 설정하며, 코인 수집 현황에 따라 레벨을 종료하는 로직을 관리한다.

// SpartaGameState.cpp 주요 루프 로직
void ASpartaGameState::StartLevel()
{
    SpawnedCoinCount = 0;
    CollectedCoinCount = 0;

    TArray<AActor*> FoundVolumes;
    UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundVolumes);

    const int32 ItemToSpawn = 40;
    for (int32 i = 0; i < ItemToSpawn; i++)
    {
        if (FoundVolumes.Num() > 0)
        {
            ASpawnVolume* SpawnVolume = Cast<ASpawnVolume>(FoundVolumes[0]);
            if (SpawnVolume)
            {
                AActor* SpawnedActor = SpawnVolume->SpawnRandomItem();
                // 스폰된 액터가 코인이면 카운트 증가
                if (SpawnedActor && SpawnedActor->IsA(ACoinItem::StaticClass()))
                {
                    SpawnedCoinCount++;
                }
            }
        }
    }

    // 30초 제한 시간 타이머 설정
    GetWorldTimerManager().SetTimer(LevelTimerHandle, this, &ASpartaGameState::OnLevelTimeUp, LevelDuration, false);
}

void ASpartaGameState::OnCoinCollected()
{
    CollectedCoinCount++;
    // 모든 코인을 주웠다면 즉시 레벨 종료
    if (SpawnedCoinCount > 0 && CollectedCoinCount >= SpawnedCoinCount)
    {
        EndLevel();
    }
}

🟧 2. Game Instance를 활용한 데이터 유지

🟦 레벨 전환과 데이터 유실 문제

UGameplayStatics::OpenLevel을 통해 맵을 이동하면 기존의 GameStateGameMode는 파괴되고 새로 생성되어 모든 변수가 초기화된다.

🟦 USpartaGameInstance의 역할

  • Game Instance : 게임 애플리케이션이 실행될 때 생성되어 종료될 때까지 유지되는 유일한 객체다.
  • TotalScoreCurrentLevelIndex처럼 맵이 바뀌어도 유지되어야 하는 전역 데이터를 저장하는 데 사용한다.
// SpartaGameInstance.h
UCLASS()
class SPARTAPROJECT_API USpartaGameInstance : public UGameInstance
{
    GENERATED_BODY()
public:
    USpartaGameInstance();

    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "GameData")
    int32 TotalScore;

    UFUNCTION(BlueprintCallable, Category = "GameData")
    void AddToScore(int32 Amount);
};

🟧 핵심 요약 및 주의사항

  • OpenLevel 주의사항 : 맵 전환 시 GameState의 데이터가 날아가므로, 누적 점수 등은 반드시 GameInstance에 따로 저장해야 한다.
  • IsA 함수 활용 : 스폰된 액터가 특정 클래스(ACoinItem)인지 확인할 때 매우 유용하다.
  • 타이머 관리 : 레벨이 강제 종료되거나 전환될 때는 ClearTimer를 통해 돌아가고 있는 타이머를 정리해줘야 버그를 방지할 수 있다.
  • 코인 수집 알림 : CoinItem이 파괴되기 직전에 GameState->OnCoinCollected()를 호출하여 수집 상태를 갱신해야 한다.

'내일배움캠프 Unreal_7기 > 본캠프' 카테고리의 다른 글

[CH 3] 팀프로젝트 - 쌍권총 연사 시스템 구현  (0) 2026.02.09
TIL - 47일  (0) 2026.02.03
TIL - 45일차  (0) 2026.01.30
TIL - 44일차  (0) 2026.01.29
TIL - 43일차  (1) 2026.01.28