[ CH4_Unreal Engine 멀티플레이어 게임 개발 ]
[강의 1-3. 서버의 종류와 데디케이티드 서버]
< 서버의 종류와 구조 >
P2P Server(Peer to Peer Server)
ㄴ 각각의 컴퓨터가 클라이언트이자 서버인 구조

Listen Server
ㄴ 클라이언트이자 서버인 방장(Host)이 있고, 나머지 참가자(Guest)는 모두 클라이언트 역할만 맡는 형태
P2P의 일종

Dedicated Server
ㄴ 서버를 담당하는 컴퓨터가 별도로 존재
서버 - 클라이언트 구조

< Dedicated Server의 실행 흐름도 >
1. PIE를 했거나, Server.exe를 실행하는 식으로 서버 프로세스 실행.
- 실행할 때 Open {Level이름}?Listen 명령어가 인자로 전달
- 해당 Level을 열고 Socket이 생성되며 다른 PC가 접속 가능하게끔 설정
- Listen 명령어가 없다면 싱글플레이
2. Level의 정보를 토대로 GameMode와 GameState 액터를 생성
- Level의 WorldSettings 속성이 존재하고 WorldSettings에는 GameMode와 GameState 정보가 할당
- 이를 통해 GameMode와 GameState 액터를 생성
- 중요한 것은 GameMode 액터는 전체 컴퓨터에서 딱 한 곳(Server)에만 존재
- 그래서 GameMode와 Server를 동일시 설정해도 무방
3. 클라이언트에서 접속 요청
- 클라이언트는 서버의 IP주소와 포트번호로 접속 시도
- 서버는 접속 시도하는 클라이언트에게 Level 정보를 전달
- 클라이언트도 해당 Level을 열고, Level을 여는데 성공했다고 데디 서버에게 알림
4. 접속 성공 시 게임의 정보를 클라이언트에 복제/대입
- Level을 여는데 성공한 클라이언트 전용 PlayerState, PlayerController, PlayerCharacter가 서버에 생성
- PlayerState, PlayerController, PlayerCharacter 등 이 다시 Client1에 복제
- GameState 도 복제
5. 또 다른 클라이언트도 접속 ( 3~4 단계 반복 )
- 마찬가지로 서버는 접속 시도하는 또 다른 클라이언트에게 Level 정보를 전달
- 클라이언트도 해당 Level을 열고, Level을 여는데 성공했다고 데디 서버에게 알림
- Client2 전용 PlayerState, PlayerController, PlayerCharacter가 서버에 생성
- 이것이 다시 Client2에 복제
- GameState도 복제
- 이때, 클라이언트 간의 PlayerState와 PlayerCharacter도 복제되면서 서로 존재 확인 가능
※ 다른 클라이언트의 PlayerState와 PlayerCharacter의 정보는 저장하지만
PlayerController는 본인의 클라이언트의 정보만 저장 ※
< 이미지 설명 >

↓

↓

↓

↓

↓

< 서버 - 클라이언트 구조의 중요한 특징 >
- 클라이언트와 클라이언트 간의 통신이 불가능
- 오직 서버와 클라이언트 사이의 통신만 가능
- RPC나 프로퍼티 레플리케이션에도 영향
[ 강의 1-4. 실습 환경 설정 ]


- 위젯 객체는 Owning Client에서만 생성
- Owning Client를 판별하기 위해서 IsLocalController() 함수를 호출
< PlayerController 의 BeginPlay 함수 안에 작성 >
// Owning Client 인지 확인
if (IsLocalController() == false)
{
return;
}
< 데디케이티드 서버 환경 설정 >
< Allow Late Joining 체크 >

