1. PlayerController
타이틀 전용 컨트롤러 및 맵 추가
동작 모습
- 게임 시작 전 start 화면 구현
- Start 버튼을 누르면 Join Server 수행
구조
- Title Level (새로운 레벨 추가)
- GameModeBase_Title (새로운 GameMode 추가)
- DXTitlePlayerController (새로운 PlayerController 추가 -> 위젯 띄워주기)
구현 내용
- ADXTitlePlayerController
void ADXTitlePlayerController::BeginPlay()
{
Super::BeginPlay();
if (!IsLocalController())
{
return;
}
//not server
if (IsValid(UIWidgetClass))
{
UIWidgetInstance = CreateWidget<UUserWidget>(this, UIWidgetClass);
if (IsValid(UIWidgetInstance))
{
UIWidgetInstance->AddToViewport();
FInputModeUIOnly Mode;
Mode.SetWidgetToFocus(UIWidgetInstance->GetCachedWidget());
SetInputMode(Mode);
bShowMouseCursor = true;
}
}
}
void ADXTitlePlayerController::JoinServer(const FString& InIPAddress)
{
FName NextLevelName = FName(*InIPAddress);
UGameplayStatics::OpenLevel(GetWorld(), NextLevelName, true);
}
- UUW_TitleLayout
...
void UUW_TitleLayout::OnPlayButtonClicked()
{
ADXTitlePlayerController* PlayerController = GetOwningPlayer<ADXTitlePlayerController>();
if (IsValid(PlayerController))
{
FText ServerIP = ServerIPEditableText->GetText();
PlayerController->JoinServer(ServerIP.ToString());
}
}
void UUW_TitleLayout::OnExitButtonClicked()
{
UKismetSystemLibrary::QuitGame(this, GetOwningPlayer(), EQuitPreference::Quit, false);
}
2. GameMode & GameState
2 - 1. Overview
게임의 흐름
- 서버 실행
- 플레이어 접속을 체크
- 인원 수가 채워지면 카운트다운 이후 게임 실행
이러한 서버 로직에 대해서는 "게임 모드"에서 처리해주어야 한다.
이후 서버가 모든 클라이언트에게 게임의 흐름에 있어서 발생한 정보를 전해주기 위해서 "게임 스테이트"를 사용
- 게임 스테이트 액터는 각 클라이언트에게 복제된다.
- Replicated 지정자 사용
UPROPERTY(Replicated, VisibleAnywhere, BlueprintReadOnly)
int32 AlivePlayerControllerCount = 0;
- 클라이언트의 UI 구현에 있어서 게임 스테이트에 접근해서 정보를 가져오도록 한다.
2 - 2. 접속 관련 구현
- DXGameModeBase
- PostLogin, Logout 함수 override 하여 접속한 플레이어의 정보를 저장
//헤더 파일
virtual void PostLogin(APlayerController* NewPlayer) override;
virtual void Logout(AController* Existing) override;
//cpp 파일
void ADXGameModeBase::PostLogin(APlayerController* NewPlayer)
{
Super::PostLogin(NewPlayer);
ADXPlayerController* NewPlayerController = Cast<ADXPlayerController>(NewPlayer);
if (IsValid(NewPlayerController))
{
AlivePlayerControllers.Add(NewPlayerController);
//UI 출력
NewPlayerController->NotificationText = FText::FromString(TEXT("Connected to the game server"));
}
}
void ADXGameModeBase::Logout(AController* Existing)
{
Super::Logout(Existing);
ADXPlayerController* ExistingPlayerController = Cast<ADXPlayerController>(Existing);
if (IsValid(ExistingPlayerController) && AlivePlayerControllers.Find(ExistingPlayerController) != INDEX_NONE)
{
AlivePlayerControllers.Remove(ExistingPlayerController);
DeadPlayerControllers.Add(ExistingPlayerController);
}
}
- DXPlayerController
- 접속한 플레이어는 위쪽에 현재 상태(대기, 게임 시작 등)를 알려주는 Text UI 표시
- 실제 UI에 text를 세팅하는 것은 BP의 Bind를 통해 구현
void ADXPlayerController::BeginPlay()
{
Super::BeginPlay();
//server check
if (!IsLocalController())
{
return;
}
if (IsValid(NotificationTextUIClass))
{
UUserWidget* NotificationTextUI = CreateWidget<UUserWidget>(this, NotificationTextUIClass);
if (IsValid(NotificationTextUI))
{
NotificationTextUI->AddToViewport(1);
NotificationTextUI->SetVisibility(ESlateVisibility::Visible);
}
}
}
void ADXPlayerController::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, NotificationText);
}
2 - 3. 게임 상태 관련 구현
게임 스테이트에서 플레이어를 기다리는 상태, 게임 시작 상태, 게임 종료 상태 등의 정보를 저장해두고
게임 모드에서는 게임 스테이트에서 상태를 가져와 그에 따라 처리를 해준다.
구현
- DXGameStateBase
//헤더
UENUM(BlueprintType)
enum class EMatchState : uint8
{
None,
Waiting,
Playing,
Enging,
End
};
UCLASS()
class SCC_DEDICATEDX_API ADXGameStateBase : public AGameStateBase
{
GENERATED_BODY()
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
public:
UPROPERTY(Replicated, VisibleAnywhere, BlueprintReadOnly)
int32 AlivePlayerControllerCount = 0;
UPROPERTY(Replicated, VisibleAnywhere, BlueprintReadOnly)
EMatchState MatchState = EMatchState::Waiting;
};
//cpp
void ADXGameStateBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, AlivePlayerControllerCount);
DOREPLIFETIME(ThisClass, MatchState);
}
- DXGameModeBase
- 타이머를 통해 Main Loop를 돌리기
- 매 틱마다 돌리는 것보다 과부화 줄일 수 있음
- 타이머로 해도 괜찮을 정도로 널널한 생각해 봐야한다. (현재는 1초마다)
- 타이머를 통해 Main Loop를 돌리기
void ADXGameModeBase::BeginPlay()
{
Super::BeginPlay();
GetWorld()->GetTimerManager().SetTimer(MainTimerHandle, this, &ThisClass::OnMainTimerElapsed, 1.f, true);
RemainWaitingTimeForPlaying = WaitingTime;
}
void ADXGameModeBase::OnMainTimerElapsed()
{
ADXGameStateBase* DXGameState = GetGameState<ADXGameStateBase>();
if (!IsValid(DXGameState))
{
return;
}
switch (DXGameState->MatchState)
{
case EMatchState::None:
break;
case EMatchState::Waiting:
{
FString NotificationString = FString::Printf(TEXT(""));
if (AlivePlayerControllers.Num() < MinimumPlayerCountForPlaying)
{
NotificationString = FString::Printf(TEXT("Wait another players for playing"));
RemainWaitingTimeForPlaying = WaitingTime;
}
else
{
NotificationString = FString::Printf(TEXT("Wait %d seconds for playing"), RemainWaitingTimeForPlaying);
--RemainWaitingTimeForPlaying;
}
if (RemainWaitingTimeForPlaying <= 0)
{
NotificationString = FString::Printf(TEXT(""));
DXGameState->MatchState = EMatchState::Playing;
}
NotifyToAllPlayer(NotificationString);
break;
}
case EMatchState::Playing:
break;
case EMatchState::Enging:
break;
case EMatchState::End:
break;
default:
break;
}
}
3.
'Unreal Engine' 카테고리의 다른 글
Unreal Engine - 데디케이티드 서버 9 (게임 종료) (0) | 2025.05.04 |
---|---|
Unreal Engine - AI (2) (0) | 2025.04.29 |
Unreal Engine - AI (1) (1) | 2025.04.25 |
Unreal Engine - 멀티플레이 네트워크 최적화 2 (0) | 2025.04.23 |
Unreal Engine - 멀티플레이 네트워크 최적화 1 (0) | 2025.04.22 |