※2017년 1월 2일에 공개된 아래 블로그 기사를 번역하였습니다.
Dev Story ‘Tanks Multiplayer’: Switching from UNET to PUN
이 초빙 포스트는 리바운드 게임즈의 설립자 및 개발자인 Florian Bukmaier 씨가 작성한 것 입니다.
리바운드 게임즈는 형제가 2013년도에 설립한 인디 스튜디오입니다. 리바운드 게임즈는 에디터 익스텐션과 프로젝트 완성을 위한 스크립트 툴등 다양한 새로운 유니티 에셋을 유니티 에셋 스토어에 지속적으로 릴리즈하고 있습니다.
에셋 스토어가 재정적인 백업체제만이기는 하지만 게임 개발 자금을 조달하고 추가 비용을 충당 할 수 있는 완벽한 보완책입니다.
플로리안은 UNET (Unity Networking) 과 PUN (Photon Unity Networking) 간의 차이가 있는 최신 멀티 플레이어 프로젝트인 ‘Tanks Multiplayer’를 제작했으며 왜 Photon으로 전환했는지에 대해서 설명합니다.
유니티 에셋 스토어에서 ‘Tanks Multiplayer’ 받기
프로젝트 설명
‘Tanks Multiplayer’ 는 개발자가 자신의 멀티 플레이어 게임 작성을 위한 견고한 기반을 다지거나 학습 프로젝트를 위한 실시간 액션 슈팅 템플릿입니다. 플레이어는 탱크를 조종하고 다른 탱크를 향해 포탄을 발사하고 나를 공격하는 포탄을 피해야 합니다. 각 라운드는 3명으로 구성된 팀의 1 ~ 12명이 참가하여 팀이 승리하도록 상대편을 없애는 게임입니다.
게임중에는 방어막, 데미지 증가 또는 탄약과 같은 파워업 아이템을 수집합니다. 하나의 팀이 필요한 킬 스코어를 달성하면 게임은 종료됩니다. 또한 이 템플릿은 Unity Ads, Everyplay 및 Unity IAP와 같은 추가적인 유니티 서비스를 사용합니다만 이 서비스들에 대해서는 이 게시물에서는 다루지 않습니다.
왜 우리는 이 게임을 만들었을까요? 간단합니다 - 만드는 것이 재미있다고 생각했기 때문입니다. 하지만 재미만은 아닙니다 : Unity가 UNET (베타) 출시후 에셋 스토어에 기본적인 탱크 멀티 플레이 프로젝트를 발표하긴 했지만 고급 네트워킹 주제에 대해서 상세히 다루어야 할 필요가 있었습니다. 특히 네트워크 문서가 부족하였기 때문에, 기본적인 UNET을 코딩할 때 시행 착오를 많이 겪게됩니다.
UNET이 출시된지 몇 달 후, 유니티 포럼에 다음과 같은 질문이 올라왔습니다:
- 어떻게 여러개의 플레이어 프리팹을 추가할 수 있나요?
- 네트워크화 된 객체 폴링을 어떻게 구현하나요?
- 왜 저의 SyncVars가 동작하지 않나요?
- 팀은 어떻게 추가하나요?
- 호스트 연결해제에 대하여 어떻게 처리하나요?
- 권한 방식으로 어디에서 RPC 들을 호출하나요?
우리는 사용자가 잘 파악할 수 있도록, 문서화가 잘된 코드 및 포괄적인 문서로 이러한 의문점들에 대해서 해결하고 싶었습니다.
UNET 파해치기
모든 개발자와 마찬가지로 우리는 하루만에 복잡한 새로운 API, 특히 로우레벨 (LLAPI) 및 하이레벨 API (HLAPI)를 보자마자 마법처럼 이해할 수 있는 능력은 없었습니다. 어떻게 사용하는지에 대해서 학습을 해야 했습니다.
UNET의 오픈소스 접근방식은 이 절차에 대해 상당한 도움이 되었습니다. 우리는 커스텀 콜백으로 기본 동작을 크게 확장했으며 NetworkManager 클래스의 메소드들과 같이 많은 가상 메소드를 오버로드 했으므로 내부 동작을 살펴 보는 것이 반드시 필요했습니다. 그러나 먼저 NetworkTransform 구성 요소 덕분에 위치 및 회전과 같은 변환 값을 동기화하여 간단한 네트워크 이동 테스트는 아주 쉬웠습니다. 플레이어 체력 및 남은 총알 수와 같이 네트워크에서 플레이어 또는 게임 값을 동기화하는 작업은 SyncVars를 사용하는 것 만큼 간단합니다. 단, 마스터 클라이언트만 변경할 수 있으므로 기본적으로 권한 설정이 필요합니다.
기본사항을 이해한 후 UNET이 어떻게 원격 프로시져 호출과 컨텍스트에서 Commands 와 ClientRpcs를 처리하는지에 대하여 이해하려고 하였습니다. 왜 두 가지 액션이 있을까? 라고 의문이 들 것 입니다. 커맨드들은 서버상에서 수행되기 위한 명령어이며 ClientRpcs는 서버에서 클라이언트로 전달되는 액션입니다. 이것밖에는 모릅니다 - 그럼에도 불구하고 우리는 서버에 대한 권한있는 방법 (예 : 손상 계산과 같은)을 중요한 방식으로 실행하고 SyncVars를 통해 결과를 클라이언트에 배포 할 뿐만 아니라 인증 개념을 구현하기 위해 이 접근법을 즉시 사용했습니다. 플레이어는 성공적인 사용자 시작 작업을 수행 한 후 마스터로 요청을 보냅니다. 예를 들어 플레이어가 죽고 몇 초 동안 동영상 광고를 시청해야 할 때 이 작업이 수행됩니다. 이 경우 서버는 비디오 광고의 정확한 길이를 알지 못하므로 이 플레이어에 대한 리스폰 호출을 다른 클라이언트에게 보내지 않습니다. 대신 서버는 동영상 광고를 성공적으로 시청 할 때까지 기다린 후 플레이어가 리스폰 요청을 보내고 이행 절차를 실행합니다.
PUN 살펴보기
지금까지 모든 것은 'Tanks Multiplayer’ 의 UNET 버전에서는 잘 작동했습니다. 우리의 네트워크 개념을 통해 여러 가지 네트워크 모드 (온라인/LAN/오프라인)는 문제없이 데스크탑 및 모바일 장치에서 실행되었습니다. 지금 왜 핵심 방향이 바뀌었을까요? 간단히 말해서, 이 에셋을 개발하는 동안 UNET은 아직은 베타 버전이라는 느낌을 받았습니다. 분명히 그것은 어떤 상황에서는 오류가 나타나지만 다른 상황에서는 나타나지 않는 심각한 문제가 있으며 LLAPI에는 "아주 깊은 곳 어딘가에 감추어져 있는"무언가를 해결해야 할 필요가 있습니다. 이 사항은 이후의 패치 릴리스에서 수정 될 수도 있습니다. 릴레이 서버에서의 호스트 마이그레이션, NAT 펀치 스루 (punch-through) 및 로드 밸런싱 (로드 밸런싱이 필요한 경우) 등의 자체 서버를 쉽게 호스트 할 수 있는 능력과 같은 가까운 미래에 중요한 기능이 누락되어 있으며, 다른 개발자가 문제점을 지적한 청구서 진행 과정의 문제 및 Unity 클라우드가 주말 내내 장애가 발생한 경우가 있습니다. 게임에 모든 노력과 동기 부여를 한 결과가, 이런 식으로 실패하지 않기를 바랍니다. UNET은 네트워킹 주제를 즉시 다루거나 제한된 자원으로 프로토 타입을 제작하는 데 적합합니다. 이 프로젝트는 원하는 목표를 달성하였습니다. 이 시점을 제품출시로 사용하려는 것은 아닙니다. PUN을 통해 신뢰할 수 있고 전투 테스트를 거친 백엔드를 포함한 기능들을 사용할 수 있습니다. 또한, 우리의 에셋을 다양한 채널을 통해 지원한다는 것에 개발자들은 관심을 가졌습니다. 또한 Photon이 Unity (그리고 PUN의 무료 버전을 제공)를 위한 가장 보편적인 네트워킹 솔루션이기 때문에, 개발자를 지원하는 것이 옳다고 생각했습니다.
전환하기
이 프로젝트의 개발 단계 (개념에서 Unity 에셋 스토어에 대한 출시까지)는 3개월이 걸렸습니다. UNET에서 PUN으로 전환하는 데 약 2주가 걸렸으며 네트워킹 프로토 타입은 단 1주 만에 진행되었습니다. 전환이 쉬운 이유에는 몇 가지가 있습니다: 우선, UNET 및 PUN의 구성 요소 구조는 UNET 버전과 같이 씬에서 NetworkIdentities / PhotonViews를 많이 필요로하지 않지만 매우 유사합니다. 두 번째로, 에셋이 모듈화 되어있고 크로스 컴포넌트 인터레이스를 염두에두고 있지 않기 때문에 네트워킹 구현에 관계없이 거의 모든 방법을 사용할 수 있습니다. 이것은 처음부터 코드의 첫 번째 줄을 작성하기 전에 매우 중요한 디자인 단계입니다. 기본적으로 구조는 동일하지만 네트워크 API를 변경해야합니다. 몇 가지 변환 예를 살펴 보겠습니다.
・오너쉽체크
UNET
if (!isLocalPlayer)
{
//keep turret rotation updated for all clients
OnTurretRotation(turretRotation);
return;
}
PUN
if (!photonView.isMine)
{
//keep turret rotation updated for all clients
OnTurretRotation(turretRotation);
return;
}
・RPCS 전송
UNET
//on client
CmdShoot(pos, turretRotation);
//on server
[Command]
void CmdShoot(short[] position, short angle)
{
//instantiate shot
}
PUN
//on client
this.photonView.RPC("CmdShoot", PhotonTargets.AllViaServer, pos, turretRotation);
//on server
[PunRPC]
void CmdShoot(short[] position, short angle)
{
//instantiate shot
}
・변수 동기화
UNET
[SyncVar]
public int health = 10;
//on server
[Server]
public void TakeDamage(Bullet bullet)
{
//substract health by damage
health -= bullet.damage;
}
PUN
//on server
public static void SetHealth(this PhotonPlayer player, int value)
{
player.SetCustomProperties(new Hashtable() { { "health", (byte)value } });
}
마지막 섹션에서 PUN의 변수 동기화에 관해 UNET 버전과 완전히 다르다는 것을 알았을 것입니다. 이는 실제로 OnPhotonSerializeView가 더 적합 할 수 있는 변수 자체를 동기화하지는 않았지만 SyncVars를 플레이어의 CustomProperties로 대체했기 때문입니다. 특히 초당 여러 번 동기화 할 필요는 없지만 이벤트와 같은 방식으로 더 많이 동기화 할 필요가 있는 체력 또는 선택된 블렛과 같은 플레이어 변수의 경우 사용자 지정 속성은 지속적인 업데이트 방식보다 더 많은 대역폭을 절약합니다. 마지막으로 UNET과 PUN을 동일한 에셋 스토어 패키지에서 지원하기 위해 우리는 각 버전에 대해 별도의 Unity 프로젝트를 적극적으로 관리하고 있으며 '주요'프로젝트에서 별개의 스크립트를 단일 패키지로 끌어들입니다. 사용자가 'Tanks Multiplayer'를 다운로드하고 가져 오는 경우 원하는 유니티 패키지를 가져 와서 사용할 네트워킹 솔루션을 선택할 수 있습니다. 이것은 또한 Photon Thunder를 나중에 지원할 가능성을 열어 줍니다 - 그러나 현재 이게 전부입니다!
의견 및 질문이 있으면 [at] rebound-games [dot] com 으로 연락 주시기 바랍니다.
유니티 에셋 스토어에서 ‘Tanks Multiplayer’ 받기
작성자 Robert
댓글
댓글 0개
댓글을 남기려면 로그인하세요.