본문 바로가기
언리얼 C++ 게임 개발의 정석/14. 게임의 완성

14.3 타이틀 화면 제작

by ftftgop3 2024. 6. 1.

타이틀 레벨 제작

미리 만들어진 UI폴더를 현재 프로젝트에 적용

아무 기능 없이 UI 화면 띄위는 역할

 

ABUIPlayerController 클래스 생성

UI 입력 모드로 설정 및 UI 생성해 뷰포트에 부착

UCLASS()
class ARENABATTLE_API AABUIPlayerController : public APlayerController
{
	GENERATED_BODY()
	
protected:
	virtual void BeginPlay() override;
	
	//UI 인스턴스 
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = UI)
		TSubclassOf<class UUserWidget> UIWidgetClass;

	//뷰포트에 부착 할 UI
	UPROPERTY()
		class UUserWidget* UIWidgetInstance;
	
	
};

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

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

	ABCHECK(nullptr != UIWidgetClass);
	//UIWidgetClass 클래스로 widget 생성
	UIWidgetInstance = CreateWidget<UUserWidget>(this, UIWidgetClass);
	ABCHECK(nullptr != UIWidgetInstance);
	//생성된 위젯 뷰포트에 부착
	UIWidgetInstance->AddToViewport();

	//UI전용 입력 모드로 적용
	FInputModeUIOnly Mode;
	//해당 UI 포커스 지정
	Mode.SetWidgetToFocus(UIWidgetInstance->GetCachedWidget());
	SetInputMode(Mode);
	//마우스 커서 활성화
	bShowMouseCursor = true;
}

 

ABUIController 기반으로 블루프린터 클래스 생성

UIwidgetClass에 UI_Title 지정

 

BP_TitleGameMode

블루프린트 기반 BP_TitleGameMode 생성

PlayerController, Pawn 클래스 지정

Title 레벨의 월드 셋팅에서 Gamemode를 BP_TitleGameMode 변경

 

 

각 버튼에 있는 로직 설명

블루프린트로 기능 구현

새로 시작하기 : 캐릭터 선택을 위한 Select 레벨로 이동

이어하기 : GamePlay 레벨로 이동, Player1 슬롯의 게임 데이터가 없으면 이어하기 버튼은 비활성화

종료하기 : 프로그램 종료

 

Select 레벨 생성

이미 생성된 Select.umap, UI 애셋을 복사 하여 임포트 

위에 사용한 ABUIPlayerConroller 를 상속받은 BP_SelectPlayerController 생성

UIWidget Class 에 UI_Select 지정

 

BPSelsectGamemode 생성

위에 같이 GameMode를 상속 받은 블루 프린트

PlayerController, Pawn 설정 후 Select 월드 셋팅에 GameMode로 지정

 

C++로 각 버튼 로직 구현

UserWidget 상속 받는 ABCharacterSelectWidget 클래스 생성

현재 레벨에 있는 스켈레탈 메시 엑터를 목록 화

좌우 버튼으로 스켈레탈 메시를 변경 하는 기능 구현

캐릭터 생성 시 SaveData를 슬롯에 저장, 성공 시 GamePlay 레벨로 전환

 

UCLASS()
class ARENABATTLE_API UABCharacterSelectWidget : public UUserWidget
{
	GENERATED_BODY()
	
protected:
	//블루프린트 로직에서 호출 가능하게 설정
	UFUNCTION(BlueprintCallable)
		void NextCharacter(bool bForward = true);
	//widget 초기화
	virtual void NativeConstruct() override;

protected:
	//현재 index
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Character)
		int32 CurrentIndex;
	//최대 Index
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Character)
		int32 MaxIndex;
	//모든 스켈레톤 메쉬 컴포넌트를 저장
	TWeakObjectPtr<USkeletalMeshComponent> TargetComponent;

	//UI Select 버튼 및 text box 변수
	UPROPERTY()
		class UButton* PrevButton;

	UPROPERTY()
		class UButton* NextButton;

	UPROPERTY()
		class UEditableTextBox* TextBox;

	UPROPERTY()
		class UButton* ConfirmButton;

private:
	//바인딩 할 함수

	UFUNCTION()
		void OnPrevClicked();

	UFUNCTION()
		void OnNextClicked();

	UFUNCTION()
		void OnConfirmClicked();
	
};

 

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

#include "ABCharacterSelectWidget.h"
#include "ABCharacterSetting.h"
#include "ABGameInstance.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)
{
	//bool 값으로 index 값 증감소 
	bForward ? CurrentIndex++ : CurrentIndex--;

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

	//캐릭터 애셋 경로를 ini 파일에서 로드
	auto CharacterSetting = GetDefault<UABCharacterSetting>();
	//현재 index에 해당하는 캐릭터 애셋 경로 지정
	auto AssetRef = CharacterSetting->CharacterAssets[CurrentIndex];

	//캐릭터 애셋을 로드하는 게임 인스턴스 지정
	auto ABGameInstance = GetWorld()->GetGameInstance<UABGameInstance>();
	ABCHECK(nullptr != ABGameInstance);
	ABCHECK(TargetComponent.IsValid());
	//해당 경로에 있는 캐릭터 애셋 로드
	USkeletalMesh* Asset = ABGameInstance->StreamableManager.LoadSynchronous<USkeletalMesh>(AssetRef);
	if (nullptr != Asset)
	{
		//타켓 컴포넌트의 메쉬에 지정
		TargetComponent->SetSkeletalMesh(Asset);
	}
}

