이득우의 언리얼 C++ 게임 개발의 정석을 읽고 개인 공부 목적으로 요약 정리한 글입니다!
SaveGame 시스템은 게임의 데이터를 저장하고 불러들일 수 있는 기능이다.
언리얼 엔진이 지원하니 언리얼 오브젝트를 상속받은 클래스를 생성하면 된다.
SaveGame 시스템을 사용하면,
에디터에서 저장할 경우 일반적으로 Saved/SaveGames/
폴더에 저장된다.
슬롯 이름 : 각 저장 파일에 접근할 수 있는 고유 이름
슬롯 이름 별로 세이브 데이터를 구분할 수 있다.
void AMyPlayerState::SavePlayerData()
{
UMySaveGame* NewPlayerData = NewObject<UMySaveGame>();
NewPlayerData->PlayerName = GetPlayerName();
NewPlayerData->Level = CharacterLevel;
NewPlayerData->Exp = Exp;
NewPlayerData->HighScore = GameHighScore;
NewPlayerData->CharacterIndex = CharacterIndex;
if (!UGameplayStatics::SaveGameToSlot(NewPlayerData, SaveSlotName, 0)) {
HUNT_LOG(Error, TEXT("SaveGame Error"));
}
}
void AMyPlayerState::AddGameScore()
{
GameScore++;
if (GameScore >= GameHighScore) {
GameHighScore = GameScore;
}
OnPlayerStateChanged.Broadcast();
SavePlayerData();
}
NewObject
언리얼 오브젝트 생성 시
NewObject
명령을 사용해서 생성한다.
이 오브젝트를 더 이상 사용하지 않으면,
Garbage Collector가 자동으로 언리얼 오브젝트를 소멸시켜준다.
오브젝트 삭제를 위해 delete하지 않아도 된다.
타이틀 화면에서는 UI만 띄울거고,
필요한 것은
UI, 게임 모드, UI를 띄울 플레이어 컨트롤러
UI를 담당할 플레이어 컨트롤러를 C++ 클래스로 만든다
(UI 인스턴스를 만들고, 화면에 띄워서, 입력이 UI에 전달되도록 해준다)
void AMyUIPlayerController::BeginPlay()
{
Super::BeginPlay();
HUNT_CHECK(nullptr != UIWidgetClass);
UIWidgetInstance = CreateWidget<UUserWidget>(this, UIWidgetClass);
HUNT_CHECK(nullptr != UIWidgetInstance);
UIWidgetInstance->AddToViewport();
FInputModeUIOnly Mode;
Mode.SetWidgetToFocus(UIWidgetInstance->GetCachedWidget());
SetInputMode(Mode);
bShowMouseCursor = true;
}
그리고
이 C++ 클래스로부터 블루프린트를 만들고
이 블루프린트의 UIWidgetClass를 UI로 설정한다.
그리고
GameModeBase를 블루프린트를 통해 새로 만들고
DefaultPawnClass를 Pawn
으로
PlayerControllerClass를 BP_TitleUIPlayerController
로 설정해준다
(블루프린트가 아니라 그냥 C++클래스로하면 UIWidgetClass가 설정이 안되니 주의.
C++ 클래스로 하려면 하드코딩해주거나, 에디터에서 설정하도록 프로퍼티 값을 변경하자.)
이렇게 하면
이렇게 잘 나온다.
위젯의 내용은
이렇게 되어있고,
C++로도 구현할 수 있다.
현재 월드에 있는 특정 타입을 상속받은 액터의 목록 구하는 함수 :
TActorIterator<액터타입>
for (TActorIterator<ASkeletalMeshActor> It(GetWorld()); It; ++It) { TargetComponent = It->GetSkeletalMeshComponent(); break; }
이렇게 하면 ASkeletalMeshActor 타입을 상속받은 액터들의 목록에 대한 for문이 되는거다.
게임 시스템에 관련된 입력을 처리할 때는 PlayerController에서 처리한다.
플레이어 컨트롤러에서 게임 중지 관련 입력처리를 하면
빙의한 폰에 상관없이 입력을 처리할 수 있으므로,,
플레이 일시정지 함수 : PlayerController의
SetPause
마우스 커서 보이도록 설정하는 법 :
bShowMouseCursor
속성 변경
void AMyPlayerController::OnGamePause()
{
MenuWidget = CreateWidget<UMyGameplayWidget>(this, MenuWidgetClass);
HUNT_CHECK(nullptr != MenuWidget);
MenuWidget->AddToViewport(3);
SetPause(true);
ChangeInputMode(false);
}
void AMyPlayerController::ChangeInputMode(bool bGameMode)
{
if (bGameMode) {
SetInputMode(GameInputMode);
bShowMouseCursor = false;
}
else {
SetInputMode(UIInputMode);
bShowMouseCursor = true;
}
}
현재 뷰포트에 띄워진 UI 제거하는 함수 :
RemoveFromParent
UI에서 자신을 관리하는 PlayerController의 정보를 알아오는 법 :
GetOwningPlayer
void UMyGameplayWidget::OnResumeClicked()
{
auto MyPlayerController = Cast<AMyPlayerController>(GetOwningPlayer());
HUNT_CHECK(nullptr != MyPlayerController);
RemoveFromParent();
MyPlayerController->ChangeInputMode(true);
MyPlayerController->SetPause(false);
}
결과 화면 UI의 인스턴스를 시작할 때 만들어 놓고,
게임이 종료될 때 만들어진 인스턴스를 화면에 띄운다 (AddToViewport)
게임이 종료되는 조건은
GameState의 정보를 통해 결정되며,
게임이 종료되면
GameMode에서
폰의 입력을 멈추고,
PlayerController에게 결과 UI를 띄우라고 시킨다.
void AHunt_PrototypeGameModeBase::AddScore(AMyPlayerController* ScoredPlayer)
{
...
if (GetScore() >= ScoreToClear) {
MyGameState->SetGameCleared();
for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It) {
(*It)->TurnOff();
}
for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It) {
const auto MyPlayerController = Cast<AMyPlayerController>(It->Get());
if (nullptr != MyPlayerController) {
MyPlayerController->ShowResultUI();
}
}
}
}
UI의
NativeConstruct
:AddToViewport
함수가 외부에서 호출될 때, UI 위젯이 초기화되며 호출된다.
그래서
PlayerController의 ShowResultUI
함수에서 AddToViewport
함수를 호출하는데,
그 전에 미리 UI 위젯이 GameState의 정보을 바인딩하도록 한다.
유니티만 10년 넘게 하다가 이번에 언리얼로 전향하려고 공부 중입니다.
많은 도움이 됐습니다~ 감사합니다!