2014년 11월 25일
모바일 게임을 웹 브라우저 게임으로 포팅하기 - HTML5 + WebGL 실전 사례
모바일 게임이 게임 시장의 주도권을 갖고 있는 것은 사실이지만, 게임 사용 환경으로서 웹 브라우저 역시 아래와 같은 몇 가지 이유로 충분히 매력적인 환경이다.
- 아직도 많은 국가에서는 모바일 기기를 이용하는 게이머보다 PC를 통해 게임을 즐기는 인구수가 더 많다.
- 모바일 대비 넓은 화면과 빠른 성능이 제공되므로 좀 더 좋은 품질의 게임 플레이가 가능하다.
- 페이스북 웹 게임 형식으로 제공한다면 글로벌하게 매우 많은 유저에게 직접 서비스 가능하며, 소셜 기능을 통한 사용자 확산의 잇점을 누릴 수 있다.
- 유저들이 모바일 기기와 데스크탑에서 동시에 게임을 플레이 할 수 있으므로 어떤 상황에서도 끊임없이 게임에 연결할 수 있다.
- 게임의 코드와 리소스를 사용자에게 매우 빠르게 업데이트하고 테스트 할 수 있다.
따라서, 기존에 모바일로 제공되던 게임을 웹 브라우저 환경에서도 서비스 할 수 있다면 게임 타이틀이 훨씬 많은 유저에게 더욱 쉽게 노출될 수 있으므로, 추가 사용자 확보라는 비즈니스 측면은 물론 모든 디바이스에서 연결가능한 잇점으로 인해 게임 플레이 몰입성에도 큰 도움이 된다.
게임이 유니티나 언리얼엔진과 같이 웹 브라우저 환경을 지원하는 엔진을 이용하여 개발되었다면 웹 브라우저용으로 포팅하는 것이 어려운 일은 아니겠으나, 게임이 C++ 코드로 직접 구현되어 있다면 웹 브라우저 게임으로 포팅 또는 개발하는 것은 과거에는 사실상 게임을 새로 개발하는 것과 다름이 없었다. 그러나 이제는 이미 업계 표준이 되어있는 HTML5, JavaScript(Emscripten), WebGL 기술들을 활용하면 그다지 어렵지 않게 웹 브라우저 게임으로 포팅할 수 있다.
본 글에서는 '드래곤프렌즈' (이하 드프) 게임의 웹 포팅 사례를 중심으로, C++로 개발된 모바일 게임을 웹 브라우저 게임으로 포팅하는 방법과 웹 환경에서 발생하는 다양한 문제점들에 대해 다루고자 한다.
이미 많은 사람들에게 알려져있다시피, C++ 코드를 JavaScript 코드로 컴파일 할 수 있는 매우 좋은 툴인 Emscipten 이 존재한다.
모질라 재단에 의해서 관리되며, 오픈소스이고, 충분히 안정화 되었고, 성능은 빠르고, 무료이다. 현재도 활발히 개발되고 있기 때문에 빠른 속도로 안정화 되고 있으며 신기능들이 계속 도입되고 있다.
Emscripten을 이용하면 C++을 JS로 변환하여 웹 페이지에서 작동되게 하는 목표 자체는 매우 쉽게 달성할 수 있다. 게다가 VisualStudio상에서 C++ 솔루션을 JS로 컴파일 할 수 있는 플러그인도 제공된다.
그러나 브라우저 상에서 작동하는 JS 코드이기 때문에 상용게임을 컴파일 했을 때 다양한 문제점을 보게 된다.
- JS 코드 크기문제
웬만한 상용 게임 프로젝트라면 디버그 가능한 JS 코드로 컴파일 했을때 JS 파일의 크기가 쉽게 100MB에 다다른다.
이정도 크기의 JS 코드는 대부분의 웹 브라우저에서 실행이 불가능하다. 따라서, 코드 크기를 최소로 줄일 수 있는 옵션을 모두 사용해야만 구동이 가능하나, 대신에 모든 소스코드 심볼의 이름이 축약화되므로 웹브라우저 내에서 JS 디버깅은 매우 어려워진다.
최소한 크래쉬 발생시 JS 콜스택을 통해 C++ 코드의 콜스택을 유추할 수 있어야 하는데, 다행히도 Emscripten으로 빌드시 자동 생성되는 .symbols 파일을 이용하면 JS 콜스택에 해당하는 C++ 코드 위치를 알 수 있다. JS 콜스택 텍스트를 C++ 콜스택으로 변환해주는 스크립트를 한 번 작성해두면 이러한 경우에 두고두고 도움이 될 것이다. - JS 코드의 직접 코딩
JS의 window, document 등 최상위 개체에 접근하거나 Http request를 전송하거나 웹 페이지의 HTML element를 게임코드에서 조작하는 등, C++ 코드에서는 구현에 어렵고 JS 코드로 직접 작성하는 것이 유리할 때가 많다.
이런 경우에는 Emscripten 라이브러리의 EM_ASM 매크로를 사용하면 C++ 코드 안에서 JS 코드가 함께 작동하게 할 수 있다. C++ 코드의 값을 EM_ASM에 파라메터로 전달하거나 반환 받을 수 있고, Module.Heap 접근을 통해 EM_ASM의 JS 코드 내에서 C++ heap을 직접 조작하는 것도 쉽다. - 비동기 처리 - JS Callback 문제
C 코드는 대부분의 경우 작업 호출의 결과가 반환될때까지 코드의 실행이 대기하는 동기적 방식으로 구현되나, JS는 작업의 결과를 비동기적인 callback에서 처리하는 것이 일반적이다. 작업의 결과를 처리하는 접근법이 전혀 다르기 때문에 일부 기능들은 일반적인 C 라이브러리의 방식으로 웹 브라우저 상에서 작동하게 하는 것이 쉽지 않다.
예를 들어, 웹서버에서 파일을 다운로드하여 이를 메모리에 로드하는 과정이 JS 상에서는 callback으로 처리되어야 하기 때문에, Emscripten에서는 앱에 필요한 리소스 파일 전체를 웹브라우저의 메모리에 미리 적재한 후에 C의 동기적 파일 시스템을 에뮬레이션 한다. 이 때문에 리소스 파일 로드시 메모리 문제가 심각한데, 이에 대해서는 이 글의 아래쪽에서 자세히 언급될 것이다.
또 다른 예로, Http request의 경우에는 Emscripten에서 제공되는 C의 동기적 호출 에뮬레이션이 없기 때문에 JS의 XMLHttpRequest를 직접 사용하고, C 코드에서는 비동기 방식으로 구현하여 JS의 callback에서 완료된 작업을 C 코드에 전달해야 한다.
최근에는 Emscripten 차원에서 이 문제를 해결하기 위해 Emterpreter 라는 간접 실행환경 기능이 지원된다. - 메모리 정렬 (Alignment)
Emscripten으로 컴파일 된 코드는 heap 메모리 접근시 데이터크기에 맞추어 정렬되어 있어야 한다. 즉, int/float와 같은 4 bytes 크기의 값에 접근하기 위해서는 해당 데이터의 메모리 주소가 4의 배수에 정렬되어 있어야 한다.
모바일 기기용 ARM CPU에서도 비슷한 특성이 있으므로, 이미 여기에 맞춰진 코드라면 Emscripten에서의 정렬 문제도 쉽게 해결될 것이다. - 외부 라이브러리 빌드
Emscripten은 C/C++ 소스코드만을 JS로 컴파일 할 수 있다. 따라서, .lib 등의 바이너리 형태의 라이브러리 파일들은 사용할 수 없고, 모두 소스코드로부터 빌드되어야 한다.
일반적으로 많이 사용되는 오픈소스 라이브러리들은 이미 Emscripten 라이브러리 내에 컴파일 가능한 버전의 C 소스코드 또는 JS 버전으로 포함되어 있으나, 그렇지 않은 경우 직접 소스코드로부터 빌드하거나 JS로 직접 구현해야 한다. JS로 구현해야만 하는 경우, 이미 다양한 기능들이 JS 공개 라이브러리로 존재하므로 그들을 검색해 보는것이 빠를수도 있다. - 쓰레드
웹의 JS 환경에서는 쓰레드와 유사하게 작동하는 Web Worker 라는 기능이 제공되나, 메인 쓰레드와 메모리 공유가 되지 않기 때문에(PS3의 SPU와 유사한 구현 문제) 일반적인 쓰레드 코드를 그대로 Web Worker로 변환할 수가 없다. 압축 해제와 같은 매우 독립적인 단위 기능에 대해서는 해당 데이터만 Web Worker에 전달하여 쓰레드화 할 수 있을 것이나, 게임 리소스 로드와 같이 메인 쓰레드의 메모리에 대용량 접근해야 하는 작업이라면 Web Worker의 사용이 어렵다.
그렇다고 모든 작업을 메인 쓰레드에서 동기식으로 처리한다면 JS의 각 프레임 호출이 너무 길어져서 웹브라우저가 페이지다운으로 간주하여 강제 종료시킬 가능성이 높다.
드래곤프렌즈는 웹 환경인 경우 각 쓰레드 구현이 시간분할 모드로 작동되도록 하여, 각 논리 쓰레드 개체가 매 프레임당 일정 시간 내의 연산만을 수행하고 다음 프레임에서 작업을 이어하는 방법으로 이 문제를 해결하였다.
파일 시스템
대부분의 게임은 저장소의 파일에서 텍스처 등의 리소스를 로드해야 하는데, 웹 브라우저 환경이기 때문에 서버로부터 리소스를 다운받은 후에 로드해야 한다.
Emscripten은 C 소스코드의 파일 억세스 코드를 그대로 사용할 수 있도록 하기 위해 앱 시작시 C의 표준 라이브러리의 동기적 파일 억세스를 시뮬레이션 하는 기능이 지원된다. 세부적으로는 여러 종류의 서버 또는 웹 브라우저의 소스로부터의 파일 접근을 지원하나, 대체로 아래와 같은 방식으로 작동한다.
- 개발자가 앱의 리소스들을 Emscrpten의 file packer를 사용하여 Emscripten으로 빌드된 JS 코드가 사용할 수 있는 리소스 파일로 묶음
- 사용자가 웹 페이지를 로드하면, 웹 페이지에서 JS 코드 다운로드 직후 실제 앱 코드 실행 이전에 서버상의 파일 또는 웹 브라우저가 보관한 로컬 리소스 파일을 다운로드
- 다운로드된 리소스 파일 내의 단위 파일들이 웹 브라우저의 메모리로 모두 로드됨
- C 소스코드의 fopen, fread 등의 파일 접근 API는 모두 JS 코드에 의해 웹 브라우저 메모리상의 파일들에 접근함
Emscripten이 기존 C 코드에서 웹 리소스를 로드할 수 있도록 훌륭히 호환성을 구현하나, 실제 게임에 적용하게 되면 역시나 다양한 문제가 발생한다.
- 과도한 메모리 사용량
모든 파일을 우선 메모리에 로드해야 하는데, 32비트 웹 브라우저의 가용 메모리는 수 백 MB에 불과하다. 앱의 구동을 위해 동적으로 할당될 메모리를 제외하면 파일이 로드될 메모리는 200MB 미만으로 봐야한다.
때문에, 그래픽 리소스 등을 다량으로 포함하는 게임이라면 PC에서조차도 메모리 때문에 리소스를 로드할 수 없는 상황이 발생한다.
이 문제를 해결하기 위해서는 리소스 데이터를 압축된 형태로 메모리에 로드하거나, 게임의 각 스테이지에 필요한 데이터만 웹서버에 요청하여 다운로드 받도록 구현해야 한다.
드래곤프렌즈의 경우에는 모든 리소스를 zip 압축하여 패킹하고, 웹 브라우저도 zip 압축된 상태로 메모리에 로드한다. 게임 코드가 데이터를 필요로 하는 최종 순간에만 일시적으로 압축 해제한다. - 리소스 다운로드 용량
게임의 리소스 파일을 최초 한 번 다운로드 받는 것은 피할 수 없겠으나, 매번 게임 페이지에 접속시마다 대용량의 다운로드가 일어난다면 게임의 로드 속도와 서비스 비용에 큰 문제가 발생한다.
즉, 한 번 다운로드된 리소스는 재접속시 다시 다운로드 받지 않도록 해줘야 한다.
다행히도 모든 웹 브라우저는 브라우저 수준의 캐쉬를 구현하기 때문에 많은 경우에는 웹 어플리케이션 개발자가 아무런 작업을 하지 않고 웹브라우저 캐쉬에 의존해도 된다. 그러나 웹 브라우저의 캐쉬 기능이 언제 어떻게 작동하느냐는 각 웹 브라우저의 정책에 달려있기 때문에 의도와 다르게 캐쉬가 작동하지 않는 경우도 많다. 대표적인 예로, 서버에서 다운받아야 하는 파일의 크기가 20MB를 넘어가면 대부분의 웹브라우저에서는 서버의 파일을 로컬에 캐쉬하지 않는다. 또는 PC의 하드디스크 여유공간 상태에 따라 자동으로 캐쉬 기능이 꺼지기도 한다.
따라서 웹브라우저의 기본 캐쉬 기능에 의존하기 보다는, 리소스 파일들을 20MB 이하로 유지하고 HTML5 Application Cache 등의 로컬 캐쉬 기능의 사용을 추천한다.
또한, 대부분의 웹서버는 네트웍 전송시 zip 압축을 지원하므로 파일 다운로드에 대한 HTTP request와 response에서 gzip이 작동하도록 설정해야 네트웍 전송량을 최소화 할 수 있다. - 로컬에 파일 저장
유저의 설정 파일과 같이 PC의 하드디스크에 파일을 저장해야 하는 경우가 있다.
다행히 대부분의 웹 브라우저들은 로컬에 파일을 저장하기 위한 목적으로 Web Browser Local Storage와 Indexed DB 기능을 지원한다.
게다가 Emscripten은 C 코드의 표준 파일 API를 이용하여 웹 브라우저의 Indexed DB에 파일을 읽고 쓸 수 있도록 지원한다. (File system API 문서 참고)
그러나 브라우저 환경에 따라 로컬 저장수에 수 십 메가 이상의 용량은 저장 안 될 수 있다는 점에 유의해야 한다.
OpenGL → WebGL
대부분의 PC용 웹브라우저들은 WebGL 1.0을 지원하며, 다행히도 WebGL 1.0은 현재 모바일 기기에서 가장 널리 지원되는 OpenGL ES 2.0과 거의 유사하다. 그러므로 OpenGL ES 2.0에서 작동하는 코드라면, 단 한가지의 용법을 제외하고는 Emscripten을 이용한 컴파일만으로 별 문제없이 작동한다.
WebGL에서는 Client side data (메인 메모리상에 있는 렌더링 데이터로부터 직접 렌더링 하는 경우) 기능을 지원하지 않는데, 이것을 사용하는 부분에 대해서만 Server side data 를 생성하도록 약간의 수정을 해 주면 되므로 큰 제약사항은 아니다.
WebGL을 사용해도 PC의 GPU에 의한 하드웨어 렌더링 가속이 작동하므로 모바일 환경보다 강력한 렌더링 성능의 잇점을 누릴 수 있다.
Audio 구현
현재의 웹브라우저 환경에서는 HTML5의 audio 태그 또는 Web Audio API 를 이용하여 사운드 구현을 할 수 있으나, 인터넷 익스플로러는 11 버전에서 조차 Web Audio API를 지원하지 않기 때문에 사실상 audio 태그만을 유일한 동적 사운드 구현 방안으로 볼 수 있다.
Emscripten에 포함된 SDL 라이브러리 호환 포팅도 내부적으로 audio 태그를 사용하도록 구현되어 있다.
audio 태그는 웹 페이지에서 배경음이나 간단한 사운드 효과음 플레이만을 지원하기 위한 기능이기 때문에 그 이상의 효과는 웹 환경에서 사실상 구현이 어렵다고 봐야 한다.
게다가 웹브라우저마다 동적으로 생성되는 audio 태그에 대한 작동 방식이 약간씩 다르기 때문에 Emscripten 1.1x 버전도 사운드 기능의 브라우저 호환성이 매우 안좋다. 호환성 문제때문에 드프의 경우 Emscripten 내부의 SDL 구현에 다수의 수정을 가해야 했다.
브라우저 호환성의 고려
전세계에서 사용되는 PC용 주요 웹브라우저들을 점유율 순서대로 나열하면 크롬, 인터넷익스플로러, 파이어폭스, 사파리로 볼 수 있다. 각 조사 기관마다 각 브라우저별 점유율과 순위가 다소 차이가 있지만 추세는 크게 다르지 않다.
이들 중에서 내부 엔진이 전혀 다른 인터넷익스플로러만 나머지 브라우저들과 큰 호환성 차이가 있고, 그 외 4개의 브라우저들은 매우 유사한 특징을 가진다.
그러므로 인터넷 익스플로러의 경우가 가장 호환성 문제가 심각하다고 볼 수 있으며, 그나마 인터넷 익스플로러 11 이상 버전만이 위에서 언급한 대부분의 기능들을 지원하고, 그 이전 버전에서는 WebGL을 비롯한 다수의 기능이 사용 불가능하다. 때문에 드래곤프렌즈도 IE는 11이상만을 지원한다.
본인이 웹 환경으로 게임을 포팅중에 발견한 브라우저별 주요 문제점들을 정리하면 아래와 같다.
- 크롬
- 수 십 메가 이상의 JS 파일이 로드되면 JS 디버거에서 소스코드를 제대로 볼 수 없다.
- 원인을 알 수 없으나, 정상 작동하던 일부 PC에서 WebGL 기능이 갑자기 영구적으로 사용 불가능해 지는 경우가 있다. chrome://gpu 페이지에 들어가면 렌더링 가속이 모두 사용 불가능한 것으로 표시된다.
- 페이지 재로드 시에 JS 코드의 실행속도가 매우 느려지는 경우가 간혹 발생한다. 크롬 프로파일러로 확인하면 브라우저의 내부 기능에서 매우 느려짐을 볼 수 있다. 다행히 페이지를 다시 한 번 재로드하면 해결된다. - 인터넷 익스플로러 11
- 브라우저의 버그인 것으로 보이는데, 개발자용 창들이 켜지면 메모리를 무제한 할당하는 현상이 있어, JS나 HTML 페이지의 디버깅은 사실상 불가능하다.
- 페이지 재로드 시에 기존에 페이지에서 할당된 메모리가 먼저 해제되지 않기 때문에 메모리 부족 현상이 발생하기 쉽다.
- 불과 몇 달 전까지만 해도 facebook.com에 접속시 구버전 익스플로러버전 호환 모드로 자동 전환되어 페이스북 사이트 내에서 WebGL 기능을 사용할 수 없기 때문에 사용자가 호환성 설정을 바꿔줘야만 페이스북 사이트와의 문제를 해결할 수 있었다. (본인이 페이스북의 기술 담당자에게 이 문제를 계속 제기한 때문인지는 모르겠으나) 최근에는 IE11 기본 설정에서도 facebook.com에 접속시 기존버전 모드로 전환되지 않는다.
- audio 태그를 동적으로 삭제하면 내부적으로는 즉시 사라지지 않아, 다수의 사운드를 연속으로 재생시 동시 플레이 채널 수 부족 오류가 발생한다. - 파이어폭스
- 대체로 크롬과 매우 유사한 특징을 지니나, 크롬에 비해 실행 속도가 느리다. - 사파리
- 7 버전에서는 WebGL 기능이 기본 설정에서는 막혀 있고, 사용자가 직접 이 기능을 켜줘야 한다.
- JS 코드가 매 프레임 실행될 떄 사용자 입력이 전혀 안 먹히는 경우가 간혹 발생한다.
- facebook.com과 같이 https로 접속되는 사이트 내에 임베딩 되는 페이지의 경우(일반적인 페이스북 게임의 형태), local storage를 사용할 수 없다.
이상으로 모바일 게임을 PC 웹 브라우저 게임으로 포팅하는데 발생하는 여러 이슈들을 간략이 정리하였다.
웹에서의 HTML5와 WebGL 기술은 아직 완벽하지는 않지만, 다양한 PC 환경의 유저들에게 상용 게임을 서비스 할 수 있는 충분한 여건은 이미 조성되어 있다.
이 기술들은 게임 뿐만이 아니라 매우 광범위한 애플리케이션들에서 활용 가능하다. MS 오피스나 3DSMAX와 같이 거대한 클라이언트 앱을 웹으로 옮기는 것도 충분히 실현 가능한 일이다.
불과 2년 전만 해도 미래를 기대하는 비전 제시에 불과해 보였던 관련 기술들이 최근에 여러 참여 업체들에 의해 빠르게 표준으로 자리잡았음을 느낀다.
게임 서비스에 필요한 모든 것을 웹 환경에서 구현해 내는 것에는 많은 노력이 필요한 것이 사실이나, 웹브라우저에서 거의 모든 것을 할 수 있는 환경을 구현한다면 고생한 것 이상의 가치가 있다고 생각한다.
# by | 2014/11/25 17:20 | 일(Work) | 트랙백 | 덧글(0)



