NetMode
NetDriver, NetConnection
OwnerShip
NetRole
로컬 컨트롤러, 로컬 플레이어
PossessedBy
게임 프로세스가 현재 네트워크 상에서 어떤 역할을 하고 있는지를 의미
스킬 사용, 데미지로직 등 게임에서 중요한 로직들은 클라이언트가 아니라 서버에서 진행되어야 함
또 하나의 코드가 서버PC, 내PC, 다른 PC 등 여러 곳에 존재하며 동작하기도 함

PlayerCharacter의 BeginPlay()에 있는 코드는 서버, 모든 클라이언트 PC에서 동작함PlayerController의 코드는 서버와 내 PC에 존재하고 동작따라서, 각 로직은 현재 자신이 서버에서 돌고 있는지 클라이언트에서 돌고 있는지를 알아야하고, 이때 NetMode를 사용
NM_StandAlone : 싱글플레이어의 경우 NM_Listen : 서버이면서 동시에 클라이언트일 경우NM_DedicatedServer : Dedicated 서버의 경우NM_Client : 클라이언트의 경우// EngineBaseTypes.h 언리얼 엔진 코드. enum으로 정의하여 사용함
enum ENetMode
{
/** Standalone: a game without networking, with one or more local players.
Still considered a server because it has all server functionality. */
NM_Standalone,
/** Dedicated server: server with no local players. */
NM_DedicatedServer,
/** Listen server: a server that also has a local player who is hosting the game,
available to other players on the network. */
NM_ListenServer,
/**
* Network client: client connected to a remote server.
* Note that every mode less than this value is a kind of server,
so checking NetMode < NM_Client is always some variety of server.
*/
NM_Client,
/* ENetMode 열거형(enumeration)의 마지막 값으로,
실제 네트워크 모드를 나타내기보다는 사용 가능한 네트워크 모드의 총 개수를 정의하거나
배열의 크기를 지정하는 데 사용되는 내부 상수 */
NM_MAX,
};
NetConnection과 NetDriver을 이용하여 정해짐// World.cpp
ENetMode UWorld::InternalGetNetMode() const
{
if (NetDriver != NULL)
{
const bool bIsClientOnly = IsRunningClientOnly(); // 클라만 존재하는지
return bIsClientOnly ? NM_Client : NetDriver->GetNetMode();
}
}
InternalGetNetMode()함수는 위에서 본 ENetMode열거형을 반환값으로 사용
NetDriver가 설정되어 있으면, 네트워킹하는 멀티플레이이므로, 함수들 통해 넷모드 반환
IsRunningClinetOnly()함수를 찾아보니 ForceInline 키워드가 존재.
원래 Inline키워드로 함수호출의 오버헤드를 줄이고, 직접 내재화 시키는 방식인데 컴파일러가 스스로 판단하고 해주기에 안 될수도 있음.
ForceInline 쓰면 무조건 해주므로 장점도 있지만, 강제이기에 조심해서 사용해야함

