타이틀 레벨 제작
미리 만들어진 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 레벨로 지정
'언리얼 C++ 게임 개발의 정석 > 14. 게임의 완성' 카테고리의 다른 글
14.4 게임 중지 와 결과 화면 (0) | 2024.06.02 |
---|---|
14.2 전투 시스템 설계 (0) | 2024.05.27 |
14.1 게임 데이터의 저장과 로딩 (0) | 2024.05.24 |