[ Unreal Engine 5 / #6 Widget, FSM, GameMode ]

SeungWoo·2024년 9월 4일
0
post-thumbnail

FSM( Finite State Machine )

  • 유한 상태 제어( 흐름 )
  • 동작의 흐름을 구조화 할때 많이 사용한다
  • 하위 Sub State Machine

UI

  • C++ 클래스를 하나 만든다 FSM으로 게임모드를 관리하기 위해 해당 함수와 Enum으로 구조화를 잡아둔다

ShootGameMode.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "CShootGameMode.generated.h"

// enum를 사용하는데 블루프린트로도 사용할때,
UENUM(BlueprintType)
enum class EShootGameState : uint8
{
	Title,
	Ready, 
	Start, 
	Playing,
	Pause,
	Gameover
};


UCLASS()
class SHOOTING_API ACShootGameMode : public AGameModeBase
{
	GENERATED_BODY()
	
public:

	ACShootGameMode();

	virtual void BeginPlay() override;

	virtual void Tick(float DeltaTime) override;

public:

	// FSM상태를 만들어서 게임이 흘러갈 수 있게
	UPROPERTY(VisibleAnywhere, Category = "Game")
	EShootGameState myState;

	void Title();
	void Ready();
	void Start();
	void Playing();
	void Pause();
	void Gameover();

	// 필요한 Main 위젯 
	UPROPERTY(EditDefaultsOnly, Category = "Game")
	TSubclassOf<class UUserWidget> MainUIFactory;

	UPROPERTY()
	class UUserWidget* mainUI;
};
  • #include "Blueprint\UserWidget.h"를 선언하면 UserWidget를 사용한다

ShootGameMode.cpp

#include "CShootGameMode.h"
#include "Blueprint\UserWidget.h"

ACShootGameMode::ACShootGameMode()
{
	PrimaryActorTick.bCanEverTick = true;

	
}

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

	mainUI = CreateWidget<UUserWidget>(GetWorld(), MainUIFactory);
	
	if (mainUI)
	{
		mainUI->AddToViewport();
	}
}

void ACShootGameMode::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	switch (myState)
	{
	case EShootGameState::Title:
		Title();
		break;
	case EShootGameState::Ready:
		Ready();
		break;
	case EShootGameState::Start:
		Start();
		break;
	case EShootGameState::Playing:
		Playing();
		break;
	case EShootGameState::Pause:
		Pause();
		break;
	case EShootGameState::Gameover:
		Gameover();
		break;
	default:
		break;
	}
}

void ACShootGameMode::Title()
{
}

void ACShootGameMode::Ready()
{
}

void ACShootGameMode::Start()
{
}

void ACShootGameMode::Playing()
{
}

void ACShootGameMode::Pause()
{
}

void ACShootGameMode::Gameover()
{
}
  • 이 부분이 블루프린트로 재생 부분이다
	mainUI = CreateWidget<UUserWidget>(GetWorld(), MainUIFactory);
	
	if (mainUI)
	{
		mainUI->AddToViewport();
	}
  • WBP_MainUI 이름으로 UUserWidget를 하나 만든다

  • CShootGameMode 클래스를 상속받은 'BP_GameMode'이름의 Blueprint를 하나 만든다

  • 'BP_GameMode' 블루프린트로 해당 만든 MainUI윗젯를 BeginPlay()로 시작하면 UI를 띄워보자

  • UI를 Setting

  • 메인 Canvas를 하나 만든다
    • Canvas안에 또하나의 Canvas를 하나만들고
    • 배경을 넣을 Image, Button을 넣고 Button밑에 TEXT를 하나 놓는다
    • Button 설정을 사진 처럼 놓는다

  • Text 설정을 놓는다

  • MainUI를 BP_ShootGameMode에서 관리 하기 위해 WorldSetting을 해서 BP_ShootGameMode로 해놓는다

  • UUserWidget를 사용하기 위해 모듈에 UMG를 추가 한다

  • MainUI도 C++ 클래스로 제어하기 위해 C++ 클래스 UserWidget 클래스로 CMainUI 라는 이름의 클래스를 하나만든다

  • 기존의 있던 WBP_MainUI를 만든 클래스에 접목시킨뒤 바인딩 한다

CMainUI.h

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "CMainUI.generated.h"

/**
 * 
 */
UCLASS()
class SHOOTING_API UCMainUI : public UUserWidget
{
	GENERATED_BODY()
	
public:
	// 라이프 사이클 버튼 이벤트 함수를 호출할
	virtual void NativeConstruct() override;

	// GameStartbutton Binding
	UPROPERTY(meta = (BindWidget))
	class UButton* btn_GameStart;

	// TitleCanvas Binding 
	UPROPERTY(meta = (BindWidget))
	class UCanvasPanel* TitleCanvas;

	// Ready TEXT Binding
	UPROPERTY(meta = (BindWidget))
	class UTextBlock* ReadyText;

	UFUNCTION()
	void OnGameStartBtnClicked();
};
  • WBP_MainUI에 만들어논 btn_GameStart 버튼과 그외 Canvas, Text를 제어하기 위해 해당 변수로 바인딩 한다

  • 블루프린트를 끊어버리고 WBP_MainUI를 BP_GameMode 'Game' 카테고리에 선언한 WBP_mainWidget를 넣는다

CMainUI.cpp

#include "CMainUI.h"
#include "Components\Button.h"
#include "Components\CanvasPanel.h"
#include "Components\TextBlock.h"

