* 타이틀 제작을 위해서 빈공백의 레벨을 새로 만든 후 Title 로 저장한다.
* 해당 레벨은 아무 기능 없이 UI 화면만 띄우는 역활을 수행할 것이다.
* 사용할 게임 모드와 UI를 띄울 플레이어 컨트롤러를 제작한다.
* PlayerController 상속받아 ABUIPlayerController 클래스 생성
* 새로운 플레이어 컨트롤러 클래스를 생성하면, 이를 상속받은 블루프린트에서 앞으로 띄울UI 클래스 값을 에디터에서 설정할 수 있도록 위젯 클래스 속성을 추가하고 EditDefaultOnly 키워드를 지정한다.
* 플레이어 컨트롤러의 로직은 게임을 시작하면 해당 클래스로부터 UI 인스턴스를 생성하고, 이를 뷰포트에 띄운 후에 입력은 UI에만 전달되도록 제작한다.
//ABUIPlayerController.h
#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
#include "ABUIPlayerController.h"
#include "Blueprint/UserWidget.h"
void AABUIPlayerController::BeginPlay()
{
Super::BeginPlay();
ABCHECK(nullptr != UIWidgetClass);
UIWidgetInstance = CreateWidget<UUserWidget>(this, UIWidgetClass);
ABCHECK(nullptr != UIWidgetInstance);
UIWidgetInstance->AddToViewport();
FInputModeUIOnly Mode;
Mode.SetWidgetToFocus(UIWidgetInstance->GetCachedWidget());
SetInputMode(Mode);
bShowMouseCursor = true;
}
* C++ 플레이어 컨트롤러의 기본 로직을 완성하면 이를 기반으로 한 블루프린트 클래스를 생성한다.
* 새롭게 생성된 블루 프린트 애셋에는 BP_TitleUIPlayerController 로 정함
* 생성된 플레이어 컨트롤러 블루 프린트를 더블 클릭한 후 오른쪽 상단의 UIWidget Class 속성을 클릭해 복사해 넣은 UI 애셋인 UI_Title을 지정한다.
.
* 컨트롤러 세팅이 끝나면, BP를 이용해 게임모드를 생성해 본다. Game Mode Base를 상속한 BP_TitleGameMode 생성
* Default Pawn Class의 정보를 아무 기능이 없는 Pawn으로 지정하고
* Player Controller class의 정보를 방금 생성한 BP_TitleGameMode로 설정한다
* 이젠 월드세팅에서 게임모드를 쉽게 변경 할 수 있다.
* UI 에서 새로시작하기 버튼을 누르면 캐릭터 선택을 위한 Select 레벨로 이동하고
* 이어하기 버튼을 누르면 GamePlay 레벨로 이동한다. 또한 Player1이라는 슬롯의 게임 데이터가 없으면, 이어하기 버튼은 누를 수 없게 비활성화 된다.
* 로직은 다음과 같음
* 아직 Select 라는 레벨이 없어서 새로 시작하기 버튼을 누르면 오류가 발생한다.
* 그래서 캐릭터 선택을 담당하는 레벨인 Select 레벨을 제작해본다.
* 위와 동일한 방법으로 ABUIPlayerController을 상속받은 블루프린트 BP_SelectUIPlayerController 을 생성한다
* 그리고 BP_SelectGameMode라는 게임 모드를 생성하고, PlayerController 클래스와 DefaultPawnClass를 각각 BP_SelectUIPlayerContoller와 Pawn으로 지정한다
* 이제 월드세팅에서 BP_SelectGame를 기본 게임모드로 지정하면 캐릭터 양옆으로 버튼이 보인다.
* 이제 C++로 각 버튼에 대한 로직을 구현해본다
* UserWidget을 기반으로 하는 ABCharacterSelectWidget 클래스를 생성한다
* 현재 레벨에 있는 스켈레톤 메시 액터의 목록을 가져오고, 버튼을 누를 떄마다 스켈레톤 메시 컴포넌트에 지정한 스켈레톤 메시를 변경한 ㄴ기능을 구현한다.
* 현재월드에 있는 특정 타입을 상속 받은 액터의 목록은 TActorIterator<액터타입> 구문을 사용해 가져올 수 있다
//ABCharacterSelectWidget.h
#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;
TWeakObjectPtr<USkeletalMeshComponent> TargetComponent;
};
//ABCharacterSelectWidget.cpp
#include "ABCharacterSelectWidget.h"
#include "ABCharacterSetting.h"
#include "ABGameInstance.h"
#include "EngineUtils.h"
#include "Animation/SkeletalMeshActor.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];
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();
CurrentIndex = 0;
auto CharacterSetting = GetDefault<UABCharacterSetting>();
MaxIndex = CharacterSetting->CharacterAssets.Num();
for (TActorIterator<ASkeletalMeshActor> It(GetWorld()); It; ++It)
{
TargetComponent = It->GetSkeletalMeshComponent();
break;
}
}
* NextCharacter 함수는 블루프린트에서 사용할 수 있도록 함수에 UFUNCTION 매크로를 추가하고 BlueprintCallable 키워드를 지어했다.
* 이번 C++코드에서 버튼 컨트롤과 NextCharacter 함수를 직접바인딩 했지만, 해당 함수는 블루프린트 로직에서도 호출할 수 있다.
//ABCharacterSelectWidget.h
{
protected:
...
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();
}
//ABCharacterSelectWidget.cpp
#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "ABSaveGame.h"
#include "ABPlayerState.h"
void UABCharacterSelectWidget::NativeConstruct()
{
.....
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()
{
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"));
}
else
{
ABLOG(Error, TEXT("SaveGame Error!"));
}
}
* 컴파일에 문제가 없다면 UI_Select 애셋의 부모클래스를 ABCharacterSelectWidget으로 변경한다
* 이제 진행을 하면 버튼을 누르 때 마다 캐릭터 애셋이 변경이 된다.
* 그리고 캐릭터를 선택한 수 하단의 텍스트 박스에 캐릭터가 사용할 이름을 입력하고 캐릭터 생성 버튼을 누른다. 그러면 입력한 플레이어 이름으로 게임이 시작되지만, 선택한 캐릭터와 다른 기본 캐릭터가 나온다.
* 현재 선택한 캐릭터가 게임플레이 에서도 동일하게 나오도록 하려면, 현재 선택한 캐릭터 정보를 저장하고 게임플레이 레벨에서 이를 로딩하는 기능을 만들어야 한다
* 캐릭터 생성 버튼을 누르면 현재 선택한 캐릭터 정보를 세이브데이터에 저장하고 이를 읽어들이는 기능을 구현한다
//ABSaveGame.h
{
public:
...
UPROPERTY()
int32 CharaterIndex;
};
//ABSaveGame.cpp
UABSaveGame::UABSaveGame()
{
...
CharaterIndex = 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->CharaterIndex = CurrentIndex;
.......
}
//ABPlayerState.h
{
public:
int32 GetCharacterInex() const;
protected:
UPROPERTY(Transient)
int32 CharacterIndex;
}
//ABPlayerState.cpp
AABPlayerState::AABPlayerState()
{
...
CharacterIndex = 0;
}
int32 AABPlayerState::GetCharacterInex() 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->CharaterIndex; //추가
SavePlayerData();
}
void AABPlayerState::SavePlayerData()
{
UABSaveGame* NewPlayerData = NewObject<UABSaveGame>();
NewPlayerData->PlayerName = GetPlayerName();
NewPlayerData->Level = CharacterLevel;
NewPlayerData->Exp = Exp;
NewPlayerData->HighScore = GameHighScore;
NewPlayerData->CharaterIndex = CharacterIndex; //추가
if (!UGameplayStatics::SaveGameToSlot(NewPlayerData, SaveSlotName, 0))
{
ABLOG(Error, TEXT("SaveGame Error!"));
}
}
//ABCharacter.cpp
void AABCharacter::BeginPlay()
{
.....
auto DefaultSetting = GetDefault<UABCharacterSetting>();
if (bIsPlayer)
{
//AssetIndex = 4;
//추가함
auto ABPlayerState = Cast<AABPlayerState>(GetPlayerState());
ABCHECK(nullptr != ABPlayerState);
AssetIndex = ABPlayerState->GetCharacterIndex();
}
.....
}
※ GetCharacterInex() -> GetCharacterIndex() 수정
* 이제 시작 UI 기능을 모두 완성했으므로 프로젝트 세팅으로 가서 Title 레벨을 기본 레벨로 설정한다.
'Unreal > Game 1 (C++)' 카테고리의 다른 글
23. 게임 실행 (0) | 2019.05.16 |
---|---|
22. 게임의 완성 4 (게임의 중지와 결과화면) (0) | 2019.05.13 |
22. 게임의 완성 2 (전투 시스템의 설계) (0) | 2019.05.10 |
22. 게임의 완성 1 (게임 데이터의 저장과 로딩) (0) | 2019.05.10 |
21. 게임플레이 제작 3 (게임 데이터의 관리) (0) | 2019.05.09 |