암튼, IsRunningClientOnly()함수를 통해 클라만 있으면 먼저 NM_Client로 확정. 아니라면 GetNetMode()를 통해 확인
ENetMode UNetDriver::GetNetMode() const {
// ... //
return (IsServer() ?
(GIsClient ? NM_ListenServer : NM_DedicatedServer) :
NM_Client);
}
NetMode 열거형에서 NM_Client말고는 다 서버의 일환으로 취급했음.IsServer()로 서버 아니면 NM_Client반환.bool UNetDriver::IsServer() const {
return ServerConnection == NULL;
}
ServerConnection이 있고, 서버는 본인이 서버니까 없음. 따라서 ServerConnection으로 서버인지를 판단.StandAlone, ListenServer, DedicatedServer는 서버로 취급됨ServerConnection 설정이 안 되어있으므로 서버)static FString GetNetModeString(const AActor* InWorldContextActor)
{
FString NetModeString = TEXT("None");
if (IsValid(InWorldContextActor))
{
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;
}
위에서 NetMode를 얻기 위해 사용했던 객체들
NetDriver는 언리얼 엔진에서 네트워크 통신을 총괄하는 핵심 클래스
NetDriver는 서버와 클라이언트 사이의 데이터 전송을 관리하며, 여러 NetConnection을 통제
다른 PC와 연결되면 NetConnection이 생성됨. 이 연결을 NetDriver가 소유하고 관리
서버의 경우 클라가 접속하면 NetDriver 객체에 ClientConnection객체가 생성되고 관리함.
그래서 서버는 접속한 클라 개수만큼의 ClientConnection이 생김
당연히, ServerConnection은 없음(NULL)
그래서 위에서 본 IsServer()에서 이 ServerConnection으로 서버인지를 판단
클라의 경우 서버에 접속했으므로 NetDriver객체에 ServerConnection객체가 하나만 생성되고 관리됨.
ClientConnection은 없음
// 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) */
// 서버의 경우만 여러 개 가지니까 배열로 선언
UPROPERTY()
TArray<TObjectPtr<UNetConnection>> ClientConnections;
}
NetDriver의 역할
NetDriver는 서버가 네트워크 시작할 때 월드의 Listen함수를 호출하여 생성되고, 이 객체를 통해 통신이 된다
클라이언트의 경우 서버와 달리 서버에 접속을 요청할 때, NetDriver가 생성됨
싱글게임의 경우 Listen호출 안 하니 NetDriver생성 안 됨
// AActor.cpp
UNetConnection* AActor::GetNetConnection() const
{
return Owner ? Owner->GetNetConnection() : nullptr;
}
Actor의 경우 Owner가 있을때만 NetConnection을 가져오고 없다면 nullptr임.
다시 말해, Actor가 NetConnection을 가지려면, 액터가 반드시 Owner를 가져야 하며, 그 오너가 NetConnection을 소유하고 있어야 함
액터는 자신의 NetConnection은 없고, 대신 액터의 Owner가 NetConnection을 소유하는 구조
class UNetConnection* APawn::GetNetConnection() const
{
if ( Controller )
return Controller->GetNetConnection();
return Super::GetNetConnection();
}
폰의 경우는 컨트롤러가 있으면 컨트롤러의 패밀리에 들어가 NetConnection을 가짐
컨트롤러가 없으면 Actor의 GetNetConnection호출하여 똑같이 실행
UNetConnection* APlayerController::GetNetConnection() const
{
return (Player != NULL) ? NetConnection : NULL;
}
플레이어 컨트롤러는 해당 플레이어의 NetConnection을 직접 관리하는 객체
따라서 연결된 플레이어가 있으면 NetConnection가지는 거니까 바로 반환
플레이어가 없으면 Owner가 없는것이므로 NULL반환
위에서 언급했듯이, 서버에서 하나의 ClientConnection은 하나의 PlayerController를 소유

그리고 컨트롤러가 Possess한 캐릭터는 컨트롤러를 Owner로 설정함
또 이 캐릭터가 무기를 소유하여 Owner관계를 맺으면 ClientConnection으로부터 Weapon까지를 하나의 패밀리라고 부르게 되며, 이 소유관계에 속한 액터들은 GetNetConnection()을 이용하여 자신의 NetConnection을 얻을 수 있다
이전에 공부한 NetMode는 월드 단위, 해당 월드가 서버PC의 월드인지, 클라이언트PC의 월드인지에 대한 정보
하지만, 실제 우리가 작성할 대부분의 코드는 액터관련 코드이고 이 액터가 서버PC, 클라이언트PC 등 어디에서 스폰되었는지를 확인하는 것이 NetRole
서버에서 스폰된 액터의 NetRole은 Authority. 권한을 가졌다는 뜻
이 액터가 클라에 복제되면 NetRole이 Autonomous Proxy. 허상이라는 뜻
하나의 액터는 2개의 Role을 가짐
액터의 Local Role은 내 PC에서의 해당액터의 NetRole
내 액터가 NetConnection을 통해 다른 PC에서 복제되었을 때의 다른 PC에서의 Local Role이 내 액터의 Remote Role