#include "Kismet\GameplayStatics.h"
#include "CShootGameMode.h"

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

	btn_GameStart->OnClicked.AddDynamic(this, &UCMainUI::OnGameStartBtnClicked);
}

void UCMainUI::OnGameStartBtnClicked()
{
	// 게임 상태를 Ready로 전환하고 싶다
	// 1. 게임 모드가 있어야 한다
	// 월드에있는 게임모드를 ACShootGameMode로 캐스팅 하겠다
	auto gm = Cast<ACShootGameMode>(UGameplayStatics::GetGameMode(GetWorld()));
	// 2. 게임모드의 상태를 바꿔준다
	if (gm)
	{
		gm->myState = EShootGameState::Ready;
		
		ReadyText->SetVisibility(ESlateVisibility::Visible);
	}

	// button 클릭시 숨김
	TitleCanvas->SetVisibility(ESlateVisibility::Hidden);

	// 입력모드를 Game으로 설정해주기
	GetWorld()->GetFirstPlayerController()->SetInputMode(FInputModeGameOnly());
	// 마우스 커서 안보이게 하기
	GetWorld()->GetFirstPlayerController()->SetShowMouseCursor(false);
}
  • NativeConstruct가 블루프린트 Event 함수랑 같은 역할을 해주면서 click시 호출하는 함수를 Onclicked.AddDynamic 를 사용해 Button 클릭시 OnGameStartBtnClicked() 함수를 호출한다

입력모드

	// 입력모드를 Game으로 설정해주기
	GetWorld()->GetFirstPlayerController()->SetInputMode(FInputModeGameOnly());
	// 마우스 커서 안보이게 하기
	GetWorld()->GetFirstPlayerController()->SetShowMouseCursor(false);
  • GameMode로 넘어가 해당 게임 구조를 잡기 위해
    • 일정시간 지나면 상태를 Start로 바꾼다.
    • 또 일정 시간이 지나면 Playing으로 바꾼다

CShootGameMode.h

public:
	UPROPERTY(EditDefaultsOnly, Category = "Game")
	float ReadyDelayTime = 2;

	UPROPERTY(EditDefaultsOnly, Category = "Game")
	float StartDelayTime = 2;

	float currentTime = 0;

CShootGameMode.cpp

#include "CMainUI.h"

void ACShootGameMode::Ready()
{
	currentTime += GetWorld()->DeltaTimeSeconds;
	if (currentTime > ReadyDelayTime)
	{
		currentTime = 0;
		myState = EShootGameState::Start;
	}
}

// 일정시간 지나면 상태를 Playing로 바꾼다.
void ACShootGameMode::Start()
{
	currentTime += GetWorld()->DeltaTimeSeconds;
	if (currentTime > StartDelayTime)
	{
		currentTime = 0;
		myState = EShootGameState::Playing;
	}
}
  • player가 게임일때만 움직이게
    • 게임의 상태가 Ready, Start, Playing 일때만 이동, 공격, Enemy 스폰이 가능하게

CPlayer.cpp

// 추가
#include "CShootGameMode.h"


void ACPlayer::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 게임의 상태가 Ready, Start, Playing 일때만 이동 가능
	auto gm = Cast<ACShootGameMode>(GetWorld()->GetAuthGameMode());
	
	if (gm && !(gm->myState == EShootGameState::Ready ||
		gm->myState == EShootGameState::Start ||
		gm->myState == EShootGameState::Playing))
	{
		return;
	}

	// 등속운동, 등가속운동
	// p = p0 + vt
	FVector dir(0, h, v);
	FVector P0 = GetActorLocation();
	FVector vt = dir * speed * DeltaTime;
	
	FVector P = P0 + vt;
	SetActorLocation(P);

}

void ACPlayer::Fire()
{
	// 게임의 상태가 Ready, Start, Playing 일때만 이동 가능
	auto gm = Cast<ACShootGameMode>(GetWorld()->GetAuthGameMode());

	if (gm && !(gm->myState == EShootGameState::Ready ||
		gm->myState == EShootGameState::Start ||
		gm->myState == EShootGameState::Playing))
	{
		return;
	}

	PlayFireSound();
	GetWorld()->SpawnActor<ACBullet>(
		bulletFactory, GetActorLocation(), FRotator::ZeroRotator);
}
  • 적 스폰도 game이 시작할때만
    CEnemyGOD.cpp
// 추가
#include "CShootGameMode.h"

void ACEnemyGOD::CreateEnemy()
{
	// 게임의 상태가 Ready, Start, Playing 일때만 이동 가능
	auto gm = Cast<ACShootGameMode>(GetWorld()->GetAuthGameMode());

	if (gm && !(gm->myState == EShootGameState::Playing))
	{
		return;
	}

	if (spawnPoints.Num() < -1)
	{
		return;
	}

	// spawnpoints중에 랜덤한 위치를 뽑아서 스폰하겠다
	// 1. 랜덤한 스폰 인덱스 추출 하기
	int32 spawnIndex = FMath::RandRange(0, spawnPoints.Num() - 1);
	// 2. 스폰할 위치 
	FVector loc = spawnPoints[spawnIndex]->GetActorLocation();
	// 3. 생성하자 
	GetWorld()->SpawnActor<ACEnemy>(enemyFatory, loc, FRotator::ZeroRotator);
}
profile
This is my study archive

0개의 댓글