1. 프로퍼티 레플리케이션 (Property Replication)
액터의 모든 정보를 네트워크 너머로 복제하는 것은 비효율적이다. 따라서 우리가 원하는 특정 속성(Property)만 선택하여 다른 클라이언트에게 동기화하는 기법을 '프로퍼티 레플리케이션'이라고 한다.
- 방향성: 프로퍼티 레플리케이션은 항상 서버(Authority)에서 클라이언트(Proxy)로만 이루어지는 단방향성을 가진다.
- 전제 조건: 서버와 클라이언트 양쪽 모두에 해당 액터가 존재해야 복제가 가능하다.
프로퍼티 레플리케이션 3단계 설정 방법
- 액터 설정: 생성자에서 bReplicates = true;로 설정하여 복제 가능한 액터로 만든다.
- 프로퍼티 지정: 헤더 파일에서 동기화할 변수 위에 UPROPERTY(Replicated) 매크로를 추가한다.
- 수명 주기 등록: .cpp 파일에 #include "Net/UnrealNetwork.h"를 추가하고, GetLifetimeReplicatedProps 함수를 오버라이드하여 DOREPLIFETIME 매크로로 복제할 변수를 명시한다.
2. 게임 판정 로직의 위치: GameMode
네트워크 게임에서 규칙이나 정답을 판정하는 로직은 클라이언트가 변조할 수 없도록 반드시 서버에서 처리해야 한다. 언리얼 엔진에서 GameMode는 서버에만 존재하므로, 숫자 야구의 정답을 생성하고 판정하는 핵심 로직은 GameMode에 구현하는 것이 가장 적합하다.
3. 핵심 코드 분석: 숫자 야구 구현
3.1 세 자리 난수 생성 로직 (GameMode)
중복되지 않는 1~9 사이의 난수 3자리를 생성하여 문자열로 반환하는 핵심 로직이다.
C++
// CXGameModeBase.cpp
FString ACXGameModeBase::GenerateSecretNumber()
{
TArray<int32> Numbers;
// 1부터 9까지의 숫자를 배열에 초기화한다.
for (int32 i = 1; i <= 9; ++i)
{
Numbers.Add(i);
}
// 매번 다른 난수가 나오도록 현재 시간을 시드로 설정하여 초기화한다.
FMath::RandInit(FDateTime::Now().GetTicks());
FString Result;
// 3자리의 숫자를 뽑기 위해 3번 반복한다.
for (int32 i = 0; i < 3; ++i)
{
// 남은 숫자들 중 무작위 인덱스를 하나 선택한다.
int32 Index = FMath::RandRange(0, Numbers.Num() - 1);
// 선택된 숫자를 문자열로 변환하여 결과값에 이어 붙인다.
Result.Append(FString::FromInt(Numbers[Index]));
// 중복된 숫자가 뽑히지 않도록 사용한 숫자는 배열에서 제거한다.
Numbers.RemoveAt(Index);
}
// 완성된 3자리 난수 문자열을 반환한다. (예: "472")
return Result;
}
3.2 정답 판정 로직 (GameMode)
클라이언트가 입력한 추측값(Guess)과 서버의 정답(Secret)을 비교하여 스트라이크(S)와 볼(B)을 판정한다.
C++
// CXGameModeBase.cpp
FString ACXGameModeBase::JudgeResult(const FString& InSecretNumberString, const FString& InGuessNumberString)
{
int32 StrikeCount = 0, BallCount = 0;
// 3자리 숫자를 하나씩 순회하며 비교한다.
for (int32 i = 0; i < 3; ++i)
{
// 자리수와 숫자가 모두 일치하면 스트라이크로 판정함.
if (InSecretNumberString[i] == InGuessNumberString[i])
{
StrikeCount++;
}
// 자리수는 다르지만, 해당 숫자가 정답 문자열 어딘가에 포함되어 있다면 볼로 판정함.
else
{
// 현재 추측한 문자를 FString으로 변환한다.
FString PlayerGuessChar = FString::Printf(TEXT("%c"), InGuessNumberString[i]);
// 정답 문자열이 해당 문자를 포함하고 있는지 확인한다.
if (InSecretNumberString.Contains(PlayerGuessChar))
{
BallCount++;
}
}
}
// 하나도 맞추지 못했다면 "OUT"을 반환한다.
if (StrikeCount == 0 && BallCount == 0)
{
return TEXT("OUT");
}
// 결과 포맷에 맞춰 문자열을 반환한다. (예: "1S2B")
return FString::Printf(TEXT("%dS%dB"), StrikeCount, BallCount);
}
3.3 클라이언트의 채팅 입력을 서버 로직으로 전달 (PlayerController)
클라이언트가 채팅을 쳤을 때, PlayerController에서 Server RPC를 호출하여 서버에 있는 GameMode로 처리를 위임하는 부분이다.
C++
// CXPlayerController.cpp
// 클라이언트가 호출하지만 서버에서 실행되는 Server RPC 함수이다.
void ACXPlayerController::ServerRPCPrintChatMessageString_Implementation(const FString& InChatMessageString)
{
// 서버에서 현재 실행 중인 GameMode를 가져온다.
AGameModeBase* GM = UGameplayStatics::GetGameMode(this);
if (IsValid(GM) == true)
{
// 직접 구현한 커스텀 게임모드(CXGameModeBase)로 캐스팅한다.
ACXGameModeBase* CXGM = Cast<ACXGameModeBase>(GM);
if (IsValid(CXGM) == true)
{
// GameMode의 판정 및 브로드캐스팅 함수를 호출하여 로직을 위임한다.
// GameMode 내부에서 정답을 판정한 뒤, 다시 각 플레이어의 Client RPC를 통해 결과를 뿌려주게 됨.
CXGM->PrintChatMessageString(this, InChatMessageString);
}
}
}
'언리얼' 카테고리의 다른 글
| 언리얼 멀티플레이 지금까지의 플젝 리뷰 (0) | 2026.03.18 |
|---|---|
| 숫자야구 승패 로직 상태 관리 및 레플리케이션 구현 (0) | 2026.03.17 |
| 언리얼 엔진 5 멀티플레이 채팅 및 네트워크 기초 (0) | 2026.03.13 |
| Unreal Engine과 C++를 활용한 다수의 적을 쓰러뜨리는 슈터 게임 프로젝트 (0) | 2026.02.05 |
| Unreal_7기 | 챕터 3. C++과 Unreal Engine으로 3D 게임 개발 (0) | 2026.02.02 |
