게임의 완성 (3) 타이틀 화면과 선택 화면 제작

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

오늘은 게임의 타이틀화면과 캐릭터 선택화면을 만들고, 각각의 기능을 엮으려 한다
사용한 에셋은 오늘또한 링크를 통해 다운로드 가능하다

타이틀 화면 제작

먼저 받은 에셋의 Chapter15 안에 있는 UI_Title을 프로젝트에 넣자

우클릭 후 비어있는 새로운 레벨을 생성한다

이름은 Title로 지정해주었다


이 레벨에서 사용할 게임 모드와 UI를 띄울 플레이어 컨트롤러를 생성해주어야 한다

플레이어 컨트롤러는 PlayerController 클래스를 부모로 하는 ABUIPlayerController 클래스를 생성해주자

플레이어 컨트롤러에서는 게임을 시작하면 UI를 불러오고, UI에서 입력이 가능하게끔 설정해준다

ABUIPlayerController.h

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

#pragma once

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

/**
 * 
 */
UCLASS()
class ARENABATTLE_API AABUIPlayerController : public APlayerController
{
	GENERATED_BODY()
	
protected:
	virtual void BeginPlay() override;

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = UI)
	TSubclassOf<class UUserWidget> UIWidgetClass;

	UPROPERTY()
	class UUserWidget* UIWidgetInstance;
};

ABUIPlayerController.cpp

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


#include "ABUIPlayerController.h"
#include "Blueprint/UserWidget.h"

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

	UIWidgetInstance = CreateWidget<UUserWidget>(this, UIWidgetClass);
	UIWidgetInstance->AddToViewport();

	FInputModeUIOnly Mode;
	Mode.SetWidgetToFocus(UIWidgetInstance->GetCachedWidget());
	SetInputMode(Mode);
	bShowMouseCursor = true;
}

컴파일을 완료했다면, 이를 기반으로 한 블루프린트를 생성한다

c++ 클래스를 우클릭 후 생성이 가능하다

생성한 블루프린트의 이름을 BP_TitleUIPlayerController 로 지어주자

블루 프린트의 위젯 클래스에는 다운 받은 에셋을 넣어준다


이번에는 게임모드를 생성한다

게임모드는 블루프린트를 사용해 생성해보겠다

Game Mode Base 로부터 파생되는 BP_TitleGameMode를 생성하고,

Default Pawn ClassPawn, Player Controller Class는 방금 생성한 BP_TitleUIPlayerController로 설정한다

마지막으로 좌측 위 드랍다운 메뉴에서 월드 세팅을 BP_TitleGameMode 로 설정해준다

플레이 시 타이틀 UI가 나타난다

UI에는 이미 구현된 블루프린트 로직이 있어 새로 시작하기 를 누르면 Select 레벨로, 이어하기를 누르면 GamePlay 레벨로 가게 된다

아직 현재 Select 레벨이 없기 때문에, 시작하기를 누르면 오류가 발생하게 된다

이제 캐릭터 선택창인 Select 레벨을 만들자


캐릭터 선택창 만들기

이번에는 다운로드 받은 에셋에 있는 UI_SelectSelect 레벨을 프로젝트에 넣어주도록 한다

아까 타이틀 화면에서 한 것과 동일하게 ABUIPlayerController 로부터 파생된 BP_SelectUIPlayerController를 생성하고

UIWidget Class 를 UI_Select로 설정한다

다음은 게임 모드 블루 프린트를 생성하고, 아까 전과 동일하게 클래스를 지정한다

Select 레벨의 월드 세팅을 BP_SelectGameMode 로 바꿔준다

이 상태에서 게임을 실행하게 되면 UI가 정상적으로 출력된다


이제 C++ 클래스 ABCharacterSelectWidget 을 만들고, UI의 버튼들이 동작하도록 하겠다

ABCharacterSelectWidget.h

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

