온체인 게임 개발 및 퍼블리싱 회사인 Argus는 온체인 게임의 기술적 한계를 극복하기 위해 설계된 자체 엔진인 World Engine을 개발하였다. World Engine은 샤딩 레이어 2 SDK로, 루프 기반 런타임과 확장성 향상을 위한 샤딩을 특징으로 하며, 두 가지 모두 게임 애플리케이션에 더 적합하다.
World Engine은 스마트 컨트랙트 관련 로직과 게임 실행 로직을 각각 EVM 베이스 샤드와 게임 샤드로 분리힌다. EVM 베이스 샤드는 Polaris라는 프레임워크를 사용하여 기존 이더리움 개발자 도구와 인프라를 활용하고, 라우터를 통해 게임 샤드와 상호 작용한다.
Argus의 World Engine의 잠재적인 적용 사례는 온체인 비주얼 노벨 게임의 사례를 통해 확인할 수 있다. 인터체인 해커톤에 참여하면서 개발된 이 게임의 목표는 Cardinal, Nakama (오픈소스 게임 서버), 사용자 Client를 연결하여 멀티플레이어 온라인 비주얼 노벨 경험을 제공하는 것이다.
게임은 만들기 어렵다. 그런데, Fully Onchain Game(FOCG)은 더 만들기 까다롭다. 이러한 간극을 그나마 줄이려는 주목할만한 시도들 중에서 오늘은 Argus와 World Engine을 살펴보려고 한다.
Argus의 뿌리는 1세대 FOCG인 Dark Forest에서 출발한다. 1주일만에 10K+ 넘는 플레이어를 넘기면서 당시 Gnosis Chain의 블록스페이스 대부분을 채운 Dark Forest는 FOCG의 가능성을 보여준 것으로 역사적인 가치를 가진다. Scott은 이 Dark Forest를 함께 만들었고, 아마 이 시기의 경험들이 최종적으로 Argus를 창업하는 것까지 이어지지 않았을까 추측해본다.
Source : designboom
Argus하면 World Engine이 가장 먼저 떠오르지만, Argus는 자신들을 인프라 회사로 비쳐지는 것을 경계하고, 게임 개발과 퍼블리싱 회사로 소개한다. 왜 그럴까? 해당 팟캐스트에서 그 답을 찾을 수 있었다. World Engine은 Argus에서 온체인 게임을 만들면서 현재에 존재하는 기술적 선택지에 한정되지 않기 위해서 자체적인 엔진으로써 만든 것이고, 해당 인프라를 다른 게임 스튜디오들도 사용할 수 있게 하기 위해서 공개한 것일 뿐이다. 즉, 무게중심은 ‘재밌는 게임을 만드는 것’에 있다.사실 웹사이트만 살펴봐도, 현재 약 3개의 Wolrd Engine을 이용한 게임들을 준비중인 것으로 보인다.
기존 게임 산업의 초창기를 살펴봐도 유명한 게임 엔진들은 의도적으로 만들어진 것이 아니라, 대부분 게임 스튜디오들이 자신의 게임을 만들기 위해서 제작되어서 공유된 형태인데, 온체인 게임 산업 역시 유용한 게임 엔진이 온체인 게임 인프라 전문 회사가 아닌, 다양한 게임을 만드는 게임 스튜디오들에게서 나올 것이라고 추측해볼 수 있다.
Source: Argus
온체인 게임과 관련된 많은 기술적 한계점과 제약사항들은 근본적으로 우리가 애초에 게임을 위해서 설계되지 않은 블록체인 위에서 게임을 만들려고 하였기 때문에 발생하였다고 볼 수 있다. 또한, 게임을 위한 체인이라고 주장하는 많은 프로젝트들은 사실 NFT를 위한 체인에 가까운 것이 지금의 현실이다. 이러한 배경 속에서 등장한 World Engine은 애초에 게임 개발자와 플레이어들을 염두하고 만든 샤딩 레이어2 SDK이다.
World Engine의 주된 특징은 다음과 같다:
먼저 Loop-driven runtime이다. Uniswap과 같은 대다수의 기존 dApp들은 유저의 인풋에 응답하여 동작하는 Event-driven runtime 방식을 사용한다. 그러나 이 방식은 게임에는 적합하지 않은데, 예를 들어, 마인크래프트와 같은 게임에서는 날씨나 중력과 같은 요소들이 유저의 인풋에 관계 없이 지속적으로 업데이트 되어야 한다. 이처럼, 게임은 하나의 틱 내에서 정해진 루프에 따라서 상태가 업데이트 되는 Loop-dirven runtime을 선호한다. 또한, 매 틱마다 게임의 상태 변화 순서는 어느정도 정해진 고정되어 있는 것이 일반적이다. 하지만, event-driven runtime은 트랜젝션 실행 순서가 여러 요소들에 의해서 항상 변할 수 있기 때문애 Loop-driven runtime이 게임에 더 적합하다.
다음은 샤딩이다. 레이어2도 하나의 연산 플랫폼이기 때문에, 일정 수준 이상의 플레이어들이 몰리면, 확장성을 위해서 특정 조치를 취해야한다. 일반적으로는 또 다른 롤업을 띄우는 방법을 취할 수 있는데, 이 때 롤업 간의 상호호환성과 파편화 문제에 직면하게 된다. 요즘 트렌드인 공유 시퀀싱 레이어를 사용하면 해당 문제를 어느정도 막을 수는 있겠지만, 슬래싱에 의존해야하고, DOS 공격에 취약해지는 등 새로운 문제가 또 발생하게 된다. 이에 따라서, World Engine은 기존 MMO 게임들의 서버 구조에서 착안한 샤딩 방식을 사용한다. 실제로 어떻게 샤딩하는지는 게임사들에 따라서 달라질 수 있는데, 각 지역을 쪼개서 다른 샤드를 배정할 수도 있고, 아니면 메이플스토리의 채널처럼 각 서버 인스턴스를 샤드에 배정할 수 있다. 각 샤드들은 기본적으로 서로 비동기적인 상호호환성을 가지고 있기 때문에 샤드간 atomic bundle과 같은 것은 불가능한데, 사실 디파이가 아니면 굳이 두 체인이 서로 동기적으로 호환될 필요가 적긴 하다.
마지막은 호환성(Composability)이다. Argus는 단순히 게임 간의 호환뿐만 아니라, 게임사 퍼블리셔, 마켓플레이스, 모더 등과 같이 온체인 게임 산업의 이해관계자들간의 호환성인 Intergame Thesis를 꿈꾼다. 이 Intergame Thesis를 이루기 위해선 공통적으로 사용되는 규격, 혹은 시스템이 필요하고, World Engine이 현재 그 역할을 하려고 한다.
Wolrd Engine은 스마트 컨트랙트와 관련된 로직은 EVM 베이스 샤드에서, 게임 실행과 관련된 로직은 게임 샤드에서 분리해서 처리함으로써, 높은 처리량을 가진 게임 서버와 함께 기존 블록체인의 호환성을 모두 제공할 수 있다.
Source: Argus
앞서 World Engine은 샤딩을 사용한다고 하였는데, 스마트 컨트랙트와 관련된 로직은 전부 해당 샤드에서 처리한다. EVM 베이스 샤드는 일반적인 EVM 롤업으로, 이더리움 생태계의 개발자 도구나 인프라를 그래도 레버리지할 수 있고, 라우터를 통해서 후술할 게임 샤드와 상호작용한다. EVM 베이스 샤드는 개발자, 모더, 플레이어들이 마켓플레이스나 DEX와 같은 다양한 게임과 관련된 서비스를 배포할 수 있는 장소이다. Argus는 Polaris라는 것을 통해서 EVM 베이스 샤드를 구현하였다.
3.1.1 Polaris
Polaris는 Berachain에서 만든 프레임워크, 혹은 플러그인으로, 주 목적은 임의의 체인이 이더리움과 동일한 수준의 EVM을 사용할 수 있도록 해준다. 예를 들어, 합의 레이어로 Comet을 사용하고, 실행 레이어로 Cosmos SDK를 사용한 뒤에, 그 위에 Polaris를 런타임 레이어로 사용해서 이더리움과 동일한 수준의 EVM을 사용할 수 있다.
Polaris는 베이스 레이어와 EVM 런타임 레이어를 분리시켜서 개발자들이 EVM을 해치지 않고, 베이스 레이어에 여러가지를 시도해볼 수 있도록 한다. 대표적으로 현재 준비중인 Precompile Development Kit을 통해서 더욱 더 쉽게 커스텀 프리컴파일을 추가할 수 있을 것으로 예상된다. Polaris의 상태유지 프리컴파일(Stateful Precompile)은 코스모스의 어플리케이션 레이어와 합의 레이어에도 접근할 수 있어서, EVM을 통하여 Staking이나 Authz와 같은 코스모스 모듈도 사용할 수 있다.
3.1.2 Back to World Engine
이러한, Polaris의 Customizability를 이용하여서 World Engine의 EVM 베이스 샤드는 다음과 같은 기능을 가진다.
특수한 gRPC 서버가 있어서, 후술할 게임 샤드들이 EVM 베이스 샤드에 트랜젝션을 제출하고, 저장하기 위해 연결할 수 있다. 참고로 gRPC는 우리가 흔히 사용하는 JSON-RPC보다 더 효율적이고, 다양한 프로그래밍 언어나 스트리밍 타입을 지원하는 등 여러가지 장점이 있다.
그리고, World Engine의 EVM 베이스 샤드만의 특수한 프리컴파일를 통해서 스마트 컨트랙트에서 이 gRPC 서버를 구현한 게임 샤드로 메시지를 전달할 수 있다.
해당 EVM 베이스 샤드는 Rollkit을 통해서 데이터 가용성(Data Availability) 레이어인 Celestia와 연결되어 있는데, Rollkit에 대한 정보는 해당 글에서 더 확인할 수 있고, Polaris와 Rollkit을 같이 사용하는 부분에 대해선 해당 영상을 참고하기를 바란다.
게임 실행과 관련된 로직은 전부 게임 샤드에서 처리되는데, 게임 개발자들은 자신의 게임에 맞춰서 기존에 존재하는 게임 샤드를 사용할 수도 있고, 혹은 새롭게 게임 샤드를 만들 수도 있다. 그리고 Cardinal은 Argus에서 만든 최초의 게임 샤드 구현체이다.
Cardinal은 첫 번째 게임 샤드로써 다음과 같은 특징을 가진다.
게임 개발에서 흔히 사용되는 ECS 프레임워크를 기본값으로 지원하여 게임 개발자들이 더 쉽게 적응할 수 있다.
틱 기반의 Loop driven runtime을 지원하며, 최대 초당 20틱까지 지원한다.
게임 로직을 Golang을 통해서 작성할 수 있다.
Unity나 Unreal Engine과 같은 게임 엔진과 호환된다.
별도의 인덱서가 필요 없다.
Argus의 깃허브에서 Caridnal을 이용한 게임 예시를 살펴볼 수 있다.
해당 내용은 인터체인 해커톤에 참여하면서 자의적으로 World Engine을 이해하고, 사용하여서 나온 결과물이기 때문에, 잘못된 부분이 있을 수 있으며 World Engine을 사용하는 최적의 방식이 아닐 가능성이 높다. 단순히 ‘아 이렇게도 World Engine을 쓸 수 있구나’ 정도로만 보기를 추천한다. 전체 코드는 다음 깃허브들에서 확인할 수 있다.
우리가 만들려고 한 게임은 멀티플레이어 온체인 미연시로, GPT-4 API를 이용해서 유저들이 인게임 NPC와 실제로 대화하는 것처럼 느끼게 하고, 매치에 참여한 여러 플레이어들 중에서 NPC로부터 가장 먼저 호감도 100을 달성한 플레이어가 이기는 방식으로 진행된다.
우리의 게임은 기존 EVM 베이스 샤드에 스마트 컨트랙트 로직을 추가할 필요가 없었기에 따로 EVM 베이스 샤드를 수정하지는 않았고, 우리가 크게 신경 써야하는 부분은 크게 다음과 같은 3가지 요소였다:
Cardinal: 우리는 기존 Cardinal을 그대로 쓰기 위해서 Argus 깃허브의 ‘stater-game-template’을 포크한 뒤에 게임 로직만 우리 게임에 맞게 수정하였다.
Nakama: Nakama는 Heroic Labs에서 제작한 오픈 소스 게임 서버로써 멀티플레이어 게임에 필요한 여러 요소들을 제공한다. 해당 예시에서는 Nakama를 통해서 Cardinal과 Client를 연결한다.
Client: 유저들이 직접 게임을 보고 즐길 수 있는 인터페이스로써 우리는 Next.js를 사용하였다.
우리가 이해한 Cardinal, Nakama, 그리고 Client 간의 관계는 다음과 같다.
유저의 인풋이 발생한다.
RPC 콜을 통해서 Client가 Nakama로 해당 인풋 전달한다.
Nakama는 RPC 콜에 해당하는 트랜젝션을 Cardinal로 전달한다.
Cardinal은 트랜젝션을 받아서 다음 게임 틱에서 실행한 후, 게임 상태를 업데이트 하고, 트렌젝션 영수증(tx receipt) 생성한다.
Cardinal에서 Nakama로 트랜젝션 영수증이 전달된다.
Client는 주기적으로 Cardinal로부터 트랜젝션 영수증을 받고, Client 상태를 업데이트한다.
4.3.1 ECS 모델
Cardinal을 살펴보기 전에 먼저 ECS 구조에 대해서 알아보자. 우리가 일반적으로 프로그래밍을 할 때 사용하는 상속 기반의 구조는 대규모 멀티플레이어 게임에서 효율적이지 않을 수 있다. 아래 예시를 보면, 현재 우리는 ‘Thing’이라는 클래스를 상속받는 ‘Mammal’과 ‘Bird’라는 클래스가 있는 상황인데, 만약 이 때 오리너구리를 추가해야한다면, 위 두 가지 클래스 중 어느것으로부터 상속받을 수 없고, 새로운 클래스를 만들어야 한다. 실제 게임에서는 이와 같이, 새로운 게임 오브젝트를 추가해야할 일이 많고, 이럴 때 상속 기반의 구조는 적절하지 않을 수 있다.
상속 기반의 구조와 달리, ECS는 다음과 같이 각 부분을 분리해서 모듈러리티를 강조한다.
Component: Component는 ECS 모델의 핵심으로 데이터만 보관한다. 예를 들어, ‘Health’ Component는 체력에 대한 수치만, ‘Location’ Component는 현재 위치에 대한 좌표 값만 포함한다.
Entity: Entity는 고유한 식별자로 분류되는 여러 Component들을 담는 컨테이너 역할이다. 예를 들어, 특정 Entity는 ‘Health’, ‘Location’, ‘Player’ 등과 같은 Component들을 담고, 이는 유저를 표현하는데 사용될 것이다.
System: 특정 Component에 접근하거나, 변경하는 로직을 말한다. 예를 들어, ‘Health’ Component에 영향을 주는 ‘attack_player’, ‘Location’ Component에 영향을 주는 ‘move_player’ 등이 전부 System이 될 수 있다.
4.3.2 트랜젝션 영수증(tx_receipt)
Cardinal은 트랜젝션을 받자마자 처리하는 것이 아니기 때문에, 일단 트랜젝션 해시 값을 Nakama로 전달하고, 실제로 처리된 이후에 해시, 결과값, 그리고 오류를 담은 영수증을 생성해서 전달한다.
4.3.3 코드 예시
우리 게임은 새롭게 플레이어와 호감도를 Component로 새롭게 정의하고, 그에 대한 로직을 ECS 모델로 구현하였는데, 그 중에서 예시로써 플레이어에 대한 부분을 어떻게 추가하였는지 살펴보자.
먼저 ‘Player’ Component를 정의하고, 해당 Component는 플레이어의 이름에 대한 데이터만 보관한다.
그 다음에는 유저가 게임에 참가하면 새로운 Player의 생성을 트리거링하는 ‘CreatePlayer’ 트랜젝션을 먼저 정의하였다.
위 트랜젝션을 받아서 Cardinal은 실제로 플레이어를 생성하는 ‘PlayerSpawnerSystem’을 실행하게 된다. 아래 코드에서 알 수 있는 것처럼, ‘PlayerSpawnerSystem’은 ‘CreatePlayer’ 트랜젝션을 받게되면, 플레이어와 호감도 Component를 가진 새로운 Entity를 생성한다.
마지막으로 ‘main.go’ 파일에서 새롭게 만든 Component, Transaction, System들을 ‘world’ 오브젝트에 등록해줘야 한다.
Nakama는 1) Cardinal과 Client 사이를 연결해주고, 2) 멀티플레이어 게임 서버의 여러가지 기능을 제공한다. 예를 들어, 유저 인증(authentication), 세션 관리, 매치 설정 등을 Nakama를 통해 쉽게 구현할 수 있다. Javascript, Unity, Godot 등 사실상 모든 게임 관련 언어들에 대한 SDK 및 가이드 예제가 있기 때문에, Nakama를 처음 접한 우리도 사용할 수 있었다. 우리 팀의 경우, Next.js로 클라이언트를 구현하였기 때문에, ‘xoxo-phaserjs’라는 Nakama + Javascript 예제를 많이 참고하였다.
4.4.1 Nakama w/ Cardinal
Argus에서 제공하는 ‘starter-game-template’은 Nakama가 Cardinal과 더 잘 작동하도록 Nakama를 약간 수정하였기 때문에, Cardinal을 이용해서 게임을 만들고자하는 경우, 해당 템플릿을 사용하면 더 편하다. Starter-game-template을 사용하면, 앞서 ‘world’ 오브젝트에 등록한 트랜젝션들에 대한 Nakama RPC 엔드포인트가 자동으로 생성된다.
새로운 플레이어가 Cardinal 기반 게임을 시작할 때, 가장 먼저 'Persona Tag'를 설정해야 한다. 이 Persona Tag는 Nakama에 저장되어 해당 유저의 서명 키를 생성하고, 앞으로 이 유저가 제출하는 트랜잭션을 자동으로 서명하여 Cardinal로 전송한다. 그래서 기본 값으로 이미 ‘claim-persona’와 ‘show-persona’ RPC 엔드포인트가 존재한다.
4.4.2 코드 예시
우리는 ‘starter-game-template’과 앞서 언급한 ‘xoxo-phaserjs’를 적절히 조합하는 방식으로 Nakama 부분을 구현하였는데, Nakama와 Cardinal이 연결된 부분은 기존의 ‘starter-game-template’의 구현을 그대로 사용하였고, Nakama와 Client를 연결하는 부분은 아까 언급한 ‘xoxo-phaserjs’ 프로젝트를 기본값으로 하여서 몇가지 부분들을 수정하였다.
Nakama와 Cardinal을 연결하기 위해선 가장 먼저 Persona Tag를 새로운 유저가 설정할 수 있도록 해야하기 때문에, ‘starter-game-template’에 존재한 ‘claimPersonaTag’ 함수를 가져왔다.
또한, 우리는 현재 매치를 찾고 있는 플레이어가 한명도 없을 시에 새로운 매치를 만들고, 현재 이미 게임을 찾고 있는 플레이어가 한명이라도 있을 경우, 바로 게임이 사작하도록 로직을 추가하였다. 해당 부분은 게임의 성격에 따라서 최소나 최대 참가 플레이어 수도 설정할 수 있다.
마지막으로, 앞서 만들었던 새로운 플레이어 생성을 트리거링하는 ‘CreatePlayer’ 트랜젝션에 해당하는 RPC 콜을 Client에서 불러올 수 있도록 ‘createPlayer’ 함수를 추가해주었다.
4.5.1 코드 예시
우리의 목표는 해당 페이지에서 유저가 자신의 이름과 함께 엔터 버튼을 눌렀을 때, 다음과 같은 일이 발생하도록 하는 것이다.
유저 인증을 진행한다.
유저가 입력한 이름을 Persona Tag로 설정한다.
‘createPlayer’를 통해서 새로운 entity를 생성한다.
매치를 찾는다.
이를 코드로 구현한 부분은 다음과 같다.
World Engine의 핵심 의의는 기존 게임 서버와 최대한 비슷한 환경과 처리량을 가지면서, 블록체인의 최대 장점인 Composability를 포기하지 않는다는 것이다. Argus의 파운더인 Scott이 얘기했던 ‘Why not build Onchain?’이라는 질문이 가까운 미래에 게임 개발자들에게 나오기를 바라면서, Argus에서 공개할 게임들을 함께 기다려보자.
Building Complex Real-time Onchain Game With World Engine - Scott Sunarto (Argus)
Scott Sunarto - Crypto Gaming, Argus Labs, and the Infrastructure vs. Application Debate
The Intergame Thesis: Endgame for Onchain Games - Scott Sunarto
이 글의 비주얼을 제공해주신 Kate에게 감사의 말씀을 전합니다.