본문 바로가기

공부/이득우의 언리얼 프로그래밍

[Study] Part 2 - 게임의 완성 (15/15)

728x90
반응형

 

15강 게임의 완성

  • 정리
    • 게임 제작의 완성
      1. 게임 모드를 활용한 게임 규칙의 완성
      2. 전용 이벤트 함수를 활용한 블루프린트와의 연동
      3. 게임 데이터의 저장 방식의 이해
      4. 게임의 최종 빌드
  • 게임 모드
    • 멀티플레이를 포함해 게임에서 유일하게 존재하는 게임의 심판 오브젝트
    • 최상단에서 게임의 진행을 관리하며, 게임 판정에 관련된 중요한 행동을 주관하는데 적합함
    • 다양한 게임 규칙을 적용할 수 있도록 핵심 기능과 분리해 설계하는 것이 바람직
    • 게임의 상태와 플레이어의 상태를 별도로 저장할 수 있는 프레임웍을 제공함
    • 하나의 운동장에서 발야구, 축구, 야구등을 할 수 있도록 게임을 관장하는 규칙을 관리하는 부분이라고 이해하면 편하다. 그렇기 때문에 핵심 기능과는 분리해서 설계!

 

게임 모드

 

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ABGameInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UABGameInterface : public UInterface
{
	GENERATED_BODY()
};

class ARENABATTLE_API IABGameInterface
{
	GENERATED_BODY()

public:
	// 플레이어의 스코어가 변경될 때 호출
	virtual void OnPlayerScoreChanged(int32 NewPlayerScore) = 0;
	// 플레이어가 죽었을 때
	virtual void OnPlayerDead() = 0;
	// 현재 게임이 클리어 되었는지 확인하는 함수
	virtual bool IsGameCleared() = 0;
};
  • ABGameInterface.h

 

  • 인터페이스를 하나 만들어서, 다른 스코어가 변경되거나, 플레이어가 죽었을 때, 게임이 클리어가 되었는지 확인할 때 GameMode에 대한 직접적인 참조보다는 간접 참조를 위해 인터페이스를 구성한다.

 

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "Interface/ABGameInterface.h"
#include "ABGameMode.generated.h"

UCLASS()
class ARENABATTLE_API AABGameMode : public AGameModeBase, public IABGameInterface
{
	GENERATED_BODY()
	
public:
	AABGameMode();

	virtual void OnPlayerScoreChanged(int32 NewPlayerScore) override;
	virtual void OnPlayerDead() override;
	virtual bool IsGameCleared() override;
	
	// 게임을 클리어하기 위한 조건을 위한 점수
	// 현재 진행되고 있는 게임의 점수
	UPROPERTY(EditDefaultsOnly,BlueprintReadWrite, Category = Game)
	int32 ClearScrore;

	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Game)
	int32 CurrentScore;

	// 게임을 클리어했는지 판단하기 위한 변수
	UPROPERTY(VisibleInstanceOnly,BlueprintReadOnly, Category = Game)
	uint8 bIsCleared : 1;
};
  • AABGameMode.h
#include "Game/ABGameMode.h"
#include "ABGameMode.h"
#include "Player/ABPlayerController.h"

AABGameMode::AABGameMode()
{
	// ...
	
	ClearScrore = 3;

	// CurrentScore의 경우 게임의 규모가 조금 더 커진다면 PlayerState같은 객체에 보관하는 것이 좋다.
	CurrentScore = 0;
	bIsCleared = false;
}

// 플레이어의 현재 점수가 변경되었을 때
void AABGameMode::OnPlayerScoreChanged(int32 NewPlayerScore)
{
	CurrentScore = NewPlayerScore;

	// 스코어가 변했을 때 신호를 날려주기 위한
	// 1인 플레이기 때문에 FirstPlayerController를 가져와도 된다.
	AABPlayerController* ABPlayerController = Cast<AABPlayerController>(GetWorld()->GetFirstPlayerController());
	if (ABPlayerController)
	{
		ABPlayerController->GameScoreChanged(CurrentScore);
	}

	// 게임을 클리어했을 때
	if (CurrentScore >= ClearScrore)
	{
		bIsCleared = true;

		if (ABPlayerController)
		{
			ABPlayerController->GameClear();
		}
	}
}

// 플레이어가 죽었을 때
void AABGameMode::OnPlayerDead()
{
	// 플레이어가 죽었을 때 관련된 UI를 날리는 이벤트를 PlayerController에게 알려주기 위한
	AABPlayerController* ABPlayerController = Cast<AABPlayerController>(GetWorld()->GetFirstPlayerController());
	if (ABPlayerController)
	{
		ABPlayerController->GameOver();
	}
}

// 현재 게임을 클리어 하였는가?
bool AABGameMode::IsGameCleared()
{
	return bIsCleared;
}
  • AABGameMode.cpp
#include "Interface/ABGameInterface.h"

	// ...
		// 플레이어가 죽으면 게임이 종료되도록 GameMode를 통해 전달하기
		IABGameInterface* ABGameMode = Cast<IABGameInterface>(GetWorld()->GetAuthGameMode());
		if (ABGameMode)
		{
			ABGameMode->OnPlayerDead();
		}
	// ...
  • AABCharacterPlayer.cpp

 

  • GameMode 클래스에 다음과 같이 게임모드에 필요한 변수와 함수들을 추가해준다.
  • Gimmick 클래스와 CharacterPlayer 클래스에서 인터페이스 헤더를 추가해준 뒤, 다음과 같이 인터페이스GetAuthGameMode를 통해 GameMode의 함수들을 호출해주도록 한다.
  • 이후, PlayerController 클래스에서 UI 이벤트를 위해 로직을 구성해준다.

 

  • 간단히 게임을 저장하는 방법

