아이템 구현에 대한 내용과 연결되는 내용입니다.
1. Game Loop
게임이 진행되는 흐름을 말한다. (Game Flow라고도 한다)
언리얼 엔진에서는 게임 루프나 전역 상태를 위해 GameState나 GameMode를 사용한다.
- 주의할 점
- GameState나 GameMode 모두 Base가 뒤에 붙은 클래스가 존재한다.
- GameMode와 GameState는 둘 간의 호환성이 중요하기 때문에,
- 둘 다 Base로 만들던지, 아니면 둘 다 Base가 아닌 것으로 만들지 맞춰주어야 한다.
- GameMode
- 게임 규칙(승패 조건, 플레이어 스폰 등)을 서버에서 제어하는데 사용
- 서버 전용 로직을 담는 곳이다. (멀티 플레이 게임)
- 클라이언트가 알아야 하는 정보의 경우 GameMode에 두지 않는다.
- 우리의 구현에 있어서는, default pawn이나 player controller를 설정할 때 사용했다.
- GameState
- 게임 전반에 걸쳐 모든 플레이어가 공유해야 하는 상태를 담은 클래스이다.
- 전역 상태가 필요한 경우 활용한다.
- 게임이 시작되면, 서버에서 GameState가 생성되고 클라이언트는 이것을 복제해서 사용
- GameMode에서 말한 게임 규칙들을 싱글플레이 게임의 경우 GameState에 구현한다.
- GameInstance
- 모든 레벨에 걸쳐 유지하는 정보
- 애플리케이션이 종료될 때 까지 유일하게 살아있음, 맵이 전환되어도 파괴되지 않음
- 주로 싱글 게임에서 사용
2. 게임 루프 설계
- 게임 시작
- GameInstance 생성, GameState, GameMode 생성
- BeginPlay
- 아이템 스폰
- 맵에 스폰된 아이템의 정보 받아오기
- 타이머 시작
- 다음 레벨 이동
- 맵에 있는 동전을 특정 갯수만큼 먹은 경우
- 30초가 지난 경우
- 주의)
- GameState, GameMode 및 대부분의 객체는 새로 생성된다!
- GameInstance로 데이터 유지하는 것 필요하다.
- 게임 오버
- 마지막 레벨(레벨3) 에서 "다음 레벨 이동" 로직이 실행된 경우
- 캐릭터의 체력이 0이 된 경우
수정할 내용
- 기존 구현에서는 SpawnVolume에서 SpawnItem이라는 함수를 통해서 랜덤한 아이템을 소환하였다.
- 수정) 게임 시작 시,
- GameState에서 소환할 아이템의 갯수를 지정하고 스폰하며,
- 스폰한 아이템 중 동전 아이템의 갯수를 알고 있어야 함
- OnCoinCollected() 라는 함수로 현재 모은 동전 아이템의 갯수가 생성된 동전 갯수보다 많아지면, 다음 레벨로 이동할 수 있는 로직 추가
- 해당 함수는 Coin 클래스에서 이전에 구현한 GameState를 통해 call 하게 된다.
3. GameState 설계
3 - 1. 수정된 Spawn Volume 클래스
- 아래 두 함수의 return 값을 AActor* 로 만들어서, GameState에서 spawn 한 아이템의 정보를 받아올 수 있도록 구현하였다.
- 기억할 점) GetWorld()->SpawnActor<>() 를 AActor* 로 리턴값을 받아서 저장할 수 있다.
//SpawnVolume.cpp
AActor* ASpawnVolume::SpawnItem(TSubclassOf<AActor> ItemClass)
{
if (!ItemClass)
return nullptr;
AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(
ItemClass
, GetRandomPointInVolume()
, FRotator::ZeroRotator
);
return SpawnedActor;
}
AActor* ASpawnVolume::SpawnRandomItem()
{
if (FItemSpawnRow* SelectedRow = GetRandomItem())
{
if (UClass* ActualClass = SelectedRow->ItemClass.Get())
{
return SpawnItem(ActualClass);
}
}
return nullptr;
}
3 - 2. GameInstance
- 레벨 전환 시 GameState와 GameMode는 새로 생성된다. 그러나 점수나 현재 레벨의 정보 등은 게임이 끝나기 전까지 사라지면 안되기 때문에, GameInstance에 구현해야 한다.
- GameInstance에는 게임 전체 누적 점수(점수 획득 함수) 및, 현재 레벨 인덱스에 대한 정보를 저장하였다.
#include "SpartaGameInstance.h"
USpartaGameInstance::USpartaGameInstance()
:TotalScore(0)
,CurrentLevelIndex(0)
{
}
void USpartaGameInstance::AddToScore(int32 Amount)
{
TotalScore += Amount;
UE_LOG(LogTemp, Warning, TEXT("Total Score: %d"), TotalScore);
}
3 - 3. GameState 구현
- BeginPlay에서 StartLevel 함수를 수행
- StartLevel 함수
- GameInstance를 통해 전역 데이터를 저장 (레벨 인덱스)
- SpawnVolume을 가져와서 아이템 스폰
- 타이머 시작
- 기억할 것)
- IsA() 함수를 통해 유효성 체크를 수행할 수 있다.
- StartLevel 함수
void ASpartaGameState::StartLevel()
{
if (UGameInstance* GameInstance = GetGameInstance())
{
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance)
{
//현재 레벨 인덱스 저장
CurrentLevelIndex = SpartaGameInstance->CurrentLevelIndex;
}
}
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++;
}
}
}
}
GetWorldTimerManager().SetTimer(
LevelTimerHandle
, this
, &ASpartaGameState::OnLevelTimeUp
, LevelDuration
, false);
UE_LOG(LogTemp, Warning, TEXT("Level %d start spawned %d coins"),
CurrentLevelIndex + 1
, SpawnedCoinCount);
}
- 모든 동전을 모았거나, 시간이 지났거나 캐릭터의 체력이 0 일때 EndLevel 함수 실행
- EndLevel 함수
- 타이머 리셋
- 레벨 인덱스 로직 수행
- 레벨 인덱스 올려주기
- 유효하다면, 다음 레벨 열기
- 유효하지 않다면, 게임 오버
- 기억할 것)
- UGameplayStatics::OpenLevel 을 통해 다음 레벨을 열어주는 로직 수행 가능
- 이때 TArray<FName> 타입의 레벨을 저장하는 Array를 사용 (BP에서 세팅함)
- EndLevel 함수
void ASpartaGameState::EndLevel()
{
GetWorldTimerManager().ClearTimer(LevelTimerHandle);
CurrentLevelIndex++;
if (UGameInstance* GameInstance = GetGameInstance())
{
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance)
{
AddScore(Score);
SpartaGameInstance->CurrentLevelIndex = CurrentLevelIndex;
}
}
if (CurrentLevelIndex >= MaxLevelIndex)
{
OnGameOver();
return;
}
if (LevelMapNames.IsValidIndex(CurrentLevelIndex))
{
UGameplayStatics::OpenLevel(GetWorld(), LevelMapNames[CurrentLevelIndex]);
}
else
{
OnGameOver();
}
}
- 실행 모습 1
- level 1시작
- 아이템 획득 체크
- Total Score
- 실행 모습2
- 시간이 지나서 자동으로 Level 2수행
- Total Score는 유지
- Coin 수는 Reset
'Unreal Engine' 카테고리의 다른 글
UE5 Issues : Complex Collision (0) | 2025.02.11 |
---|---|
UE5 Issues : Look Action (bUsePawnControlRotation) (0) | 2025.02.11 |
Unreal Engine - 아이템 스폰 및 캐릭터와 연동 (0) | 2025.02.07 |
Unreal Engine - 아이템 만들기 (+충돌 처리) (1) | 2025.02.06 |
Unreal Engine - Simple Niagara (with cpp) (0) | 2025.02.04 |