1. PlayerState를 활용한 플레이어 정보 관리
멀티플레이어 환경에서 각 플레이어의 고유한 데이터(이름, 시도 횟수 등)를 관리하기 위해 PlayerState 클래스를 상속받아 ACXPlayerState를 구현함. GameModeBase의 OnPostLogin 함수를 통해 새로운 플레이어가 접속할 때마다 전체 컨트롤러 배열의 크기를 기반으로 "Player1", "Player2"와 같이 고유한 번호를 지정해 주었음. 또한, 게임 진행에 필요한 현재 시도 횟수(CurrentGuessCount)와 최대 시도 횟수(MaxGuessCount)를 이곳에서 안전하게 보관하도록 설계함.
2. 네트워크 동기화를 위한 프로퍼티 레플리케이션
서버에서 관리되는 플레이어의 정보와 UI 공지사항을 클라이언트와 일치시키기 위해 프로퍼티 레플리케이션을 적용함. 레플리케이션이 필요한 변수에 UPROPERTY(Replicated) 매크로를 선언하고, GetLifetimeReplicatedProps 함수 내에서 DOREPLIFETIME 매크로를 사용하여 서버의 값이 변경될 때마다 클라이언트에게 자동으로 전달되도록 설정함. 변하지 않는 값인 MaxGuessCount도 클라이언트가 게임 시작 시 초기화된 값을 정상적으로 넘겨받아 UI 등에 활용하려면 레플리케이션 등록이 필요하다는 것을 알게 됨.
3. 게임 승패 판정 로직 및 UI 연동
채팅으로 입력받은 숫자 문자열을 판별하여 3 스트라이크일 경우 승리로 처리하고, 모든 플레이어가 최대 시도 횟수를 소진할 경우 무승부로 처리하는 로직을 GameModeBase에 구현함. 결과 판정 후에는 PlayerController에 선언된 NotificationText 변수를 갱신함. 이 변수는 클라이언트의 UMG 위젯(WBP_NotificationText)과 바인딩되어 있어, 값이 변경되면 즉시 플레이어의 화면에 공지사항으로 출력됨. 게임이 종료된 후에는 ResetGame 함수를 호출하여 정답을 새로 생성하고 모든 플레이어의 시도 횟수를 0으로 초기화함.
1. 변수 동기화 (프로퍼티 레플리케이션)
멀티플레이어 게임에서 서버와 클라이언트 간의 데이터를 일치시키는 핵심 설정이다.
// CXPlayerState.cpp
#include "CXPlayerState.h"
#include "Net/UnrealNetwork.h"
ACXPlayerState::ACXPlayerState()
: PlayerNameString(TEXT("None"))
, CurrentGuessCount(0)
, MaxGuessCount(3)
{
// 이 액터가 네트워크를 통해 레플리케이트 되도록 활성화한다.
bReplicates = true;
}
void ACXPlayerState::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// 서버에서 값이 변경되면 클라이언트들에게 동기화되도록 변수들을 등록한다.
DOREPLIFETIME(ThisClass, PlayerNameString);
DOREPLIFETIME(ThisClass, CurrentGuessCount);
DOREPLIFETIME(ThisClass, MaxGuessCount);
}
2. 게임 승패 판정 및 초기화 로직
스트라이크 개수에 따라 승리를 판정하거나, 모든 플레이어의 턴이 끝났는지 확인하여 무승부를 판정하는 로직이다.
// CXGameModeBase.cpp
void ACXGameModeBase::JudgeGame(ACXPlayerController* InChattingPlayerController, int InStrikeCount)
{
// 3 스트라이크를 달성한 경우 해당 플레이어의 승리로 판정한다.
if (3 == InStrikeCount)
{
ACXPlayerState* CXPS = InChattingPlayerController->GetPlayerState<ACXPlayerState>();
for (const auto& CXPlayerController : AllPlayerControllers)
{
if (IsValid(CXPS) == true)
{
// 승리 메시지를 만들어 모든 플레이어의 UI 텍스트 변수를 갱신한다.
FString CombinedMessageString = CXPS->PlayerNameString + TEXT(" has won the game.");
CXPlayerController->NotificationText = FText::FromString(CombinedMessageString);
// 게임 데이터를 초기 상태로 되돌린다.
ResetGame();
}
}
}
else
{
// 무승부 여부를 확인하기 위한 플래그 변수를 선언한다.
bool bIsDraw = true;
for (const auto& CXPlayerController : AllPlayerControllers)
{
ACXPlayerState* CXPS = CXPlayerController->GetPlayerState<ACXPlayerState>();
if (IsValid(CXPS) == true)
{
// 단 한 명이라도 시도 횟수가 남아있다면 무승부가 아니므로 검사를 중단한다.
if (CXPS->CurrentGuessCount < CXPS->MaxGuessCount)
{
bIsDraw = false;
break;
}
}
}
// 모든 플레이어가 시도 횟수를 소진했다면 무승부 처리 후 게임을 초기화한다.
if (true == bIsDraw)
{
for (const auto& CXPlayerController : AllPlayerControllers)
{
CXPlayerController->NotificationText = FText::FromString(TEXT("Draw..."));
ResetGame();
}
}
}
}
위젯 (WBP_NotificationText)

Text > Details 에 다음과 같이 설정

BP_PlayerController > Details
- Notification Text Widget Class : WBP_NotificationText 설정

위와 같이 로직을 채우면 아래와 같이 화면에 출력되는 것을 볼 수 있다.

'언리얼' 카테고리의 다른 글
| 언리얼 멀티플레이 지금까지의 플젝 리뷰 (0) | 2026.03.18 |
|---|---|
| 언리얼 엔진 - 프로퍼티 레플리케이션(Property Replication) 및 숫자 야구 게임 로직 구현 (0) | 2026.03.16 |
| 언리얼 엔진 5 멀티플레이 채팅 및 네트워크 기초 (0) | 2026.03.13 |
| Unreal Engine과 C++를 활용한 다수의 적을 쓰러뜨리는 슈터 게임 프로젝트 (0) | 2026.02.05 |
| Unreal_7기 | 챕터 3. C++과 Unreal Engine으로 3D 게임 개발 (0) | 2026.02.02 |
