멀티 플레이 게임의 세계
멀티 플레이어 게임은 두 명 이상의 플레이어가 참여하는 다중 게임 세션으로 이루어집니다. 이러한 게임에서는 여러 플레이어가 동시에 게임을 진행하며, 각각의 플레이어는 별도의 시스템에서 자신의 게임 인스턴스를 실행합니다.
예를 들어, 1번 플레이어가 자신의 캐릭터를 움직이면 이 움직임은 네트워크를 통해 2번 플레이어에게 전송되어야 합니다. 마찬가지로, 2번 플레이어의 움직임도 1번 플레이어에게 전달되어야 합니다. 이는 멀티 플레이어 게임의 핵심적인 요소로, 실시간으로 여러 플레이어 간의 상호작용을 가능하게 합니다. 이와 같은 상호 작용은 네트워크를 통해 정보를 교환함으로써 이루어지며, 이를 위한 다양한 방법이 존재합니다.
멀티 프로그래밍 서버 구현 방식
P2P(Peer to Peer)
P2P 방식은 각 플레이어의 컴퓨터가 서로 데이터를 직접 주고받는 방식입니다. 별도의 중앙 서버 없이 네트워크에 참여하는 각 노드(참여자 또는 장치)가 서버와 클라이언트의 역할을 동시에 수행합니다. 각 노드는 다른 노드와 직접 통신하며, 데이터나 자원을 공유하고, 서로간에 파일이나 정보를 직접 전송할 수 있습니다.
장점
- 난이도가 낮은 구현 방식: 다른 세션들을 직접적으로 연결하면 되기 때문에 구현이 쉬운 편이다.
- 서버 비용 절감: 중앙 서버를 유지할 필요가 없기 때문에, 서버 운영에 대한 비용이 크게 줄어듭니다.
- 확장성: 게임이 인기를 얻으면서 사용자가 증가해도, P2P 네트워크는 자연스럽게 확장되어 네트워크 부하를 분산시킬 수 있습니다.
- 저지연 통신: 사용자들이 지리적으로 서로 가까운 경우, 직접적인 데이터 교환으로 인해 지연 시간이 줄어들 수 있습니다.
- 고장에 대한 강인함: 중앙 서버에 의존하지 않기 때문에, 서버 고장에 따른 전체 네트워크의 다운 위험이 적습니다.
단점
- 피어 관리: 네트워크 내의 모든 피어들을 효과적으로 관리하고 동기화하는 것이 어렵습니다.
- 불균일한 성능: 사용자의 컴퓨터 성능이나 인터넷 연결 상태에 따라 게임의 성능이 달라질 수 있어, 일관된 게임 경험을 제공하기 어렵습니다.
- 데이터 동기화 문제: 여러 플레이어가 각자의 기기에서 게임을 진행하면서, 각자의 게임 상태는 지속적으로 변화합니다. 각각의 상태 변화가 다른 기기에 실시간으로 전달되지까지 시간 소요가 발생하기 때문에 모든 플레이어 게임 상태가 일치하는 경우가 드뭅니다. 즉, 어떤 게임 상태가 올바르고 정확한 원본 상태인지 정의할 수 없습니다.
클라이언트-서버(Client-Server)
클라이언트-서버 방식은 게임 데이터와 상태를 중앙 서버에서 관리하고 처리하는 핵심 역할을 수행합니다. 플레이어들은 각자의 클라이언트(즉, 게임을 실행하는 개별 기기)를 통해 게임 서버에 접속하며, 게임 내에서의 자신의 행동, 위치, 상호작용 등과 관련된 데이터를 서버에 보냅니다. 이러한 데이터는 서버에서 처리되어, 게임의 현재 상태를 반영하게 됩니다.
장점
- 일관된 게임 환경: 중앙 서버가 게임 상태를 관리하기 때문에 모든 플레이어에게 일관된 게임 경험을 제공할 수 있습니다.
- 유지 보수 및 업데이트 용이: 게임의 업데이트나 유지 보수 작업이 서버 측에서 이루어지므로 클라이언트 측의 개별 조치 필요성이 줄어듭니다.
- 데이터 관리와 동기화 용이: 모든 게임 데이터와 상태가 중앙 서버에 저장되고 관리되므로, 데이터의 동기화와 관리가 용이합니다.
- 보안성 강화: 중앙 서버를 통해 사용자 인증과 데이터 검증을 수행할 수 있어, 해킹이나 부정 행위를 방지하기 쉽습니다.
단점
- 서버 비용 및 유지 관리: 중앙 서버의 구축 및 유지 관리에 상당한 비용과 노력이 필요합니다.
- 서버 고장 시 전체 서비스 영향: 중앙 서버에 문제가 발생하면 전체 게임 서비스에 영향을 줄 수 있습니다.
- 지연 시간(Latency) 문제: 플레이어의 위치나 서버의 위치에 따라 지연 시간이 발생할 수 있으며, 이는 게임 플레이에 영향을 줄 수 있습니다.
- 스케일링 제한: 서버의 처리 능력이나 대역폭에 한계가 있어, 사용자 수가 급증할 경우 서비스 품질이 저하될 수 있습니다.
- 부하 분산의 어려움: 높은 트래픽 상황에서 서버 부하를 효과적으로 분산시키는 것이 어려울 수 있습니다.
클라이언트-서버 구현 방식
Listen server
게임을 호스팅하는 플레이어의 컴퓨터가 서버 역할을 겸합니다. 이는 플레이어가 자신의 게임을 다른 플레이어와 공유하면서 동시에 게임을 즐기는 구조입니다.
특징
- 호스팅 플레이어의 기기는 서버의 역할을 수행하면서도 게임 플레이가 가능
- 일반적으로 소규모 멀티플레이어 게임이나 친구들과의 비공식적인 게임 세션에 사용되며, 호스팅하는 플레이어의 기기가 게임 데이터 처리와 네트워크 통신을 모두 담당
Dedicated Server
게임 서버가 별도의 전용 서버에서 운영됩니다. 이 서버는 게임을 직접 플레이하는 것이 아니라, 오직 게임 데이터의 처리와 플레이어 간의 통신 관리에 전념합니다.
특징
- 높은 수준의 게임 성능과 안정성을 제공하며, 대규모 멀티플레이어 게임이나 공식적인 게임 서비스에 주로 사용됨
- 서버와 클라이언트가 분리되어 있기 때문에, 서버의 성능이 게임 플레이의 질을 결정함
언리얼 엔진에서는 서버를 사용할 시 권위있는 클라이언트-서버 방식(Authoritative Client-Server)을 사용합니다.
Authoritative Client-Server
클라이언트-서버 네트워크 구조에서, 서버가 게임의 주요 결정과 데이터 검증을 책임지는 방식을 말합니다. 이 구조에서 서버는 '권위 있는' 역할을 하며, 게임의 핵심 로직과 중요한 데이터 관리를 담당합니다.
클라이언트는 사용자의 입력과 게임 내의 상호작용을 서버에 전송하고, 서버는 이러한 정보를 검증하고 게임의 상태를 업데이트합니다. 예를 들어, 플레이어가 캐릭터를 이동시키는 경우, 해당 이동 명령은 서버에 전달되고, 서버는 이 명령의 유효성을 확인한 후 게임의 상태를 업데이트합니다.
이 방식의 핵심은 모든 중요한 결정과 데이터의 진실성이 서버에 의해 관리되며, 클라이언트는 주로 표시(display)와 입력(input)을 담당한다는 점입니다. 이는 게임의 일관성과 보안을 유지하는 데 도움이 되며, 부정행위를 방지하는 데에도 효과적입니다. 서버가 모든 중요한 처리를 책임지므로, 클라이언트 간의 데이터 불일치 문제를 최소화할 수 있습니다.
LAN(Local Area Network)을 활용하여 로컬 멀티 플레이 게임 구현하기
LAN(Local Area Network)을 활용하여 로컬 멀티 플레이 게임을 구현해보겠습니다.
- LAN: 동일한 라우터에 연결된 PC들이 로컬 IP 주소(Local IP Address)를 통해 같은 네트워크로 연결되는 것을 의미합니다.
- 로컬 IP 주소(Local IP Address): 여기서 사용되는 라우터에서 컴퓨터에 할당한 IP 주소
- 공용 IP 주소(Public IP Address): 인터넷 서비스 제공자가 라우터에 할당한 IP 주소
- Server ->Public IP -> Local IP 를 거처 PC에서 데이터를 받을 수 있다.
준비물 : 같은 라우터(like Wifi)에 연결된 두 대 이상의 컴퓨터. 하나는 Server을, 다른 컴퓨터들은 Client 역할을 맡는다.
1. Listen Server 설정 (Server에서 실행)
.umap 파일을 Listen 서버로 엽니다.
// listen 서버 열기
void AMyProjectCharacter::OpenLobby()
{
UWorld* World = GetWorld();
if (World)
{
// Lobby.umap을 listen 서버로 열기
World->ServerTravel("/Game/ThirdPerson/Maps/Lobby?listen");
}
}
2. Client 연결 설정 (PC2에서 실행)
Client에서는 Server의 Local IP 주소를 사용하여 게임에 접속합니다.
// local IP 입력해서 서버에 연결하기
void AMyProjectCharacter::CallOpenLevel(const FString& Address)
{
UGameplayStatics::OpenLevel(this, *Address);
}
void AMyProjectCharacter::CallClientTravel(const FString& Address)
{
APlayerController* PlayerController = GetGameInstance()->GetFirstLocalPlayerController();
if (PlayerController)
{
PlayerController->ClientTravel(Address, ETravelType::TRAVEL_Absolute);
}
}
실행 결과
클라이언트 겸 서버 담당자가 서버를 생성하고, 같은 Local IP를 사용하고 있는 클라이언트가 생성한 게임에 접속할 수 있는 것을 확인할 수 있습니다.