New C++ Class>SaveGame

  • SaveGame이라는 부모 클래스를 가진 코드를 만들어 주고 사용해주면 된다.

 

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/SaveGame.h"
#include "ABSaveGame.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABSaveGame : public USaveGame
{
	GENERATED_BODY()
	
public:
	UABSaveGame();

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = Game)
	int32 RetryCount;
};
  • ABSaveGame.h
#include "Player/ABSaveGame.h"

UABSaveGame::UABSaveGame()
{
	RetryCount = 0;
}
  • ABSaveGame.cpp

~Saved>SaveGames>Player0.sav

 

  • RetryCount라는 변수를 통해 다시 플레이한 횟수를 카운팅 해준다. Save된 카운트는 다음 위치에 저장되어진다. BP에서 쓸 수 있도록 프로퍼티 설정을 해준다.
  • 플레이어0번의 세이브 기록이기 때문에 Player0으로 저장되는 모습을 볼 수 있다. 다음 파일을 삭제하면 0부터 시작된다.

 

게임을 시작하자, 우측 상단의 Game Retry Count가 7로 적혀있는 모습

 

UFUNCTION(BlueprintImplementableEvent, Category = Game, Meta = (DisplayName = "OnGameRetryCountCpp"))
void K2_OnGameRetryCount(int32 NewRetryCount);

protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = SaveGame)
	TObjectPtr<class UABSaveGame> SaveGameInstance;
  • ABPlayerController.h
void AABPlayerController::GameOver()
{
	K2_OnGameOver();

	// 게임이 끝나면 저장하기
	if (!UGameplayStatics::SaveGameToSlot(SaveGameInstance, TEXT("Player0"), 0))
	{
		UE_LOG(LogABPlayerController, Error, TEXT("Save Game Error!"));
	}

	K2_OnGameRetryCount(SaveGameInstance->RetryCount);
}

void AABPlayerController::BeginPlay()
{
	Super::BeginPlay();		// override의 경우 Super 붙이기

	FInputModeGameOnly GameOnlyInputMode;		// 구조체 선언
	SetInputMode(GameOnlyInputMode);			// 구조체 넘기기 (시작하자마자 포커스가 뷰 포트 안으로 들어가게 된다.)

	SaveGameInstance = Cast<UABSaveGame>(UGameplayStatics::LoadGameFromSlot(TEXT("Player0"), 0));
	if (SaveGameInstance)
	{
		SaveGameInstance->RetryCount++;
	}
	else
	{
		SaveGameInstance = NewObject<UABSaveGame>();
	}

	K2_OnGameRetryCount(SaveGameInstance->RetryCount);
}
  • ABPlayerController.cpp

 

  • ABPlayerController.h에서 ABSaveGAme에 대한 인스턴스를 만들어주고, BP에서 사용할 K2_OnGameRetryCount함수를 만들어 준다.
  • ABPlayerController.cpp에서 다음과 같이 BeginPlay에서 SaveGameInstance를 통해 세이브 파일이 존재한다면 RetryCount를 증가시켜주고, 아니라면 생성해주고 BP에 적용될 수 있도록 RetryCount를 넘겨준다.

 

BP_ABPlayerController

  • ABPlayerController.cppBeginPlayRetryCount를 바탕으로 BP에서 SetRetryCount 값을 채워주는 모습

 

BP_ABPlayerController

  • BP를 통해 게임을 플레이하면 UI가 생성되고 보여지도록 만들어주고, 게임을 플레이할 수 있도록 조금 다듬어 준다.

 

게임을 끝낸 모습

 

  • 게임 패키징

Project Settings>Project>Packaging

  • 패키징을 하게 되면 map에 참조된 에셋들만 포함되서 패키징이 된다. 하지만, 우리는 코드단에서 아이템등을 참조했기 때문에 함께 패키징 해줘야 하는데, 다음 셋팅에서 함께 패키징할 수 있게 설정할 수 있다.
  • Additional Asset Directories to Cook을 통해 함께 빌드 해주도록 정확히 명시해준다. 또한, 현재 사용할 map을 정확히 명시해주는 것도 좋다.
  • Shipping 빌드의 경우 가장 작은 용량으로 패키징을 진행할 수 있는 방법. 다만, 디버그라인 등 디버깅용 기능들은 포함되지 않은 채로 패키징 된다.

 

정상적으로 패키징 된 모습
게임 실행 화면

  • 전체화면이 되거나 전체화면을 풀기 위해서는 Alt+Enter를 입력해주면 된다.

 

 

 

 

해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part2 -언리얼 게임 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
 

이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 강의 | 이득우 - 인프런

이득우 | 대기업 현업자들이 수강하는 언리얼 C++ 프로그래밍 전문 과정입니다. 언리얼 C++ 프로그래밍을 사용해 핵&슬래시 로그라이크 게임 예제를 처음부터 끝까지 체계적으로 제작하는 방법을

www.inflearn.com

 

 

 

 

 

728x90