📍 3주차 5강
게임 루프 게임 시작 ~ 종료까지 수행하는 단계들
게임 플로우 플레이어가 게임을 시작, 진행, 끝내는 과정에서 발생하는 모든 상호작용들 (레벨 전환 등)
GameMode
GameState
30초 제한 안에 40개 아이템을 모두 먹으면 다음 레벨 넘어감
→ 이런 게임 상태 공유가 중요하므로 GameState 사용!
그리고 GameState가 GameStateBase보다 GameMode와 더 긴밀히 연결되어 있으므로
9.1.c에서 GameState로 변경하기
스폰된 아이템의 정보(coin이 맞음?)를 GameState에서 카운팅 하기 위해 spawn 함수를 void → 스폰된 AActor* 로 반환하기
UFUNCTION(BlueprintCallable, Category = "Spawning")
AActor* SpawnRandomItem();
FVector GetRandomPointInVolume() const;
FItemSpawnRow* GetRandomItem() const;
AActor* SpawnItem(TSubclassOf<AActor> ItemClass);
// 아이템 랜덤으로 갖고 오는 함수
AActor* ASpawnVolume :: SpawnRandomItem()
{
if (FItemSpawnRow* SelectedRow = GetRandomItem())
{
if (UClass* ActualClass = SelectedRow->ItemClass.Get())
{
// // 여기서 SpawnItem()을 호출하고, 스폰된 AActor 포인터를 리턴
return SpawnItem(ActualClass);
}
}
return nullptr; // 실패 시
}
// ...
// 실질적으로 Spawn해주는 함수
AActor* ASpawnVolume::SpawnItem(TSubclassOf<AActor> ItemClass)
{
// 안전 코드
if (!ItemClass) return nullptr;
// SpawnActor가 성공하면 스폰된 액터의 포인터가 반환됨
AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(
ItemClass,
GetRandomPointInVolume(), // 위치 (랜덤 값 갖고 옴)
FRotator::ZeroRotator // 회전은 안하게
);
return SpawnedActor;
}
| 항목 | GameStateBase | GameState |
|---|---|---|
| 부모/자식 | 부모 클래스 | 자식 클래스 (GameStateBase 상속) |
| 기능 수준 | 최소한의 상태 기능 제공 | 상태 관련 기능이 더 많음 |
| 추가된 기능 | 없음 | 시간 관리, 경기 흐름 동기화 등 |
| 주로 쓰는 상황 | 커스텀 상태만 간단히 관리할 때 | 일반적인 게임 진행 상황 관리할 때 |
| 멀티플레이 | 지원 | 멀티플레이에 최적화 |
자동 연결 구조
직접 참조 가능
역할 분담이 명확
| 클래스 | 역할 |
|---|---|
| GameMode | 판단: 규칙 처리 (언제 끝나는지, 승리 조건 등) |
| GameState | 기록: 상태 저장 (시간, 점수, 현재 라운드 등) |