< Always on top > (필수 X)
- 새 클라이언트 화면이 항상 위에 생성
< Multiplayer Options - Launch Seperate Server >
- 체크 시 서버 따로 생성
- 체크 해제 시 에디터 화면이 클라이언트이자 서버
< Multiplayer Options - Run Under One Process >
- 체크하면 같은 프로세스 내라서 공유하는 에셋이 생성
- 따라서 확실한 멀티플레이 환경이라고 보기엔 무리
- 성능상의 이점 : 체크하지 않으면 서버와 각 클라이언트들이 다른 프로세스로 분기
- 추후에 멀티플레이 디버깅 시 체크하고 로깅으로 확인 가능
< “Net Mode”는 “Play as Client”로 설정 >
- Net Mode - Play as Client : Dedicated 환경
[ 강의 1-5. NetMode, NetConnection, NetDriver ]
<NetMode >
< 정의 >
- 해당 게임 프로세스가 네트워크 상에서 어떤 역할을 하고 있는지를 의미
- 싱글(NM_StandAlone) 서버(NM_Listen, NM_DedicatedServer), 클라이언트(NM_Client)
< NetMode의 필요성 >
- 로직이 서버에서 돌고 있는지 클라이언트에서 돌고 있는지를 확인 할 때 사용
- 멀티플레이 개발 시에 같은 코드가 여러 PC에서 동작 -> 서버 PC에서, 내 PC에서, 다른 사람의 PC에서 동작

위 사진처럼 구조가 짜여져 있다는 가정 하에
PlayerCharacter :: BeginPlay 함수에 UE_LOG를 작성했다면
ㄴ Server ,C1,C2에 해당돼있는 모든 PlayerCharacter의 UE_LOG가 호출된다
빨간 PlayerController :: BeginPlay 함수에 UE_LOG를 작성했다면
ㄴ Server,C1 의 PlayerController에 UE_LOG가 호출된다
< NetMode 와 NetRole >
- NetMode : 월드 단위의 속성값
- NetRole : 액터 단위의 속성값
< UWorld::InternalGetNetMode() 함수 살펴보기 >
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);
// 서버인데, 클라이언트로 참여하고 있다? 리슨서버. 서버인데 참여하고 있지 않다? 데디서버.
}
bool UNetDriver::IsServer() const
{
return ServerConnection == NULL;
// 클라이언트는 무조건 서버 커넥션을 가지고 있다.
}
< NetConnection >
< 정의 >
- 다른 PC와의 연결이 발생하면 그에 대응하는 UNetConnection 객체도 생성
- 서버에 클라이언트가 접속하면 서버에는 ClientConnection 객체가 추가
- 반대로 클라이언트에는 ServerConnection 객체가 생성
- 두 PC는 UNetConnection 객체를 통해 통신
- UNetDriver는 생성된 UNetConnection 객체를 소유하고 관리
- 서버 PC에 생성된 UNetDriver는 접속한 클라이언트의 수 만큼 UNetConnection을 관리
- 클라이언트 PC에 생성된 UNetDriver는 ServerConnection 단 하나만을 관리
서버-클라이언트 구조의 템플릿 - NetDriver : 멀티플레이 실행 장치
// UNetDriver.h
...
UCLASS(...)
class UNetDriver : public UObject, public FExec
{
...
// 클라이언트 전용 (단일 취급)
UPROPERTY()
TObjectPtr<class UNetConnection> ServerConnection;
// 서버 전용 (복수 취급 (배열))
UPROPERTY()
TArray<TObjectPtr<UNetConnection>> ClientConnections;
...
}
UNetConnection* AActor::GetNetConnection() const
{
return Owner ? Owner->GetNetConnection() : nullptr;
// 여기서 오너는 액터/폰/컨트롤러 등등이 해당
// Owner가 지정되어 있지 않으면 통신 불가
}
class UNetConnection* APawn::GetNetConnection() const
{
if ( Controller ) // 컨트롤러가 있다면
{
return Controller->GetNetConnection(); // 컨트롤러의 GetNetConnection()을 호출.
}
return Super::GetNetConnection(); // 없다면 AActor::GetNetConnection() 함수 호출.
}
UNetConnection* APlayerController::GetNetConnection() const
{
return (Player != NULL) ? NetConnection : NULL;
// 플레이어가 존재한다면 NetConnection 객체를 반환.
}
< 언리얼에서의 Ownership >
- 하나의 ClientConnection은 하나의 PlayerController를 소유 -> PlayerController의 Owning Connection은 ClientConnection 와 동일
- PlayerController가 빙의하는 폰의 Owner 속성은 해당 PlayerController로 설정
- 폰에 무기 액터가 생성되고, 무기 액터의 Owner 속성에 해당 폰을 설정 가능
- 패밀리 : ClientConnection에서부터 무기 액터에 이르는 소유 관계
- 소유 관계 속에 있는 액터가 본인의 Owning Connection을 얻으려면
AActor::GetNetConnection() 함수를 호출 - 이 소유 관계가 후에 배울 RPC와 Property Replication과 관련