#pragma once

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

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABCharacterSelectWidget : public UUserWidget
{
	GENERATED_BODY()
	
protected:
	UFUNCTION(BlueprintCallable)
	void NextCharacter(bool bForward = true);

	virtual void NativeConstruct() override;

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Character)
	int32 CurrentIndex;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Character)
	int32 MaxIndex;

	UPROPERTY()
	class UButton* PrevButton;

	UPROPERTY()
	class UButton* NextButton;

	UPROPERTY()
	class UEditableTextBox* TextBox;

	UPROPERTY()
	class UButton* ConfirmButton;

	TWeakObjectPtr<USkeletalMeshComponent> TargetComponent;

private:
	UFUNCTION()
	void OnPrevClicked();

	UFUNCTION()
	void OnNextClicked();

	UFUNCTION()
	void OnConfirmClicked();
};

ABCharacterSelectWidget.cpp

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


#include "ABCharacterSelectWidget.h"
#include "ABCharacterSetting.h"
#include "Engine/Assetmanager.h"
#include "EngineUtils.h"
#include "Animation/SkeletalMeshActor.h"
#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "ABSaveGame.h"
#include "ABPlayerState.h"

void UABCharacterSelectWidget::NextCharacter(bool bForward)
{
	bForward ? CurrentIndex++ : CurrentIndex--;

	if (CurrentIndex == -1)       CurrentIndex = MaxIndex - 1;
	if (CurrentIndex == MaxIndex) CurrentIndex = 0;

	auto CharacterSetting = GetDefault<UABCharacterSetting>();
	auto AssetRef = CharacterSetting->CharacterAssets[CurrentIndex];

	
	USkeletalMesh* Asset = UAssetManager::GetStreamableManager().LoadSynchronous<USkeletalMesh>(AssetRef);
	if (nullptr != Asset)
		TargetComponent->SetSkeletalMesh(Asset);
}

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

	CurrentIndex = 0;
	auto CharacterSetting = GetDefault<UABCharacterSetting>();
	MaxIndex = CharacterSetting->CharacterAssets.Num();

	for (TActorIterator<ASkeletalMeshActor> It(GetWorld()); It; ++It)
	{
		TargetComponent = It->GetSkeletalMeshComponent();
		break;
	}

	PrevButton	  = Cast<UButton>(GetWidgetFromName(TEXT("btnPrev")));
	NextButton	  = Cast<UButton>(GetWidgetFromName(TEXT("btnNext")));
	ConfirmButton = Cast<UButton>(GetWidgetFromName(TEXT("btnConfirm")));
	TextBox		  = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("edtPlayerName")));

	PrevButton   ->OnClicked.AddDynamic(this, &UABCharacterSelectWidget::OnPrevClicked);
	NextButton   ->OnClicked.AddDynamic(this, &UABCharacterSelectWidget::OnNextClicked);
	ConfirmButton->OnClicked.AddDynamic(this, &UABCharacterSelectWidget::OnConfirmClicked);
}

void UABCharacterSelectWidget::OnPrevClicked()
{
	NextCharacter(false);
}

void UABCharacterSelectWidget::OnNextClicked()
{
	NextCharacter(true);
}

void UABCharacterSelectWidget::OnConfirmClicked()
{
	FString CharacterName = TextBox->GetText().ToString();
	if (CharacterName.Len() <= 0 || CharacterName.Len() > 10) return;

	UABSaveGame* NewPlayerData	  = NewObject<UABSaveGame>();
	NewPlayerData->PlayerName	  = CharacterName;
	NewPlayerData->Level		  = 1;
	NewPlayerData->Exp			  = 0;
	NewPlayerData->HighScore	  = 0;

	auto ABPlayerState = GetDefault<AABPlayerState>();
	if (UGameplayStatics::SaveGameToSlot(NewPlayerData, ABPlayerState->SaveSlotName, 0))
		UGameplayStatics::OpenLevel(GetWorld(), TEXT("Gameplay"));
	
}

컴파일 후 UISelect의 부모 클래스를 _ABCharacterSelectWidget으로 설정한다

플레이 해보면 버튼을 누름에 따라 에셋이 바뀌게 된다


다만 캐릭터를 생성하면 우리가 지정한 에셋으로 되지 않는것을 볼 수 있다

이번에는 캐릭터 생성시 지정한 에셋으로 캐릭터가 생성되도록 코드를 구현하겠다

ABSaveGame.h

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