// SpartaGameState.h
public:
ASpartaGameState(); // 생성자
void StartLevel();
// SpartaGameState.cpp
void ASpartaGameState::BeginPlay()
{
Super::BeginPlay();
StartLevel();
}
void ASpartaGameState::StartLevel()
{
}
// SpartaGameState.h
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coin")
int32 SpawnedCoinCount;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coin")
int32 CollectedCoinCount;
// SpartaGameState.cpp
#include "Kismet/GamePlayStatics.h"
#include "SpawnVolume.h"
#include "CoinItem.h"
ASpartaGameState::ASpartaGameState()
{
Score = 0;
// 전체 초기화
// coin 관련 카운트 초기화 하는 변수
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
}
void ASpartaGameState::StartLevel()
{
// 레벨을 불러올 때마다 초기화
// coin 관련 카운트 초기화 하는 변수
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
// World에 SpawnVolume이 몇 개 있는지 세고 거기에 아이템 40개 스폰함
// 찾은 Volume을 배열 형태로 저장
TArray<AActor*> FoundVolumes;
// 현재 월드에서 Actor에 해당되는 모든 걸 가져옴 -> 찾은 것들을 FoundVolumes에 넣음
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundVolumes);
// 찾은 볼륨들에 몇 개를 스폰할지 지정
const int32 ItemToSpawn = 40;
for (int32 i = 0; i < ItemToSpawn; i++)
{
if (FoundVolumes.Num() > 0) // 0개 이상인지 확인
{
ASpawnVolume* SpawnVolume = Cast<ASpawnVolume>(FoundVolumes[0]);
if (SpawnVolume)
{
AActor* SpawnedActor = SpawnVolume->SpawnRandomItem(); // 9.1.b에서 void 바꿔놨음
// IsA: Coin 맞음??, 하위클래스까지 인식
if (SpawnedActor && SpawnedActor->IsA(ACoinItem::StaticClass()))
{
SpawnedCoinCount++;
}
}
}
}
}
// SpartaGameState.h
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Level")
float LevelDuration;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Level")
int32 CurrentLevelIndex;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Level")
int32 MaxLevels;
FTimerHandle LevelTimerHandle;
UFUNCTION(BlueprintCallable, Category = "Level")
void OnGameOver();
void StartLevel();
void OnLevelTimeUp();
void EndLevel();
// SpartaGameState.cpp
ASpartaGameState::ASpartaGameState()
{
Score = 0;
// 전체 초기화
// coin 관련 카운트 초기화 하는 변수
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
LevelDuration = 30.0f;
CurrentLevelIndex = 0;
MaxLevels = 3;
}
// 2번의 for문 안에 추가
// 30초 후에 OnLevelTimeUp()가 호출되도록 타이머 설정
GetWorldTimerManager().SetTimer(
LevelTimerHandle,
this,
&ASpartaGameState::OnLevelTimeUp,
LevelDuration,
false
);
UE_LOG(LogTemp, Warning, TEXT("Level %d Start!, Spawned %d coin"),
CurrentLevelIndex + 1,
SpawnedCoinCount);
void ASpartaGameState::OnLevelTimeUp()
{
// 시간이 다 되면 레벨을 종료
EndLevel();
}
void ASpartaGameState::EndLevel()
{
// 타이머 해제
GetWorldTimerManager().ClearTimer(LevelTimerHandle);
// 다음 레벨 인덱스로
CurrentLevelIndex++;
// 모든 레벨을 다 돌았다면 게임 오버 처리
if (CurrentLevelIndex > MaxLevels)
{
OnGameOver();
}
else
{
StartLevel();
}
}
void ASpartaGameState::OnGameOver()
{
UE_LOG(LogTemp, Warning, TEXT("Game Over!!"))
// 여기서 UI를 띄운다거나, 재시작 기능을 넣을 수도 있음 -> 나중에 추가
}
// SpartaGameState.h
// 코인을 주웠을 때 호출
void OnCoinCollected();
// SpartaGameState.cpp
void ASpartaGameState::OnCoinCollected()
{
CollectedCoinCount++;
UE_LOG(LogTemp, Warning, TEXT("Coin Collected: %d / %d"),
CollectedCoinCount,
SpawnedCoinCount)
// 현재 레벨에서 스폰된 코인을 전부 주웠다면 즉시 레벨 종료
if (SpawnedCoinCount > 0 && CollectedCoinCount >= SpawnedCoinCount)
{
EndLevel();
}
}
// SpartaGameState.h
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Level")
TArray<FName> LevelMapNames;
// SpartaGameState.cpp
void ASpartaGameState::EndLevel()
{
// 타이머 해제
GetWorldTimerManager().ClearTimer(LevelTimerHandle);
// 다음 레벨 인덱스로
CurrentLevelIndex++;
// 모든 레벨을 다 돌았다면 게임 오버 처리
if (CurrentLevelIndex >= MaxLevels)
{
OnGameOver();
return;
}
if (LevelMapNames.IsValidIndex(CurrentLevelIndex))
{
UGameplayStatics::OpenLevel(GetWorld(), LevelMapNames[CurrentLevelIndex]);
}
else
{
OnGameOver();
}
}
▶ BP에서 레벨 이름 정해주기
➡ BP_SpawnVolume 가서 전에 만들었던 이벤트 그래프 지우기
➡ Project Settings에서 BP_GameState 설정하기
// CoinItem.cpp
void ACoinItem::ActivateItem(AActor* Activator)
{
if (Activator && Activator->ActorHasTag("Player"))
{
if (UWorld* World = GetWorld())
{
if (ASpartaGameState* GameState = World->GetGameState<ASpartaGameState>())
{
GameState->AddScore(PointValue); // 점수 올림
GameState->OnCoinCollected(); // ✅ 현재 레벨에서 먹은 코인 개수 알려줌
}
}
DestroyItem();
}
}
레벨 전환 시 상태 유지 방법 (with 싱글톤)
레벨 전환 시 GameMode, GameState 등은 초기화됨
→ 점수나 진행 상태 같은 데이터는 따로 보존해야함
GameInstance
Seamless Travel
정리

// SpartaGameInstance.h
UCLASS()
class SPARTAPROJECT_API USpartaGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
USpartaGameInstance(); // 생성자
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "GameData")
int TotalScore;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "GameData")
int32 CurrentLevelIndex;
// 총점수 함수
UFUNCTION(BlueprintCallable, Category = "GameData")
void AddToScore(int32 Amount)
};
// SpartaGameInstance.cpp
#include "SpartaGameInstance.h"
// 생성자
USpartaGameInstance::USpartaGameInstance()
{
TotalScore = 0;
CurrentLevelIndex = 0;
}
void USpartaGameInstance::AddToScore(int32 Amount)
{
TotalScore += Amount;
UE_LOG(LogTemp, Warning, TEXT("Total Score Updated: %d"), TotalScore);
}
이 게임인스턴스를 프로젝트의 기본으로 설정해야 레벨이 바뀌어도 유지됨
GameState에서 활용하기
// GameState.cpp
void ASpartaGameState::AddScore(int32 Amount)
{
// GameInstance 호출
if (UGameInstance* GameInstance = GetGameInstance())
{
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance)
{
SpartaGameInstance->AddToScore(Amount); // 전체 총점에 점수 추가
}
}
}
void ASpartaGameState::StartLevel()
{
if (UGameInstance* GameInstance = GetGameInstance())
{
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance)
{
CurrentLevelIndex = SpartaGameInstance->CurrentLevelIndex; // 임시 저장
}
}
//...
}
void ASpartaGameState::EndLevel()
{
// 타이머 해제
GetWorldTimerManager().ClearTimer(LevelTimerHandle);
// 다음 레벨 인덱스로
CurrentLevelIndex++;
if (UGameInstance* GameInstance = GetGameInstance())
{
AddScore(Score); // 레벨이 끝날 때마다 점수 더해줌
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance)
{
SpartaGameInstance->CurrentLevelIndex = CurrentLevelIndex;
}
}
///...
}
GameInstance 블루프린트로 만들어 적용하기


짠~ 완료