아래 기사를 한국어 번역했습니다.
Dev Story: Das Tal’s Backend & Exit Games’ Photon
이 기사는 인디 스튜디오 Fairytale Distillery의 "Das Tal"의 테크니컬 디렉터 및 개발자인 Sebastian Dorda 씨가 작성한 게스트 포스팅 입니다.
"Das Tal"은 플레이어의 인터랙션에 중점을 둔 빠른 페이스의 샌드박스 MMORPG입니다. 승자가 패자의 인벤토리를 모두 빼앗는 "Fool Loot" 과 오픈 PvP로, 플레이어는 같은 편을 만들기 전에 "리스크 vs 보수"에 대해 생각해야 합니다. PvP는 언제든지 가능하지만 협력 플레이를 할 경우 보수를 받게 됩니다. 각 월드에는 고유의 특성과 지리가 있어 그 특성은 자신이 선택할 수 있습니다 !
타임 박스화 되어있는 서버에서 승자와 패자가 결정되고, 규칙적으로 신규 게임을 시작하게 됩니다. 또한 클래스리스(class-less)한 캐릭터 시스템을 사용하고 있기 때문에 플레이 스타일에 맞춰 캐릭터를 만들 수 있습니다. 플레이어는 장비 및 건물을 만들기 위해 필요한 재료들을 구하기 위해 싸우게 됩니다.
PvP대전은 완전히 스킬 베이스이며 쉽게 매칭합니다. 소규모 전투도 대규모 공격도 언제든지 플레이가 가능합니다.
"Das Tal" Alpha tester에서 체험해 보세요.。
이번 토픽은 백엔드입니다. 우선 인프라 구성의 개요를 설명한 후 Photon Server 상의 게임 서버에 대해 상세히 이야기하겠습니다.
어디까지나 하나의 방법일 뿐입니다. 의견이 있으면 언제든지 연락주세요.
게임 설계의 배경
"Das Tal"에는 고립된 작은 게임 월드가 다수 존재합니다. 등록 인원은 1000명~2000명, 하나의 월드에는 100〜200 CCU(동시 접속 플레이어)가 접속합니다. 이 규모로 정한 이유는 월드를 타임 박스화하기 위해, 그리고 친밀한 유저 커뮤니티를 만들고 (하나의 월드 내에서)기술적인 복잡함을 최소화하기 위함이었습니다.
게임 플레이의 코어는 매우 고속&단순한 게임 플레이가 중심입니다.
다시 말하면 여러 카운터 스트라이크 서버와, 접속 인스턴스로 구성된 거대한 월드를 가진 "고전적인" WoW 같은 MMORPG 구조의 중간입니다. 게임 플레이의 예를 다음과 같이 표시합니다:
인프라 구성
게임의 라이브 오퍼레이션 또는 개발에 관련된 서비스의 개요입니다. 일부 서비스는 더미이지만 대부분의 서비스는 이미 가동중입니다.
LIVE 환경
- 계정 관리(REST)
- 게임 월드에서 나가도 저장되는 정보(이름, 실적 등)(REST)
- 게임 월드 리스트(REST)
- 지도 파일의 보관 (HTTP, FTP)
- Patcher/Client 파일(HTTP、FTP)
- 게임 서버(Photon Server)
- 클라이언트(Unity)
- 데이터 수집(Graphite)
- 추적(Honeytracks、Piwik、Google Analytics)
- 감시(icinga2)
- 웹사이트(Tumblr、웹)
- 포럼(웹)
- 백업(Bacula)
- 뉴스 레터(Mailchimp)
개발 환경
이 서비스들의 대부분은 매우 사용하기 쉬워 대부분의 MMO계열 게임에서 사용되고 있습니다.
게임 서버
모든 서비스가 중요하지만, 심장부는 게임 서버입니다. 다양하게 검토해 본 결과 Photon Server을 사용하기로 했습니다. 주된 이유는 코드가 빠르고 잘 만들어져 있기 때문에 서버와 클라이언트 (C#) 간에서 코드를 간단하게 공유할 수 있었기 때문입니다. 운용전에 평가한 게임 미들웨어에 대해 간단히 적어봤습니다.
개요
우리의 게임 서버는 완전한 권한 서버(authorative server)입니다. 게임 루프의 코어 부분(시뮬레이션)은 약 15ticks/s 로 실행됩니다. 멀티 스레드는 굉장히 힘들기 때문에 게임 루프의 코어 부분을 1개의 스레드로 제한하기로 했습니다.
싱글 스레드 게임 루프의 설계와 유지가 훨씬 쉽습니다. 예를 들면 고부하로 인한 알 수 없는 타이밍 버그 (복제 아이템 등)이 적게 발생합니다.
단점은, 게임 루프의 코어 부분이 수평으로 스케일링 되지 않기 때문에 1개의 게임 월드를 스케일 업 할 수 없는 점입니다. 그러나 게임 월드가 작게 설게 되어있기 때문에 문제가 되지 않았습니다.
네트워크 IO, 외부 서비스의 호출, 장시간 실행의 계산(경로 탐색), 월드 데이터의 저장 등, 그 외의 것들은 다른 스레드에서 실행합니다. 실제로 이 모든 것은 Photon의 내부 Fiber구축을 사용하여 Fiber로서 실행됩니다.
클라이언트 인터레스트 관리에 2D grid index를 사용하고 있습니다. 이것은 트래픽과 CPU의 부하를 관리 가능한 레벨로 줄이는데 필요합니다.
게임 플레이는 Artemis의 엔티티 시스템 프레임 워크를 바탕으로 하고 있으며 충돌과 움직임에 Farseer를 사용하고 있습니다.
클라이언트와 서버 간의 작은 메시지에는 Photon 고유의 시리얼라이징 (기본적인 데이터 형식의 키 값 리스트)만을 사용합니다. 보다 크고 복잡한 데이터는 protobuf에 의해 시리얼라이징되어 Photon 프로토콜의 바이트 배열로 엔코딩됩니다.
게임의 상태는 메모리에 저장됩니다. 그러므로 protobuf를 사용하여 완전히 시리얼라이징하고, 파일에 저장하기만 하면 됩니다. 이것은 각 월드의 규모가 작고 시간 제한이 있어 기하급수적으로 성장하지 않기 때문에 실현 가능한 것입니다.
어드바이스:protobuf로 엔코딩된 파일의 옆에, 게임의 완전한 상태를 하나의 큰 YAML 파일로 덤프합니다. 이렇게 하면 텍스트 에디터만으로도 게임의 상태를 간단히 조사할 수 있습니다. 다른 툴은 필요없습니다.
모든 로깅는 Photon Server에 포함된 log4net를 통해 이루어집니다. 우리가 사용한 IoC 컨테이너는 Windsor입니다.
유닛 테스트의 셋업은 Visual Studio Unit Testing Framework를 사용하여 Photon MMO 데모 사례의 셋업을 바탕으로 하였습니다.
피어와 서비스의 상호 작용
피어는 접속한 클라이언트를 나타내고 플레이어와 통신하기 위한 게이트웨이로써 기능합니다. 각 피어에는 수신 메시지와 송신 메시지를 처리하는 파이버가 있고, 모든 피어는 별개로 존재합니다.
서로 다른 피어 간의 통신은 서비스를 통해 이루어집니다. 예를 들어 인증이 완료되면, 피어는 챗 서비스에 등록하여 챗 메시지를 송수신하게 됩니다.
피어에는 프로토콜의 현재 상태를 나타내는 프로토콜 상태 오브젝트가 있습니다. 그렇기 때문에 클라이언트가 게임에 접속해 참가하면 다음의 순서로 실행됩니다(클라이언트 관점).
- 클라이언트 버전과 서버 버전이 일치하는지 확인한다.
- 로그인 후 클라이언트를 인증한다 (계정 서비스를 이용).
- 캐릭터를 선택한다(계정 서비스를 이용).
- 서버에서 스킬 정의와 그 외 글로벌 밸런스 값을 취득한다(밸런스 파일, 스킬 정의 서비스를 이용).
- 외부 서버로부터 정적인 지도 데이터를 다운로드하기 위해 필요한 지도 정보를 취득한다(다운로드 URL, 해쉬) (지도 스트레지 서비스를 이용).
- 정적 지도를 다운로드 한다(클라이언트 상에 아직 존재하지 않고 해쉬가 일치할 경우) (게임 서버는 사용하지 않음).
- 지도를 인스턴스화 한다(게임 서버는 사용하지 않음).
- 게임에 참가한다(게임 서비스를 이용).
게임 서비스
게임 서비스는 게임 루프의 코어 부분의 파이버를 실행합니다. 게임 내 (게임 파이버로 실행됨)와 게임 외부와의 게이트웨이입니다. 피어가 게임에 참가했을 때 gameService.Enter(peer)로 호출합니다.
캐리터의 동작 메시지는 “gameService.OperationBodyMovement(peer, movement)"을 통하게 됩니다. 이 커맨드는 모두 게임 루프 파이버의 큐에 들어가, Tick 간에서 실행됩니다(게임 시뮬레이션의 1 스텝).
게임 서비스의 또 하나의 역할은, 현재 게임 상태의 스냅샷을 작성하여(동기), 상태를 시리얼라이징하여 저장하는 맵 스토리지 서비스에 넘겨주는 것입니다(비동기).
게임 플레이 루프의 코어 부분
이 스니펫은, 게임 서비스내에서 tick 메소드가 어떻게 호출되는지를 나타냅니다.
게임 루프의 중심은 Artemis 엔티티 시스템입니다. 엔티티 시스템과 상호 작용하는 모든 코드는 클라이언트와 서버 간에서 공유됩니다(게임 네트워크의 동기를 처리하는 시스템은 예외입니다).
이것은 클라이언트가 필요에 따라 같은 데이터 구조와 메소드를 사용할 수 있게 하기 위함입니다. 현재 클라이언트는 공유 게임 플레이 코드의 데이터 구조와 일부의 체크 메소드만을 사용하고 있습니다. Artemis에는 다양한 게임 플레이 파트용의 시스템이 포함되어 있습니다(예: HP와 에너지의 재생에 사용되는 ASCharacterRegs, 캐스팅 스킬에 사용되는 ASCharacterCast 등).
ASFarseer는 매우 중요한 시스템입니다. Farseer 라이브러리를 이용해 충돌과 움직임을 계산합니다.
ASSpatialIndex 시스템에는 그리드를 바탕으로 한 다양한 공간 인덱스가 포함되어 있어 효율적인 클라이언트 인터레스트 관리에 필요합니다. 발자국을 처리하기 위한 특별한 그리드 인덱스도 있습니다(발자국 투성이가 되는 상황을 막기 위해 ).
ASNetwork(다이어그램에서는 "game network outgoing" 로 칭합니다)은, 클라이언트와의 모든 게임 상태를 동기화합니다. 이 컴포넌트는 게임 서비스 내의 현재 액티브한 모든 피어 인스턴스에 액세스할 수 있습니다.
다음 순서는 아래 예로 컴포넌트의 상호 작용을 나타냅니다.
예:플레이어가 캐릭터를 움직여, 그 결과가 화면에 표시된다.
- 클라이언트가 동작 메시지를 송신한다.
- 피어가 동작 메시지를 수신한다.
- 피어의 게임 내 상태가 메시지를 처리, 게임 서비스에게 보낸다.
- 현재의 Tick을 끝낸 후, 게임 서비스는 동작 커맨드를 처리하여 영향을 받는 엔티티로 동작을 계획한다.
- 다음 게임 Tick에서 ASFarseer 시스템은 모든 엔티티의 동작을 시뮬레이션한다.
- Tick이 끝날 때 ASNetwork 시스템은 처리된 입력 메시지에 대한 ACK 메시지를 보낸다.
- 또한 ASNetwork 시스템은 관련된 위치의 업데이트를 액티브한 피어에게 송신한다(그리드를 이용).
- 클라이언트가 로컬 예측 위치를 수신 위치로 조정한다.
이번 주는 여기까지입니다. 구현 방법에 대한 질문이나 의견이 있으실 경우 sebi@fairydist.com 로 연락바랍니다.
댓글
댓글 0개
댓글을 남기려면 로그인하세요.