< NetDriver >
- 언리얼 네트워크 통신에서 로우레벨 동작들을 관리하는 클래스
- 싱글플레이에서는 UNetDriver 객체가 생성 X
- 멀티플레이에서만 UWorld::Listen() 함수를 통해 UNetDriver 객체가 생성
- 멀티플레이에 참여하는 각 PC마다 UNetDriver 객체가 생성
- ex) 컴퓨터 포맷 후 Wifi Driver를 설치하는 것과 유사합니다.
// 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);
...
}
...
}
[ NetMode에 대한 추가적인 설명 ]
< StandAlone >
- 게임이 원격 클라이언트의 연결을 허용하지 않는 서버로 실행
- 커넥션이 없음. 게임에 참여하는 모든 플레이어는 로컬 플레이어
- 싱글 플레이 및 로컬 플레이 게임에 사용
- 로컬 플레이어에 맞게 서버 측 로직과 클라이언트측 로직을 모두 실행
< Client >
- 게임이 네트워크 멀티플레이 세션에서 서버에 연결된 클라이언트로 실행
- 서버 측 로직을 실행하지 않습니다. 서버로부터 복제된 Proxy를 보여주는 역할
< Listen Server >
- 게임이 네트워크 멀티플레이어 세션을 호스팅 하는 서버로 실행
- 원격 클라이언트의 연결을 수락하고 로컬 플레이어를 서버에 직접 배치
즉, 서버 자기 자신도 게임에 직접 참여 - 이 모드는 캐주얼 협동 및 경쟁 멀티플레이어에 자주 사용
< Dedicated Server >
- 게임이 네트워크 멀티플레이 세션을 호스팅 하는 서버로 실행
- 원격 클라이언트의 연결을 허용하지만 로컬 플레이어는 존재 X
따라서 그래픽, 사운드, 입력 및 기타 플레이어 중심 기능이 필요 없으니 삭제 - 이 모드는 지속적이고 안전한 대규모 멀티플레이어가 필요한 게임에 주로 사용
[ NetMode에 따른 액터 위치 ]
서버에만 존재하는 액터
- GameMode
서버와 모든 클라이언트에 존재하는 액터
- 배경 액터와 Pawn
서버와 클라이언트에만 존재하는 액터
- PlayerController , ABP
클라이언트에만 존재하는 언리얼 오브젝트
- UI
[ 강의 1-6. NetRole ]
< Authority와 Proxy >
- 서버에 스폰 된 액터가 가진 NetRole 속성 값은 언제나 Authority
ㄴ 서버에 스폰 된 액터에서 수행될 로직은 “권한을 가지고 있다”를 의미
ㄴ 게임에 중대한 영향을 끼치는 로직은 NetRole이 Authority일 때만 수행하게 설정 - NetRole이 Authority인 액터가 클라이언트로 복제되었을 때,
클라이언트에 복제된 액터의 NetRole 속성 값은 Proxy : “허상” - 여기서는 중요한 로직을 수행 X
< 로컬 롤과 리모트 롤 >
로컬 롤(Local Role) : 현재 동작하는 컴퓨터에서의 롤
리모트 롤(Remote Role) : 커넥션으로 연결된 반대편 컴퓨터에서의 롤
< NetRole의 종류 >
< None >
- 레플리케이션 되지 않는 액터
- 다만, 무조건적이진 않음
- ex) LocalRole은 Authority이고 RemoteRole이 None이라면
서버에서 스폰 되고, 클라쪽으로 레플리케이션 되지 않는 액터라고 취급
< Authority >
- 게임에 중대한 영향을 끼칠 수 있는 권한을 가진 액터
- 서버에서 스폰된 액터가 LocalRole으로 Authority를 가질 수 있음
- ex) GameMode
< Autonomous Proxy >
- Authority 액터의 복제본
서버로부터 데이터를 수신 받아서 동기화도 되면서, 서버로 송신도 가능 - ex) PlayerController
< Simulated Proxy >
- Authority 액터의 복제본
서버로부터 수신 받아서 동기화만 가능 - ex) AnotherPlayer 의 PlayerCharacter