게임의 완성 (4) 일시정지와 클리어 화면

유영준·2023년 2월 1일
0
post-thumbnail

오늘은 아레나 배틀 마지막 챕터이다 마지막으로는 일시정지 기능과 게임 클리어 화면을 만들도록 하겠다
오늘 사용한 에셋은 링크에서 다운로드 가능하다

일시정지 화면 구현

먼저 다운로드 받은 에셋을 UI폴더에 넣고, 일시정지를 위한 버튼을 매핑하도록 한다

나의 경우에는 PauseP 키를 이용하였다

일시정지는 폰에 관계없이 입력을 받아야하기에, 플레이어 컨트롤러에 구현해주었다

ABPlayerController.h

UCLASS()
class ARENABATTLE_API AABPlayerController : public APlayerController
{
	
    ...

protected:
	virtual void BeginPlay() override;
	virtual void SetupInputComponent() override;

	...

private:
	void OnGamePause();

ABPlayerController.cpp

...

void AABPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();
	InputComponent->BindAction(TEXT("GamePause"), EInputEvent::IE_Pressed, this, &AABPlayerController::OnGamePause);
}

...

다음은 UI가 사용할 클래스를 UserWidget을 부모로 만들어준다

생성했다면, UI의 부모 클래스를 생성한 위젯으로 설정한다

플레이어 컨트롤러에 버튼을 눌렀을 경우 UI와 마우스가 나타나며 UI만 조작이 가능하도록 설정하는 함수를 만들어준다

ABPlayerController.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ArenaBattle.h"
#include "GameFramework/PlayerController.h"
#include "ABPlayerController.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API AABPlayerController : public APlayerController
{
	GENERATED_BODY()
	
public:
	AABPlayerController();

	virtual void PostInitializeComponents() override;
	virtual void OnPossess(APawn* aPawn)    override;

	class UABHUDWidget* GetHUDWidget() const;
	void  NPCKill(class AABCharacter* KilledNPC) const;
	void  AddGameScore() const;
	void  ChangeInputMode(bool bGameMode = true);
protected:
	
    ...
    
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = UI)
	TSubclassOf<class UABGameplayWidget> MenuWidgetClass;

private:
	void OnGamePause();

	...

	UPROPERTY()
	class UABGameplayWidget* MenuWidget;

	FInputModeGameOnly GameInputMode;
	FInputModeUIOnly   UIInputMode;

};

ABPlayerController.cpp

AABPlayerController::AABPlayerController()
{
	...
    
	static ConstructorHelpers::FClassFinder<UABGameplayWidget> UI_MENU_C(TEXT("/Game/Book/UI/UI_Menu.UI_Menu_C"));
	if (UI_MENU_C.Succeeded())
		MenuWidgetClass = UI_MENU_C.Class;

}

...

void AABPlayerController::ChangeInputMode(bool bGameMode)
{
	if (bGameMode)
	{
		SetInputMode(GameInputMode);
		bShowMouseCursor = false;
	}
	else
	{
		SetInputMode(UIInputMode);
		bShowMouseCursor = true;
	}
}

void AABPlayerController::BeginPlay()
{
	Super::BeginPlay();

	ChangeInputMode(true);

	HUDWidget = CreateWidget<UABHUDWidget>(this, HUDWidgetClass);
	HUDWidget->AddToViewport(1);


	ABPlayerState = Cast<AABPlayerState>(PlayerState);
	HUDWidget->BindPlayerState(ABPlayerState);
	ABPlayerState->OnPlayerStateChanged.Broadcast();
}

void AABPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();
	InputComponent->BindAction(TEXT("GamePause"), EInputEvent::IE_Pressed, this, &AABPlayerController::OnGamePause);
}

void AABPlayerController::OnGamePause()
{
	MenuWidget = CreateWidget<UABGameplayWidget>(this, MenuWidgetClass);
	MenuWidget->AddToViewport(3);

	SetPause(true);
	ChangeInputMode(false);
}

이제 UI에 기능들을 구현해준다

ABGameplayWidget.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ArenaBattle.h"
#include "Blueprint/UserWidget.h"
#include "ABGameplayWidget.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABGameplayWidget : public UUserWidget
{
	GENERATED_BODY()
	
protected:
	virtual void NativeConstruct() override;

	UFUNCTION()
	void OnResumeClicked();

	UFUNCTION()
	void OnReturnToTitleClicked();

	UFUNCTION()
	void OnRetryGameClicked();

protected:
	UPROPERTY()
	class UButton* ResumeButton;

	UPROPERTY()
	class UButton* ReturnToTitleButton;

	UPROPERTY()
	class UButton* RetryGameButton;
};

ABGameplayWidget.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABGameplayWidget.h"
#include "Components/Button.h"
#include "ABPlayerController.h"

void UABGameplayWidget::NativeConstruct()
{
	Super::NativeConstruct();

	ResumeButton = Cast<UButton>(GetWidgetFromName(TEXT("btnResume")));
	if (nullptr != ResumeButton)
		ResumeButton->OnClicked.AddDynamic(this, &UABGameplayWidget::OnResumeClicked);

	ReturnToTitleButton = Cast<UButton>(GetWidgetFromName(TEXT("btnReturnToTitle")));
	if (nullptr != ReturnToTitleButton)
		ReturnToTitleButton->OnClicked.AddDynamic(this, &UABGameplayWidget::OnReturnToTitleClicked);

	RetryGameButton = Cast<UButton>(GetWidgetFromName(TEXT("btnRetryGame")));
	if (nullptr != RetryGameButton)
		RetryGameButton->OnClicked.AddDynamic(this, &UABGameplayWidget::OnRetryGameClicked);

}