#pragma once

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

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABSaveGame : public USaveGame
{
	GENERATED_BODY()
	
public:
	
    ...
    
	UPROPERTY()
	int32 CharacterIndex = 0;
};

ABSaveGame.cpp

#include "ABSaveGame.h"

UABSaveGame::UABSaveGame()
{
	
    ...

	CharacterIndex = 0;
}

ABCharacterSelectWidget.cpp

void UABCharacterSelectWidget::OnConfirmClicked()
{
	FString CharacterName = TextBox->GetText().ToString();
	if (CharacterName.Len() <= 0 || CharacterName.Len() > 10) return;

	UABSaveGame* NewPlayerData	  = NewObject<UABSaveGame>();
	NewPlayerData->PlayerName	  = CharacterName;
	NewPlayerData->Level		  = 1;
	NewPlayerData->Exp			  = 0;
	NewPlayerData->HighScore	  = 0;
	NewPlayerData->CharacterIndex = CurrentIndex;

	auto ABPlayerState = GetDefault<AABPlayerState>();
	if (UGameplayStatics::SaveGameToSlot(NewPlayerData, ABPlayerState->SaveSlotName, 0))
		UGameplayStatics::OpenLevel(GetWorld(), TEXT("Gameplay"));
	
}

ABPlayerState.h

...

UCLASS()
class ARENABATTLE_API AABPlayerState : public APlayerState
{
	GENERATED_BODY()
public:
	AABPlayerState();

	int32 GetGameScore() const;
	int32 GetGameHighScore() const;
	int32 GetCharacterLevel() const;
	int32 GetCharacterIndex() const;

	...

protected:

	...

	UPROPERTY(Transient)
	int32 CharacterIndex;

};

ABPlayerState.cpp

AABPlayerState::AABPlayerState()
{

	..

	CharacterIndex = 0;
}

...

int32 AABPlayerState::GetCharacterIndex() const
{
	return CharacterIndex;
}

...

void AABPlayerState::InitPlayerData()
{
	auto ABSaveGame = Cast<UABSaveGame>(UGameplayStatics::LoadGameFromSlot(SaveSlotName, 0));
	if (nullptr == ABSaveGame)
		ABSaveGame = GetMutableDefault<UABSaveGame>();

	SetPlayerName(ABSaveGame->PlayerName);
	SetCharacterLevel(ABSaveGame->Level);
	GameScore      = 0;
	GameHighScore  = ABSaveGame->HighScore;
	Exp			   = ABSaveGame->Exp;
	CharacterIndex = ABSaveGame->CharacterIndex;

	SavePlayerData();
}

void AABPlayerState::SavePlayerData()
{
	UABSaveGame* NewPlayerData    = NewObject<UABSaveGame>();
	NewPlayerData->PlayerName     = GetPlayerName();
	NewPlayerData->Level	      = CharacterLevel;
	NewPlayerData->Exp		      = Exp;
	NewPlayerData->HighScore      = GameHighScore;
	NewPlayerData->CharacterIndex = CharacterIndex;

	UGameplayStatics::SaveGameToSlot(NewPlayerData, SaveSlotName, 0);
}

...

ABCharacter.cpp

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

	bIsPlayer = IsPlayerControlled();
	if (bIsPlayer)
		ABPlayerController = Cast<AABPlayerController>(GetController());
	else
		ABAIController = Cast<AABAIController>(GetController());

	auto DefaultSetting = GetDefault<UABCharacterSetting>();

	if (bIsPlayer)
	{
		auto ABPlayerState = Cast<AABPlayerState>(GetPlayerState());
		AssetIndex = ABPlayerState->GetCharacterIndex();
	}
	else
		AssetIndex = FMath::RandRange(0, DefaultSetting->CharacterAssets.Num() - 1);

	CharacterAssetToLoad = DefaultSetting->CharacterAssets[AssetIndex];
	AssetStreamingHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad
	(CharacterAssetToLoad, FStreamableDelegate::CreateUObject(this, &AABCharacter::OnAssetLoadCompleted));
	SetCharacterState(ECharacterState::LOADING);

}

마지막으로 프로젝트 시작 레벨을 Title 로 설정해주고, 실행해보자

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

0개의 댓글