서버에서 스폰된 액터의 Local Role은 Authority, Remote Role은 Autonomous Proxy
클라에서 복제된 액터의 Local Role은 Autonomous Proxy, Remote Role은 Authority
NoneLocalRole이 Authority이고, RemoteRole은 NoneAuthorityLocalRoleAutonomous ProxyAuthority액터의 복제본PlayerControllerSimulated ProxyPlayerCharacter
// EngineTypes.h
/** The network role of an actor on a local/remote network context */
UENUM(BlueprintType)
enum ENetRole : int
{
/** No role at all. */
ROLE_None UMETA(DisplayName = "None"),
/** Locally simulated proxy of this actor. */
ROLE_SimulatedProxy UMETA(DisplayName = "Simulated Proxy"),
/** Locally autonomous proxy of this actor. */
ROLE_AutonomousProxy UMETA(DisplayName = "Autonomous Proxy"),
/** Authoritative control over the actor. */
ROLE_Authority UMETA(DisplayName = "Authority"),
ROLE_MAX UMETA(Hidden),
};
static FString GetRoleString(const AActor* InActor)
{
FString RoleString = TEXT("None");
if (IsValid(InActor))
{
FString LocalRoleString = UEnum::GetValueAsString(TEXT("Engine.ENetRole"),
InActor->GetLocalRole());
FString RemoteRoleString = UEnum::GetValueAsString(TEXT("Engine.ENetRole"),
InActor->GetRemoteRole());
RoleString = FString::Printf(TEXT("%s / %s"),
*LocalRoleString,
*RemoteRoleString);
}
return RoleString;
}
이 함수를 PlayerCharacter의 BeginPlay()와 PossessedBy()에서 호출 하여 서버에서 확인해보면 서로 다른 NetRole이 나옴
먼저 서버에서 캐릭터의 LocalRole은 Authority
그리고 RemoteRole이 BeginPlay()시점에는 아직 캐릭터가 컨트롤러에 연결되지 않았으므로, Simulated Proxy이고, PossessedBy()에선 컨트롤가 연결되었으므로 Autonomous Proxy로 바뀜
즉, 컨트롤러가 캐릭터를 소유해 캐릭터의 Owner가 컨트롤러가 됨
NetConnection은 컨트롤러를 직접 소유, 관리하므로 이 둘도 Owner관계여서, ClientConnection->컨트롤러->캐릭터 다 Autonomous Proxy Role을 가진다
NetMode처럼 NetRole도 패밀리에 속하면 같은 Role가짐
AActor::HasAuthority() : LocalRole이 Authority인지 아닌지
APawn::IsLocallyControlled() : 컨트롤러가 로컬컨트롤러인지 반환.
클라에서의 컨트롤러인지를 의미. Listener의 경우도 서버/클라 같이니까 참
bool APawn::IsLocallyControlled() const
{
return ( Controller && Controller->IsLocalController() );
}
bool AController::IsLocalController() const
{
const ENetMode NetMode = GetNetMode();
if (NetMode == NM_Standalone)
{
// Not networked.
return true;
}
if (NetMode == NM_Client && GetLocalRole() == ROLE_AutonomousProxy) // 리슨서버 배제
{
// Networked client in control.
return true;
}
// 리슨 서버
if (GetRemoteRole() != ROLE_AutonomousProxy && GetLocalRole() == ROLE_Authority)
{
// Local authority in control.
return true;
}
return false;
}
NetRole을 이용하여, 로컬컨트롤러인지 확인하고, 맞으면 입력 로직 및 UI 처리서버에 접속하여 생긴 캐릭터 A와 그냥 서버에서 생성한 캐릭터 B는 둘 다 Authority임
하지만 A는 ClientConnection과 Ownership관계로 인해, Autonomous가 되고, B는 ClientConnection과 연결 안 되기에 Simulated가 되어 서로 구분이 가능해진다
로컬 컨트롤러
로컬 플레이어