void UABGameplayWidget::OnResumeClicked()
{
	auto ABPlayerController = Cast<AABPlayerController>(GetOwningPlayer());

	RemoveFromParent();
	ABPlayerController->ChangeInputMode(true);
	ABPlayerController->SetPause(false);
}

void UABGameplayWidget::OnReturnToTitleClicked()
{
	UGameplayStatics::OpenLevel(GetWorld(), TEXT("Title"));
}

void UABGameplayWidget::OnRetryGameClicked()
{
	auto ABPlayerController = Cast<AABPlayerController>(GetOwningPlayer());
	ABPlayerController->RestartLevel();
}

클리어 화면 구현

다음은 클리어화면을 구현할 것이다 클리어 화면은 방금 만든 ABGameplayWidget 을 상속하는 클래스로 생성한다

먼저 UI에서 부모 클래스로 생성한 ABGameplayResultWidget을 지정해주고

게임의 클리어 여부는 게임의 정보이므로 게임 스테이트 클래스를 수정한다

ABGameState.h

public:
	AABGameState();

	void SetGameCleared();
	bool IsGameCleared() const;
	
    ...
    
private:

	...

	UPROPERTY(Transient)
	bool bGameCleared;
};

ABGameState.cpp

AABGameState::AABGameState()
{
	TotalGameScore = 0;
	bGameCleared   = false;
}

...

void AABGameState::SetGameCleared()
{
	bGameCleared = true;
}

bool AABGameState::IsGameCleared() const
{
	return bGameCleared;
}

다음은 게임의 클리어 여부를 판단하는 작업을 하기 위해 게임모드에서 클리어가 되면 모든 작업을 멈추고 UI를 생성하도록 한다

ABGameMode.h


...

private:
	UPROPERTY()
	int32 ScoreToClear;

ABGameMode.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABGameMode.h"
#include "ABCharacter.h"
#include "ABPlayerController.h"
#include "ABPlayerState.h"
#include "ABGameState.h"


AABGameMode::AABGameMode()
{

	...
    
	ScoreToClear		  = 2;
}

...

void AABGameMode::AddScore(AABPlayerController* ScoredPlayer)
{

	...

	if (GetScore() >= ScoreToClear)
	{
		ABGameState->SetGameCleared();

		for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
			(*It)->TurnOff();

		for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
		{
			const auto ABPlayerController = Cast<AABPlayerController>(It->Get());
			if (nullptr != ABPlayerController)
				ABPlayerController->ShowResultUI();
		}
	}
}

int32 AABGameMode::GetScore() const
{
	return ABGameState->GetTotalGameScore();
}

캐릭터가 죽었을 경우에도 종료화면이 나와야한다 다만 이때는 클리어상태가 아닌 실패화면으로 나올 것이다

ABCharacer.cpp

	case ECharacterState::DEAD:
	{
		
        ...

		GetWorld()->GetTimerManager().SetTimer(DeadTimerHandle, FTimerDelegate::CreateLambda([this]() ->void
		{
			if (bIsPlayer)
			ABPlayerController->ShowResultUI();
			else
				Destroy();
		}), DeadTimer, false);

		break;
	}

이제 실제 결과가 나타날 클래스를 입력한다

ABGameplayResultWidget.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ArenaBattle.h"
#include "ABGameplayWidget.h"
#include "ABGameplayResultWidget.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABGameplayResultWidget : public UABGameplayWidget
{
	GENERATED_BODY()
	
public:
	void BindGameState(class AABGameState* GameState);
	
protected:
	virtual void NativeConstruct() override;

private:
	TWeakObjectPtr<class AABGameState> CurrentGameState;
};

ABGameplayResultWidget.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABGameplayResultWidget.h"
#include "Components/TextBlock.h"
#include "ABGameState.h"

void UABGameplayResultWidget::BindGameState(AABGameState* GameState)
{
	CurrentGameState = GameState;
}

void UABGameplayResultWidget::NativeConstruct()
{
	Super::NativeConstruct();

	auto Result     = Cast<UTextBlock>(GetWidgetFromName(TEXT("txtResult")));
	Result->SetText(FText::FromString(CurrentGameState->IsGameCleared() ? TEXT("Mission Complete") : TEXT("Mission Failed")));

	auto TotalScore = Cast<UTextBlock>(GetWidgetFromName(TEXT("txtTotalScore")));
	TotalScore->SetText(FText::FromString(FString::FromInt(CurrentGameState->GetTotalGameScore())));
}

마지막으로 플레이어 컨트롤러에 바인딩하는 작업을 해준다

ABPlayerController.cpp

void AABPlayerController::ShowResultUI()
{
	auto ABGameState = Cast<AABGameState>(UGameplayStatics::GetGameState(this));
	ResultWidget->BindGameState(ABGameState);

	ResultWidget->AddToViewport();
	ChangeInputMode(false);
}

마지막으로 잘 작동하는지 확인하도록 한다

profile
토비폭스가 되고픈 게임 개발자

0개의 댓글