1-4. NetMode, NetConnection, NetDriver
NetMode
해당 게임 프로세스가 네트워크 상에서 어떤 역할을 하고 있는지를 의미.
- 싱글(NM_StandAlone)
- 서버(NM_Listen, NM_DedicatedServer)
- 클라이언트(NM_Client)
데미지를 입어서 HP가 줄어드는 로직을 어느 컴퓨터에서 처리해야되나?
- 클라이언트 컴퓨터에서 한다면 해킹에 취약해짐.
온라인 게임 핵을 만드는 과정
- 서버와 클라이언트가 주고받는 패킷을 분석.
스킬을 계속 쓰면서, 이진법으로 이뤄진 패킷 속 공통부분을 찾음.
데미지일 것 같은 패킷 위치에 내가 원하는 숫자를 써서 서버로 패킷을 보내면 끝.
해킹을 방지하기 위해서 데미지 처리 로직은 서버 컴퓨터에서만 처리해야함.
데미지 값이 패킷에 담기지 않으므로 좀 더 안전함.
게임에 중대한 영향을 미치는 로직은 서버 컴퓨터에서 처리함.
NetMode의 필요성
작성한 로직이 서버에서 돌고 있는지 클라이언트에서 돌고 있는지 확인할 때 필요.
멀티플레이 개발 시에 가장 큰 걸림돌이 같은 코드가 여러 PC에서 동작한다는 것.
서버 PC, 내 PC, 다른 사람의 PC에서 동작.
예시:
PlayerCharacter에 BeginPlay > UE_LOG를 찍게 된다면 서버, Client01, Client02에 호출됨
PlayerController에 BeginPlay > UE_LOG를 찍게 된다면 서버, Client01에 호출됨

UWorld::InternalGetNetMode() 함수
// World.cpp
...
ENetMode UWorld::InternalGetNetMode() const
{
if ( NetDriver != NULL ) // 넷드라이버가 설정되어 있으면
{
const bool bIsClientOnly = IsRunningClientOnly();
return bIsClientOnly ? NM_Client : NetDriver->GetNetMode(); // 클라 아니면 서버 둘 중 하나.
}
...
}
// UNetDriver.cpp
ENetMode UNetDriver::GetNetMode() const
{
#if WITH_EDITOR
if (World && World->WorldType == EWorldType::PIE && IsServer())
{
FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(World);
if (WorldContext && WorldContext->RunAsDedicated) // 데디로 실행했다면
{
return NM_DedicatedServer; // 데디 서버 반환.
}
}
#endif
return (IsServer() ? (GIsClient ? NM_ListenServer : NM_DedicatedServer) : NM_Client);
// 서버인데, 클라이언트로 참여하고 있다? 리슨서버. 서버인데 참여하고 있지 않다? 데디서버.
}
// UNetDriver.cpp
bool UNetDriver::IsServer() const
{
// Client connections ALWAYS set the server connection object in InitConnect()
// @todo ONLINE improve this with a bool
return ServerConnection == NULL;
// 클라이언트는 무조건 서버 커넥션을 가지고 있다.
}
NetConnection
- 다른 PC와의 연결이 발생하면 그에 대응하는 UNetConnection 객체도 생성.
- 서버에 클라이언트가 접속하면 서버에는 ClientConnection 객체가 추가.
- 반대로 클라이언트에는 ServerConnection 객체가 생성.
- 두 PC는 UNetConnection 객체를 통해 통신.
- UNetDriver는 생성된 UNetConnection 객체를 소유하고 관리
- 서버 PC에 생성된 UNetDriver는 접속한 클라이언트의 수 만큼 UNetConnection을 관리
- 클라이언트 PC에 생성된 UNetDriver는 ServerConnection 단 하나만을 관리
// UNetDriver.h
...
UCLASS(...)
class UNetDriver : public UObject, public FExec
{
...
/** Connection to the server (this net driver is a client) */
UPROPERTY()
TObjectPtr<class UNetConnection> ServerConnection;
/** Array of connections to clients (this net driver is a host) - unsorted, and ordering changes depending on actor replication */
UPROPERTY()
TArray<TObjectPtr<UNetConnection>> ClientConnections;
...
}
// AActor.cpp
UNetConnection* AActor::GetNetConnection() const
{
return Owner ? Owner->GetNetConnection() : nullptr;
// 여기서 오너는 액터/폰/컨트롤러 등등이 될 수 있음.
// 코드에서 볼 수 있듯이 Owner가 지정되어 있지 않으면 통신이 안되게끔 되어 있음.
}
class UNetConnection* APawn::GetNetConnection() const
{
// if have a controller, it has the net connection
if ( Controller ) // 컨트롤러가 있다면
{
return Controller->GetNetConnection(); // 컨트롤러의 GetNetConnection()을 호출.
}
return Super::GetNetConnection(); // 없다면 AActor::GetNetConnection() 함수 호출.
}
UNetConnection* APlayerController::GetNetConnection() const
{
// A controller without a player has no "owner"
return (Player != NULL) ? NetConnection : NULL;
// 플레이어가 존재한다면 NetConnection 객체를 반환.
}
언리얼에서의 Ownership
- 하나의 ClientConnection은 하나의 PlayerController를 소유.
- 즉, PlayerController의 Owning Connection은 ClientConnection인 셈.
- PlayerController가 빙의하는 폰의 Owner 속성은 해당 PlayerController로 설정.
- 폰에 무기 액터가 생성되고, 무기 액터의 Owner 속성에 해당 폰을 설정할 수도 있음.
- ClientConnection에서부터 무기 액터에 이르는 소유 관계를 패밀리라고도 부름.
- 소유 관계 속에 있는 액터가 본인의 Owning Connection을 얻으려면 AActor::GetNetConnection() 함수를 호출하면 됨.