void UABCharacterSelectWidget::NativeConstruct()
{
	Super::NativeConstruct();
	//현재 index 값 초기화
	CurrentIndex = 0;
	//ini 파일에 있는 캐릭터 애셋 최대 개수로 최기화
	auto CharacterSetting = GetDefault<UABCharacterSetting>();
	MaxIndex = CharacterSetting->CharacterAssets.Num();

	//현재 레벨의 모든 스켈레톤 액터를 찾아서 targetcomponent 지정
	for (TActorIterator<ASkeletalMeshActor> It(GetWorld()); It; ++It)
	{
		TargetComponent = It->GetSkeletalMeshComponent();
		break;
	}
	//UI_Select 에 있는 버튼과 연결
	PrevButton = Cast<UButton>(GetWidgetFromName(TEXT("btnPrev")));
	ABCHECK(nullptr != PrevButton);

	NextButton = Cast<UButton>(GetWidgetFromName(TEXT("btnNext")));
	ABCHECK(nullptr != NextButton);

	TextBox = Cast<UEditableTextBox>(GetWidgetFromName(TEXT("edtPlayerName")));
	ABCHECK(nullptr != TextBox);

	ConfirmButton = Cast<UButton>(GetWidgetFromName(TEXT("btnConfirm")));
	ABCHECK(nullptr != ConfirmButton);

	//클릭 이벤트 바인딩
	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()
{
	//캐릭터 생성 
	//Text 박스에 작성한 ID 값 
	FString CharacterName = TextBox->GetText().ToString();
	if (CharacterName.Len() <= 0 || CharacterName.Len() > 10) return;

	//새로운 Save 슬롯 생성
	UABSaveGame* NewPlayerData = NewObject<UABSaveGame>();
	NewPlayerData->PlayerName = CharacterName;
	NewPlayerData->Level = 1;
	NewPlayerData->Exp = 0;
	NewPlayerData->HighScore = 0;

	//PlayerState에서 NewPlayerData 저장
	auto ABPlayerState = GetDefault<AABPlayerState>();
	if (UGameplayStatics::SaveGameToSlot(NewPlayerData, ABPlayerState->SaveSlotName, 0))
	{
		UGameplayStatics::OpenLevel(GetWorld(), TEXT("GamePlay"));
	}
	else
	{
		ABLOG(Error, TEXT("SaveGame Error!"));
	}
}

 

UI_Select 애셋의 부모로 ABCharacterSeleectWidget 지정

좌우 버튼으로 캐릭터 애셋 변경 확인

 

캐릭터 생성 버튼 기능 확장

생성 시 선택한 캐릭터로 게임 플레이 기능 구현

SaveData 에 캐릭터 인덱스 데이터 추가

UCLASS()
class ARENABATTLE_API UABSaveGame : public USaveGame
{
	GENERATED_BODY()
public:
	UABSaveGame();
	//캐릭터 인덱스
	UPROPERTY()
		int32 CharacterIndex;
}
UABSaveGame::UABSaveGame()
{
	//각 변수 데이터 초기화
	Level = 1;
	Exp = 0;
	PlayerName = TEXT("Guest");
	HighScore = 0;
	CharacterIndex = 0;
}

 

ABCharacterSelectWidget

캐릭터 생성 시 캐릭터 인덱스 저장

void UABCharacterSelectWidget::OnConfirmClicked()
{
	//...
	//새로운 Save 슬롯 생성
	UABSaveGame* NewPlayerData = NewObject<UABSaveGame>();
	NewPlayerData->PlayerName = CharacterName;
	NewPlayerData->Level = 1;
	NewPlayerData->Exp = 0;
	NewPlayerData->HighScore = 0;
	NewPlayerData->CharacterIndex = CurrentIndex;
    //...
}

 

ABPlayState

Characterindex를 PlayState 에 변수로 추가 

Save 및 Load 할 떄 CharacterIndex 에 값으로 지정 

UCLASS()
class ARENABATTLE_API AABPlayerState : public APlayerState
{
	GENERATED_BODY()
    public:
    	//캐릭터 인덱스
	int32 GetCharacterIndex() const;
    protected:
    	//캐릭터 인덱스
	UPROPERTY(Transient)
		int32 CharacterIndex;
        
}

AABPlayerState::AABPlayerState()
{
	CharacterLevel = 1;
	GameScore = 0;
	Exp = 0;
	GameHighScore = 0;
	SaveSlotName = TEXT("Player1");
	CharacterIndex = 0;
}
int32 AABPlayerState::GetCharacterIndex() const
{
	return CharacterIndex;
}
void AABPlayerState::InitPlayerData()
{
	//세이브 슬롯 네임에 해당하는 SaveGame 데이터 로드
	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;

	//해당 SaveGame 을 slotname에 저장
	if (!UGameplayStatics::SaveGameToSlot(NewPlayerData, SaveSlotName, 0))
	{
		ABLOG(Error, TEXT("SaveGame Error!"));
	}
}

 

ABCharacter

Characterindex 값으로 캐릭터 로드

void AABCharacter::BeginPlay()
{
	//...
	auto DefaultSetting = GetDefault<UABCharacterSetting>();
	if (bIsPlayer)
	{
		auto AbPlayState = Cast<AABPlayerState>(PlayerState);
		ABCHECK(AbPlayState != nullptr)
		//PlayerState 저장된 데이터로 캐릭터 로드 
		AssetIndex = AbPlayState->GetCharacterIndex();
	}
	else
	{
		AssetIndex = FMath::RandRange(0, DefaultSetting->CharacterAssets.Num() - 1);
	}
    //...
}

 

게임 시작 레벨 설정

프로젝트 맵 & 로드 에서 시작 레벨을 Title 레벨로 지정