새로운 플랫폼의 출시 첫날 공개되는 게임을 개발하는 것은 큰 도전이지만, 이 프로젝트를 맡은 유니티 내부 소규모 팀은 수십 년 동안 Unity와 게임 개발에 매진해 온 베테랑 유니티 개발자들로 구성되었습니다. 이번 블로그는 게임 제작 과정, 또한 이 과정이 어떻게 유니티의 정식 제작 검증을 향한 노력을 이끌어냈는지, 그리고 다른 유니티 게임 개발자들이 자체 프로젝트에 적용할 수 있는 유용한 내용을 다루는 시리즈의 일부입니다.
Survival Kids 개발 과정에서 쌓은 팀의 경험을 공유하는 비하인드 스토리 시리즈의 첫 번째 게시물을 읽어 보세요.
Nintendo Switch는 Nintendo의 상표입니다.
Survival Kids는 유니티 내부의 소규모 팀이 제작한 게임입니다. 코어 그룹에는 아티스트, 엔지니어, 디자이너 등 다양한 분야의 개발자가 10명 정도 참여했으며, 최대 인원은 유니티의 다른 팀에서 합류한 팀원을 포함해 약 20명이었습니다. 예를 들어 렌더링 엔지니어인 스티븐은 많은 작업을 함께했지만, 항상 프로젝트에 참여한 것은 아니었슶니다.
작은 규모의 팀이었기에 오히려 장점도 있었습니다. 엔지니어는 대부분 20여 년 이상 AAA 게임 분야에서 경력을 쌓으며 수많은 시행착오를 겪고 노하우를 축적한 베테랑들입니다. 또 대부분이 유니티에서 오래 근무한 만큼 Unity의 경험도 풍부했습니다.
일부 팀원은 Professional Services/Accelerate Solutions(현재는 Unity Studio Productions) 같은 유니티 지원 팀에서 고객 프로젝트를 지원한 경험도 있었습니다. 고객에게 프로젝트 최적화 방법을 조언하고 때로는 프로젝트 팀에 합류하여 어려운 기술적 문제를 함께 해결하기도 한 덕분에, 스튜디오들이 흔히 저지르는 실수와 그 해결 방법에 익숙했습니다. Survival Kids를 개발할 때는 모든 위험 요소가 어디에 있을지 알고 시작했기 때문에 처음부터 프로젝트를 올바른 방향으로 설계할 수 있었고, 그 덕분에 시간과 리소스를 크게 단축할 수 있었습니다.
이번 게시물에서는 게임의 네트워크 아키텍처를 자세히 다루어 보고자 합니다. 팀에서는 Unity를 기반으로 멀티플레이어 네트워킹을 구현했으며, Survival Kids는 단일 네트워크 기반에서 다양한 게임플레이 방법을 제공합니다. 그럼 게임이 어떻게 완성되었는지 자세히 살펴보겠습니다. 여러분의 프로젝트에도 도움이 되기를 바랍니다.
Survival Kids의 게임플레이
Survival Kids는 싱글 플레이, 로컬 협동, 친구와의 온라인 플레이 등 여러 가지 방식으로 플레이할 수 있습니다. 또한 Nintendo Switch™ 2에서는 GameShare 기능을 통해 다른 Nintendo Switch 2나 기존 Switch로 영상을 스트리밍하고, TV나 기기에서 다른 사람과의 멀티플레이를 즐길 수도 있습니다.
팀에서는 이처럼 다양한 조합을 모두 지원할 수 있는 구조를 만들고자 했습니다. 예를 들면 한 대의 TV에서 분할 화면으로 2명이 게임을 플레이하고, 다른 TV에서도 2명이 분할 화면으로 플레이하여 두 대의 기기에서 총 4명이 함께 게임을 즐기는 식입니다. 이러한 유연성을 제공하여 다양한 방식으로 게임을 플레이할 수 있도록 지원하는 것이 아키텍처 설계의 중요한 목표였습니다.
이를 위해 Netcode for Entities를 선택했습니다. Survival Kids의 컨셉을 KONAMI에 제안하고 나서, 멀티플레이어 게임의 재미를 보여 주기 위해 바로 프로토타이핑을 시작했습니다. 이전에 Netcode for Entities를 백엔드 네트워크로 활용하고 그 위에 GameObject 레이어를 올려 프리팹과 애니메이션을 활용하는 방법에 대한 POC를 작성한 적이 있었는데, 해당 프로젝트를 출발점으로 삼았습니다. 팀원 모두가 엔티티 작업에 익숙한 것은 아니었기 때문에 GameObject와 MonoBehaviour를 함께 사용하기로 했습니다.
또한 게임플레이 로직은 GameObject와 MonoBehaviour에 두기로 했는데, 그래야 프로토타이핑을 매우 손쉽게 진행할 수 있기 때문입니다. 이런 구조를 통해 간단히 여러 요소를 조합하고 스크립트를 작성하거나, 인터넷에서 스크립트를 다운로드하거나, 에셋 스토어의 패키지를 활용하여 프로토타입을 제작할 수 있었습니다. 빠르고 자유로운 반복 작업(iteration)이 가능하다는 점에서 좋았지만, Netcode for Entities가 뛰어난 성능의 네트워크 레이어를 제공한다는 점도 마음에 들었습니다. 저는 이미 몇몇 고객 프로젝트와 개인 연구 프로젝트에서 Netcode for Entities를 사용해 본 경험이 있었기 때문에, Netcode for Entities가 팀에서 원하는 수준의 게임플레이를 충분히 뒷받침할 수 있다는 걸 알고 있었습니다.
처음 시작했던 약 3년 전에는 Netcode for GameObjects도 있었지만, 특히 클라이언트측 예측과 같은 몇 가지 필요한 기능이 아직 미흡했습니다. 클라이언트측 예측이란 서버와 클라이언트 사이에 지연이 생겼을 때, 서버가 무엇을 할지 클라이언트가 예측하고 즉시 실행하는 방식입니다. 이렇게 하면 지연이 있더라도 플레이어의 컨트롤이 즉각 반응하는 것처럼 느껴집니다. 즉, 서버가 ‘플레이어가 이동했다’라고 알려줄 때까지 기다릴 필요 없이 이미 그 동작을 실행하는 것입니다. Netcode for Entities는 처음부터 이 기능을 지원했습니다.
프로토타이핑 단계에서는 기본적으로 기존에 있던 프로젝트를 가져와 바로 시작했습니다. 처음에는 오브젝트를 집거나 나무를 베는 등의 간단한 작업부터 시작했고, 점차 게임플레이가 어떻게 전개될지를 구체화하기 시작했습니다. 아직 프로토타이핑 단계였기 때문에 코드 품질에는 크게 신경 쓰지 않았습니다. 그보다는 재미 요소를 찾고, ‘모두를 위한 생존’을 포함한 게임의 핵심 요소에 중점을 두려고 했습니다. 서바이벌 게임을 만들고 싶었지만 너무 어렵거나 힘든 게임을 원하지는 않았으며, 이 장르를 정말 재미있고 흥미롭게 만드는 요소를 찾으려 노력했습니다.
팀에서는 자체적으로 이런 질문을 던져 보았습니다. 사람들이 제작과 자원 수집에서 좋아하는 요소는 무엇일까? 사람들이 신경 쓰지 않는 요소는 무엇일까? 이런 고민을 통해 플레이어가 자원을 얻는 방법, 한 곳에서 다른 곳으로 자원을 옮기는 방법, 도구를 제작하는 방법을 정의할 수 있었습니다. 이 모든 것을 GameObject와 MonoBehaviour를 활용한 빠른 프로토타이핑과 반복 작업으로 밝혀냈습니다.
간단한 POC 데모로 시작했기 때문에 인터넷 주소로 바로 연결할 수 있었습니다. 컴퓨터 IP를 사용하여 연결할 수도 있었지만, 클라우드에서 Relay 서버에 게임을 호스팅할 수 있는 Unity의 Relay 서비스를 사용했습니다. Relay를 사용하면 누구나 참여 코드를 사용하여 게임에 참여할 수 있으며, VPN이나 기록해 둔 IP 없이도 집이나 사무실에서 연결할 수 있습니다. 따라서 매주 플레이 테스트를 진행할 수 있었고, 사무실이나 집 네트워크로도 테스트를 진행했습니다. 이를 통해 다양한 연결 속도 환경에서 게임플레이와 네트워크 아키텍처에 대해 스트레스 테스트를 수행할 수 있었습니다. Relay는 결국 정식 제작 단계에서도 계속해서 사용하게 되었습니다.
팀은 공개적으로 출시된 패키지를 최대한 활용하고자 했습니다. 특정 패키지에서 버그가 발견되면 해당 버그를 식별하고 로컬로 패키지를 가져와 수정했습니다. 가끔은 Slack으로 Unity의 Netcode 팀에 메시지를 보내 문제와 수정 사항을 설명했고, 그러면 Netcode 팀에서 이를 기반으로 PR을 수행하거나 최종 버전에 반영하기도 했습니다. 반드시 수정에 직접적으로 참여한 것은 아니었지만, 정식 제작 환경에서 작업하다 보니 Netcode 팀에서 아직 발견하지 못한 문제를 찾기도 했습니다. 물론 Netcode 팀도 더 나은 해결 방안을 이미 보유하고 있거나, 우리 팀에서 잘못 사용하고 있는 부분을 알려 주기도 했습니다.
Survival Kids의 게임플레이
이런 방식으로 Relay를 통한 원격 개발을 진행했기 때문에 오프라인 모드는 출시가 가까워질 때까지 추가하지 않았습니다. 오프라인 모드는 네트워크 소켓을 열지 않고, 대신 인프로세스 드라이버라는 것을 사용합니다. 이는 서버와 클라이언트가 있는 네트워크처럼 작동하지만, 같은 프로세스 안에서 실행되며 서로 직접 통신합니다. 네트워크를 통해 전송하는 대신 클라이언트에 직접 전송하는 방식입니다. 이를 인프로세스 연결이라고 합니다. 실제 바이트가 네트워크를 통해 이동할 때까지 기다릴 필요가 없기 때문에 매우 빠르지만, 플로 자체는 게임플레이와 동일합니다.
이런 방식으로 작업했기 때문에 다른 버전을 따로 코딩할 필요가 없었으며, 동일한 시스템으로 싱글플레이 모드와멀티플레이 모드를 모두 처리할 수 있었습니다. 싱글플레이와 오프라인 모드도 여전히 네트워크 게임에 해당하지만, 실제로 네트워크를 거치지는 않으며 모두 내부적으로 처리됩니다.
기본적으로 어디서나 사용할 수 있는 단일 코드 아키텍처를 갖게 된 것입니다. 하지만 그 대신 호스팅 중이거나 싱글플레이 모드일 때는 서버와 클라이언트를 동시에 시뮬레이션해야 하기 때문에, 두 가지를 동시에 실행한다는 성능 부담이 생깁니다. 전용 서버를 사용할 경우 서버는 서버 팜에서 실행되므로, 클라이언트만 실행하면 됩니다. 클라이언트는 게임이 화면에 원활하게 표시되고 서버가 보내는 정보에 반응하도록 합니다. 하지만 싱글플레이에서는 시뮬레이션을 해야 하므로 게임이 서버와 클라이언트를 동시에 처리해야 하며,전용 서버처럼 따로 실행할 수 없습니다.
결국 이는 가장 큰 성능 문제 중 하나가 되었고, 서버와 클라이언트가 동일한 게임 및 동일한 프레임 안에서 실행되면서도 60fps라는 목표를 양호한 해상도에서 달성하도록 최적화하는 작업이 필요했습니다. 팀에 정말 중요한 목표였습니다.
유니티 블로그 시리즈의 다른 게시물을 읽고 Survival Kids 제작 과정에 대해 자세히 알아보세요.