NetDriver
- 언리얼 네트워크 통신에서 로우레벨 동작들을 관리하는 클래스.
- 싱글플레이에서는 UNetDriver 객체가 생성 X.
- 멀티플레이에서만 UWorld::Listen() 함수를 통해 UNetDriver 객체가 생성.
- 멀티플레이에 참여하는 각 PC마다 UNetDriver 객체가 생성.
// UWorld.cpp
bool UWorld::Listen( FURL& InURL )
{
#if WITH_SERVER_CODE
...
// Create net driver. 넷드라이버 만드는 부분이 있다~ 정도.
if (GEngine->CreateNamedNetDriver(this, NAME_GameNetDriver, NAME_GameNetDriver))
{
NetDriver = GEngine->FindNamedNetDriver(this, NAME_GameNetDriver);
...
}
...
}
멀티플레이 디버깅용 로그 매크로 작성
// ChatX.h
#pragma once
#include "CoreMinimal.h"
class ChatXFunctionLibrary
{
public:
static void MyPrintString(const AActor* InWorldContextActor, const FString& InString, float InTimeToDisplay = 1.f, FColor InColor = FColor::Cyan)
{
if (IsValid(GEngine) == true && IsValid(InWorldContextActor) == true)
{
if (InWorldContextActor->GetNetMode() == NM_Client || InWorldContextActor->GetNetMode() == NM_ListenServer)
{
GEngine->AddOnScreenDebugMessage(-1, InTimeToDisplay, InColor, InString);
}
else
{
UE_LOG(LogTemp, Log, TEXT("%s"), *InString);
}
}
}
static FString GetNetModeString(const AActor* InWorldContextActor)
{
FString NetModeString = TEXT("None");
if (IsValid(InWorldContextActor) == true)
{
ENetMode NetMode = InWorldContextActor->GetNetMode();
if (NetMode == NM_Client)
{
NetModeString = TEXT("Client");
}
else
{
if (NetMode == NM_Standalone)
{
NetModeString = TEXT("StandAlone");
}
else
{
NetModeString = TEXT("Server");
}
}
}
return NetModeString;
}
};
// ChatX.cpp
#include "ChatX.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ChatX, "ChatX" );
// CXPlayerController.cpp
...
#include "ChatX.h"
...
void ACXPlayerController::PrintChatMessageString(const FString& InChatMessageString)
{
//UKismetSystemLibrary::PrintString(this, ChatMessageString, true, true, FLinearColor::Red, 5.0f);
FString NetModeString = ChatXFunctionLibrary::GetNetModeString(this);
FString CombinedMessageString = FString::Printf(TEXT("%s: %s"), *NetModeString, *InChatMessageString);
ChatXFunctionLibrary::MyPrintString(this, CombinedMessageString, 10.f);
// 문제 상황이 생기면, 위와 같은 로깅 함수로 다양한 변수의 값들과 함수이름을 확인 후
// 문제 원인 찾기
}
디버깅 로그 제거
// CXPlayerController.cpp
...
void ACXPlayerController::PrintChatMessageString(const FString& InChatMessageString)
{
//FString NetModeString = ChatXFunctionLibrary::GetNetModeString(this);
//FString CombinedMessageString = FString::Printf(TEXT("%s: %s"), *NetModeString, *InChatMessageString);
//ChatXFunctionLibrary::MyPrintString(this, CombinedMessageString, 10.f);
ChatXFunctionLibrary::MyPrintString(this, InChatMessageString, 10.f);
}
NetMode에 대한 추가적인 설명
NetMode는 월드의 속성
StandAlone
- 게임이 원격 클라이언트의 연결을 허용하지 않는 서버로 실행 중. (커넥션이 없음)
- 게임에 참여하는 모든 플레이어는 로컬 플레이어.
- 싱글 플레이 및 로컬 플레이 게임에 사용.
- 로컬 플레이어에 맞게 서버 측 로직과 클라이언트측 로직을 모두 실행.
Client
- 게임이 네트워크 멀티플레이 세션에서 서버에 연결된 클라이언트로 실행.
- 서버 측 로직을 실행 X.
- 서버로부터 복제된 Proxy를 보여주는 역할.
Listen Server
- 게임이 네트워크 멀티플레이어 세션을 호스팅 하는 서버로 실행 중.
- 원격 클라이언트의 연결을 수락하고 로컬 플레이어를 서버에 직접 배치. (서버 자기 자신도 게임에 직접 참여)
- 이 모드는 캐주얼 협동 및 경쟁 멀티플레이어에 자주 사용.
Dedicated Server
- 게임이 네트워크 멀티플레이 세션을 호스팅 하는 서버로 실행.
- 원격 클라이언트의 연결을 허용하지만 로컬 플레이어가 없음.
따라서 그래픽, 사운드, 입력 및 기타 플레이어 중심 기능이 필요 없으니 삭제됨. - 이 모드는 지속적이고 안전한 대규모 멀티플레이어가 필요한 게임에 주로 사용.
NetMode에 따른 액터 위치
서버에만 존재하는 액터
- 게임 모드
서버와 모든 클라이언트에 존재하는 액터
- 배경 액터와 폰
서버와 클라이언트에만 존재하는 액터
- 플레이어 컨트롤러
클라이언트에만 존재하는 언리얼 오브젝트
- UI
'개인 강의 > Unreal Engine 멀티플레이어 게임 개발' 카테고리의 다른 글
| Unreal Engine 멀티플레이어 게임 개발 2-1 (0) | 2026.07.29 |
|---|---|
| Unreal Engine 멀티플레이어 게임 개발 1-5 (0) | 2026.07.29 |
| Unreal Engine 멀티플레이어 게임 개발 1-3 (0) | 2026.07.28 |
| Unreal Engine 멀티플레이어 게임 개발 1-2 (0) | 2026.07.28 |
| Unreal Engine 멀티플레이어 게임 개발 1-1 (0) | 2026.07.28 |