오늘은 게임의 타이틀화면과 캐릭터 선택화면을 만들고, 각각의 기능을 엮으려 한다
사용한 에셋은 오늘또한 링크를 통해 다운로드 가능하다
먼저 받은 에셋의 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 Class
는 Pawn
, Player Controller Class
는 방금 생성한 BP_TitleUIPlayerController
로 설정한다
마지막으로 좌측 위 드랍다운 메뉴에서 월드 세팅을 BP_TitleGameMode 로 설정해준다
플레이 시 타이틀 UI가 나타난다
UI에는 이미 구현된 블루프린트 로직이 있어 새로 시작하기 를 누르면 Select 레벨로, 이어하기를 누르면 GamePlay 레벨로 가게 된다
아직 현재 Select 레벨이 없기 때문에, 시작하기를 누르면 오류가 발생하게 된다
이제 캐릭터 선택창인 Select 레벨을 만들자
이번에는 다운로드 받은 에셋에 있는 UI_Select
와 Select
레벨을 프로젝트에 넣어주도록 한다
아까 타이틀 화면에서 한 것과 동일하게 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 로 설정해주고, 실행해보자