개발 블로그

유니티, 고도 없이 엔진 만들기 경험담

· windy85

냥냥비행단은 유니티전 세계 많은 게임이 쓰는, 이미 만들어진 게임 엔진이에요.도, 고도누구나 무료로 쓸 수 있는 오픈 소스 게임 엔진이에요.도 쓰지 않았습니다. 이미 만들어진 게임 엔진게임을 만들 때 그리기, 소리, 터치 같은 기본 일을 대신 해 주는 큰 도구 상자예요. 대신, 웹 기술웹페이지를 만드는 기술(HTML, CSS, 자바스크립트)이에요. 브라우저만 있으면 폰에서도 PC에서도 돌아가요. 하나로 모바일, 안드로이드, PC를 모두 감당하는 자체 엔진을 만들어 가고 있습니다.

엔진을 직접 만든다는 건, 엔진이 대신 해 주던 일을 하나하나 저희가 책임진다는 뜻입니다. 그중 가장 먼저, 가장 세게 부딪힌 것이 성능이었습니다.

성능이 왜 그렇게 중요할까요? 성능이 받쳐 주지 않으면, 보여 주고 싶은 연출을 포기해야 하기 때문입니다. 필살기 한 번에 화면을 가득 채우는 불꽃과 번개, 수십 마리의 적과 탄막이 냥냥비행단의 재미입니다. 폰이 버티지 못하면 이걸 덜어 내는 수밖에 없습니다.

모바일은 특히 범위가 넓습니다. 최신 폰부터 몇 년 된 폰까지 모두 같은 게임을 합니다. 그리고 캐주얼 게임은 좋은 폰을 가진 사람만 하는 게임이 아닙니다. 그래서 처음부터 옛 폰을 기준으로 삼았습니다.

원칙은 하나입니다. 옛 폰과 새 폰의 연출과 플레이 경험은 똑같이. 새 폰에서는 블룸밝은 곳 둘레로 빛이 번져 보이게 하는 효과예요. 같은 후처리다 그린 화면 위에 마지막으로 한 번 더 손보는 단계예요. 색을 다듬거나 빛을 번지게 해요. 몇 가지만 양념으로 더합니다.

전투 화면은 480×216 픽셀 아트입니다. 작은 화면이지만, 고양이 네 마리가 쏘는 기술과 수십 마리의 적, 탄막이 한꺼번에 그려집니다.

10월 3일, 게임을 하던 폰이 뜨거워졌습니다.

"폰이 뜨거워요."

프레임이 잘 나와도 폰은 뜨거울 수 있습니다. 숫자에 바로 보이지 않는 문제라서, 그날부터 한 달 동안 성능을 손봤습니다.

  • 🔥 무거운 보스전의 CPU기기의 머리예요. 계산하고 결정하는 일을 해요. 사용 시간(여러 스레드프로그램 안에서 일하는 일꾼 한 명이에요. 브라우저 안에는 일꾼이 여럿 있어서, 다 더하면 1초보다 길 수 있어요. 합): 1초에 1,047ms밀리초. 1초를 1,000칸으로 나눈 한 칸이에요. 1초에 60장을 그리려면 한 장을 약 16ms 안에 그려야 해요. → 약 600ms
  • 📱 옛 폰을 흉내 낸 로비: 42fps1초에 화면을 몇 장 그리는지예요. 60fps면 1초에 60장이고, 많을수록 부드러워요. → 60fps
  • 🎨 전투 한 프레임의 그리기 명령: 많게는 3,367번 → 드로우콜그래픽 칩(GPU)에게 "이거 그려!" 하고 한 번 시키는 거예요. 시키는 횟수가 적을수록 빨라요. 3번
전투 한 장면: 고양이 네 마리의 기술과 불꽃, 번개가 한 화면에
이 화면 한 장에 그리기 명령이 많게는 수천 번 들어갑니다.

무엇을 먼저 재었는지, 눈에 안 보이게 무거웠던 것은 무엇인지, PC에서 빨라졌는데 폰에서는 6배 느려진 실패, 그리고 마지막으로 전투 그리기를 통째로 GPU그림 그리는 일만 하는 칩이에요. 수많은 점을 한꺼번에 칠하는 데 아주 빨라요.로 옮긴 이야기를 정리해 보겠습니다.

1. 첫 번째로 찾은 것들 (10월 3일)

처음 찾은 것들은 생각보다 단순했습니다.

  • 120Hz화면이 1초에 120번 새로 바뀌는 폰이에요. 보통 폰은 60번이에요. 폰에서는 같은 장면을 두 번 그리고 있었습니다. 게임은 1초에 60번 움직이는데, 화면은 120번 그렸습니다. 게임이 한 걸음 나아갔을 때만 그리게 바꿨습니다.
  • 효과음을 칠 때마다 새로 합성하고 있었습니다. 처음 한 번 OfflineAudioContext소리를 바로 내지 않고, 녹음해 두듯 미리 만들어 두는 브라우저 기능이에요.로 미리 만들어 두고, 그다음부터는 재생만 합니다.
  • HUD게임 화면 위에 떠 있는 체력, 버튼, 점수 같은 것들이에요. 체력 막대가 width를 바꿔 매번 레이아웃화면의 글자와 상자를 어디에, 얼마나 크게 놓을지 다시 계산하는 일이에요. 자주 하면 느려져요.을 다시 잡았습니다. transform: scaleX로 바꾸고, 스타일은 값이 실제로 바뀔 때만 쓰게 했습니다.
  • three.js웹에서 3D 그림을 쉽게 그리게 도와주는 도구 모음이에요.의 투명한 양면 재질이 두 번씩 그려지고 있었습니다. forceSinglePass로 한 번에 그립니다.

가장 무거운 축에 드는 보스전에서 CPU 사용 시간이 1초에 1,047ms에서 약 600ms로 줄었습니다. CPU를 4배 느리게 했을 때 한 프레임은 21.4ms에서 약 9ms가 됐습니다.

2. 고치기 전에, 재는 도구부터

"느린 것 같다"로는 고칠 곳을 고를 수 없습니다. 그래서 두 가지를 먼저 만들었습니다.

게임 안 프로파일러프로그램이 어디에 시간을 얼마나 쓰는지 재 주는 도구예요. 의사 선생님의 청진기 같은 거예요.. 설정 화면에서 버전 글자를 일곱 번 누르면 열리는 개발용 오버레이입니다. 구간별 CPU 시간, GPU 패스별 시간(timer queryGPU에게 "이 그림 그리는 데 얼마나 걸렸어?" 하고 물어보는 기능이에요.), 적·탄·효과의 개수, 그리고 마지막으로 튄 프레임과 그때 게임에서 무슨 일이 있었는지를 보여 줍니다. 폰에서 그대로 열 수 있어서, 폰에서 끊길 때 바로 원인을 볼 수 있습니다.

PC 시험대. 헤드리스 크롬화면 없이 돌아가는 크롬이에요. 사람 대신 프로그램이 조종해요.을 Chrome DevTools Protocol프로그램이 크롬에게 "이 페이지 열어, 여기 눌러, 시간 재 줘" 하고 시킬 때 쓰는 약속된 말이에요.로 조종합니다. 같은 판을 같은 난수(seed)주사위가 나오는 순서를 정해 두는 숫자예요. seed가 같으면 매번 똑같은 판이 펼쳐져요.로 돌리고, trace프로그램이 한 일을 시간 순서대로 빠짐없이 적은 기록이에요.로 스레드별 CPU 시간을 잽니다. CPU를 2배·4배 느리게 해서 옛 폰을 흉내 냅니다.

재면서 지킨 것도 있습니다.

  • 작업하는 PC가 다른 일로 바쁠 때가 많아서, 벽시계 시간시계로 잰 실제 시간이에요. PC가 다른 일로 바쁘면 같은 일도 더 오래 걸린 것처럼 나와요. 대신 스레드별 CPU 시간을 비교했습니다.
  • 한 판을 통째로 재면 결과가 ±10%씩 흔들렸습니다. 그래서 바꾸기 전과 후를 번갈아 두 번씩 쟀습니다.
  • 가장 정확한 방법은 같은 프레임을 멈춰 놓고 다시 그리는 것이었습니다. 게임 루프"움직이기 → 그리기"를 1초에 수십 번 되풀이하는, 게임의 심장 박동이에요.를 멈추고 같은 장면을 여러 번 그려서, 그리는 비용만 따로 잽니다.
같은 프레임을 효과를 켜고, 끄고 다시 그린 모습
같은 프레임을 효과를 켠 채로(왼쪽), 끈 채로(오른쪽) 다시 그린 모습입니다. 왼쪽 위 검은 상자가 게임 안 프로파일러입니다.

3. 눈에 안 보이는 비용

고쳐 보니 무거운 것은 대부분 화면에서 눈에 띄지 않는 곳에 있었습니다.

돌고 있는 CSS 애니메이션은 하나하나가 매 프레임 비용

메뉴 화면은 HTML과 CSS웹페이지의 색, 크기, 움직임을 정하는 규칙이에요. HTML이 내용이라면 CSS는 꾸미기예요.로 만들었습니다. 반짝이는 빨간 점, 숨 쉬듯 빛나는 테두리처럼 작은 움직임이 많을수록 화면이 살아 보입니다.

그런데 크롬은 돌고 있는 CSS 애니메이션을 하나하나 매 프레임 메인 스레드브라우저에서 가장 바쁜 일꾼이에요. 게임 코드도, 화면 계산도 대부분 이 일꾼이 해요.에서 스타일 재계산화면의 모든 것이 지금 어떤 색, 크기, 모양인지 다시 따져 보는 일이에요.합니다. 컴포지터다 그린 그림 조각들을 겹쳐서 화면 한 장으로 붙이는 일꾼이에요. 옮기기(transform)와 투명도(opacity)는 이 일꾼이 맡아서 가벼운 편이에요.에서 돌아가는 transform과 opacity 애니메이션도 마찬가지였습니다. 로비의 빨간 점 10개(애니메이션 30개)만 걷어내도, CPU를 4배 느리게 한 로비가 42fps에서 60fps가 됐습니다.

그래서 계속 반복되는 장식은 한 번 그려서 APNG움직이는 PNG 그림이에요. GIF처럼 움직이지만 색이 훨씬 고와요. 한 장으로 만들고 <img>로 재생하게 바꿨습니다. 편성 화면에서 돌던 애니메이션이 101개에서 1개가 됐고, 43~56fps가 60fps가 됐습니다.

할로윈 이벤트 화면의 보상 목록은 60줄마다 반짝임, 별, 흔들리는 아이콘이 있어서 애니메이션이 318개 돌고 있었습니다. 화면 밖으로 스크롤된 줄을 멈추자 54개가 됐습니다. 레이아웃은 1초에 61ms에서 3ms로, 페인트화면의 한 부분을 실제로 다시 칠하는 일이에요.는 1초에 153번에서 3번으로 줄었습니다.

filter: drop-shadow 안에서 무언가가 움직이면, 브라우저는 매 프레임 그림자를 다시 블러그림을 뿌옇게 번지게 하는 일이에요. 보기보다 계산이 많아요.합니다. 전투 HUD 초상화가 그랬습니다. 그림자를 움직이지 않는 조각에 따로 달았습니다.

가려진 화면도 계속 그리고 있었다

팝업이 로비를 덮어도, 그 뒤의 WebGL웹페이지에서 GPU를 직접 부려 그림을 그리는 방법이에요. 배경과 애니메이션은 계속 돌고 있었습니다. 보이지 않는 화면을 열심히 그리고 있던 셈입니다.

화면을 층으로 쌓는 레이어 스택화면을 종이처럼 겹쳐 쌓는 방식이에요. 위 종이가 덮으면 아래 종이는 쉬게 해요.을 만들었습니다. 덮인 화면은 숨기고(content-visibility), 애니메이션은 멈추고, 게임 루프는 잠재웁니다. 로비 위에 팝업이 떠 있을 때 메인 스레드 사용이 1초에 약 240ms에서 20~40ms가 됐습니다.

블룸과 바닥 그림자는 빼기로

후처리 블룸은 GPU를 1.28ms 쓰고 있었습니다. 켠 화면과 끈 화면을 나란히 영상으로 놓고 보니 차이가 크지 않았습니다. 그래서 기본값을 끔으로 바꾸고, 새 폰에서 켜는 양념으로 설정에 남겼습니다. 끄면 0.20ms입니다.

바닥 그림자도 같은 방법으로 봤습니다. 같은 프레임을 켜고 끈 채로 그려 비교하니, 화면 맨 아래 10픽셀 줄에서 밝기가 255 중 5만큼 달라질 뿐이었습니다. 뺐습니다.

저사양 모드는 자동으로, 하지만 영원히는 아니게

메뉴 화면 150프레임 중 절반 넘게 24ms를 넘기면, 저사양 모드느린 폰을 위해 움직임과 그리는 횟수를 줄여 가볍게 돌리는 모드예요.가 자동으로 켜집니다. 로비를 30fps로 그리고, 반복 애니메이션은 한 번만 재생합니다.

처음에는 한 번 켜지면 계속 켜져 있었습니다. 하지만 폰이 잠깐 바빴던 것일 수도 있습니다. 그래서 가끔 1초씩 원래 속도로 돌려 보고, 폰이 따라오면 다시 끕니다. 처음에는 15초 뒤, 그다음부터는 간격을 두 배씩 늘려 최대 10분입니다. 매번 켤 때마다 다시 재면 느린 폰이 그때마다 몇 초씩 끊기니, 지난번 결과로 시작하고 나중에 조용히 다시 재는 방식입니다. 플레이어가 직접 켜거나 끈 것은 그대로 둡니다.

처음 한 번만 끊기는 것

처음 여는 화면이 끊기는 이유는 대개 같습니다. 셰이더 컴파일셰이더는 GPU에게 "이렇게 칠해" 하고 주는 작은 프로그램이에요. 처음 한 번은 GPU가 알아듣게 바꾸는(컴파일) 시간이 걸려요., 이미지 디코딩압축해 둔 그림 파일을 화면에 쓸 수 있게 푸는 일이에요., 첫 그리기가 한 프레임에 몰립니다.

  • 화면 전환: 전환 막이 닫힌 뒤에 다음 화면을 만들고, 첫 프레임과 이미지 디코딩이 끝나면 막을 엽니다. 누르고 나서 전환 막이 움직이기 시작하기까지 232ms 걸리던 것이 7ms가 됐습니다.
  • 필살기 컷인필살기를 쓸 때 화면을 가로질러 나오는 고양이 그림 연출이에요.: 전투가 시작될 때 보이지 않게 한 번 미리 재생해 둡니다. 첫 컷인에서 가장 길게 멈춘 시간이 134~184ms에서 83ms가 됐습니다.
  • 첫 뽑기 카드: 카드의 등급 별(SVG점과 선을 수식으로 적어 둔 그림이에요. 크게 해도 안 깨지지만, 그릴 때마다 계산해야 해요.)이 처음 뒤집힐 때 GPU를 100~135ms 붙잡고 있었습니다. 미리 비트맵점(픽셀)을 하나하나 칠해 둔 그림이에요. 다시 계산할 필요가 없어요.으로 구워 둡니다.

4. 실패: PC에서는 빨라졌는데, 옛 폰에서는 60 → 10프레임 (10월 27일)

전투 스프라이트게임 속 캐릭터나 총알 같은 작은 그림 한 장이에요.를 텍스처 아틀라스작은 그림들을 큰 종이 한 장에 모아 붙여 둔 거예요. GPU가 종이를 바꿔 끼우지 않아도 돼서 빨라져요.로 모았습니다. 그림마다 텍스처GPU 메모리에 올려 둔 그림이에요.가 다르면 그릴 때마다 배치GPU에게 한 번에 묶어 보내는 그리기 일감 꾸러미예요. 그림 종이가 바뀌면 꾸러미를 새로 싸야 해요.가 끊기니, 큰 페이지 몇 장에 모아서 그리면 빨라진다는 잘 알려진 방법입니다.

PC에서 재니 GPU 스레드가 3.5%, 전체가 2~3% 줄었습니다. 그날 오후 테스트 앱에 올렸습니다.

그날 밤, 2019년에 나온 갤럭시 노트10+(Mali-G76갤럭시 노트10+에 들어 있는 그래픽 칩(GPU)의 이름이에요. GPU)에서 스테이지가 60프레임에서 10프레임 정도로 떨어졌습니다.

PC에서는 다시 만들 수 없었습니다. 크롬의 저사양 기기 모드를 켜도, 2D 캔버스웹페이지에 붓으로 그리듯 선, 원, 그림을 하나씩 그리는 기본 그림판이에요. 이 그림판 그리기를 GPU가 대신 해 주는 걸 "가속"이라고 해요. 가속을 꺼도 PC는 느려지지 않았습니다. 그때의 아틀라스는 1024×1024 2D 캔버스 다섯 장(약 21MB)으로 만들어져 있었고, 모든 스프라이트를 거기서 그렸습니다. 그 경로가 그 폰의 드라이버운영 체제와 GPU 사이에서 말을 옮겨 주는 통역 프로그램이에요. 폰마다 달라서, 같은 일도 폰마다 빠르기가 달라요.에서 유독 느렸던 것으로 보입니다. 정확한 이유는 지금도 모릅니다. 아틀라스를 빼고 테스트 앱을 다시 보내자 바로 원래대로 돌아왔습니다.

이날 뒤로 지키는 규칙이 생겼습니다.

  • GPU 경로를 바꾸는 변경(큰 텍스처, 캔버스 가속, 새 GPU 캔버스)은 PC 숫자를 믿지 않고, 옛 폰에서 먼저 돌려 봅니다.
  • 그런 변경에는 스위치를 달아 기기마다 켜고 끌 수 있게 하고, 실제 서비스 빌드플레이어들이 스토어에서 받는 진짜 앱이에요.에서는 꺼 둔 채로 시작합니다.
  • 할당프로그램이 쓸 메모리를 새로 빌리는 일이에요. 자주 빌리면 나중에 치우는 시간도 늘어요.을 줄이거나 스타일 재계산을 줄이는 CPU 쪽 변경은 비교적 안전합니다.

5. 마지막: 전투 그리기를 통째로 GPU로 (11월 3일)

먼저 세어 보기

WebGL 쪽 드로우콜은 이미 적었습니다. 한 프레임에 16~18번(3D 배경 11~13번, 후처리 3번, 전경 2번)입니다. 빛, 글자, 먼지, 날씨는 이미 인스턴싱같은 모양을 여러 개 그릴 때 "어디에, 무슨 색으로"만 적은 목록을 주고, GPU가 한 번에 찍어 내게 하는 방법이에요. 도장 하나로 여러 번 찍는 것과 비슷해요.으로 한 번에 그리고 있었습니다.

문제는 효과가 그려지는 480×216 Canvas 2D였습니다. 한 프레임에 평균 430~875번, 많게는 1,000~2,300번 그리기 명령이 나갔습니다. 효과를 하나씩 꺼 보며 세어 보니, 바쁜 5★ 장면에서 1,294번이던 것이 효과를 모두 끄면 183번이었습니다. 스프라이트는 적었고, 대부분이 효과였습니다.

고양이 50마리를 한 마리씩 따로 돌려 기술과 필살기를 세어 봤습니다. 가장 많은 고양이는 한 프레임에 3,367번까지 나갔습니다.

고양이 50마리의 가장 바쁜 한 프레임, 그리기 명령 수
고양이마다 기술과 필살기를 쓰는 동안 가장 바빴던 프레임입니다. 같은 프레임을 WebGL 인스턴싱으로 그리면 50마리 모두 드로우콜 2~9번입니다.

다행히 쓰는 API프로그램끼리 일을 부탁할 때 쓰는 정해진 말과 규칙이에요.는 단순했습니다. 50마리 모두 기본 합성(source-over새 그림을 원래 그림 위에 그냥 덮어 그리는, 가장 평범한 방식이에요.)과 단색만 썼습니다. 선, 사각형, 원과 타원, 이미지가 거의 전부였고, 그라디언트색이 조금씩 바뀌며 이어지는 칠하기예요.나 그림자, 필터는 하나도 없었습니다.

Canvas 2D와 같은 API, 그리기는 GPU에서 한 번에

게임 코드에는 그리기 호출이 수백 곳 있습니다. 원과 타원을 그리는 곳만 685곳입니다. 이걸 모두 고치는 대신, Canvas 2D와 같은 모양의 API를 가진 기록기를 만들었습니다.

save/restore, 변환 행렬그림을 옮기고, 돌리고, 키우는 걸 숫자 표 하나로 적어 둔 거예요., path연필로 이어 그린 선의 길이에요. 그 길을 따라 칠하거나 테두리를 그려요., drawImage, fillRect, clip, 점선까지 Canvas 2D처럼 받습니다. 다만 바로 그리지 않고 호출 하나하나를 인스턴스 데이터도장 한 번 찍는 데 필요한 정보예요. 어디에, 얼마나 크게, 무슨 색으로. 하나로 쌓아 둡니다. 프레임 끝에 drawArraysInstanced 한 번으로 모두 그립니다. 인스턴스는 스프라이트, 사각형, 타원, 고리와 호, 선, 삼각형, 꺾인 선(리본) 같은 몇 가지 모양입니다.

  • 그리는 순서는 깊이 테스트그림마다 앞뒤 번호를 매겨서, 앞에 있는 것만 보이게 하는 GPU 기능이에요.로 지킵니다. 조각마다 z를 조금씩 낮춰서, 나중에 그린 것이 위에 옵니다.
  • clip정해진 모양 안에만 그려지게 오려 내는 거예요.은 스텐실 버퍼그려도 되는 곳만 구멍을 뚫어 둔 모양 종이예요. 그 구멍 안에만 색이 칠해져요.로 처리합니다. 사각형, 원, 다각형을 지원합니다.
  • 스프라이트는 텍스처 아틀라스에서 꺼냅니다. 1024×1024 페이지 세 장과 큰 그림용 2048×2048 한 장입니다. 10월의 실패와 달리 2D 캔버스가 아니라 WebGL 텍스처에 바로 올립니다.
  • 모르는 호출이 오면 원래의 2D 캔버스로 그리고, 그 개수를 프로파일러에 띄웁니다.

같은 그림인지 픽셀로 확인하기

성능보다 먼저 확인할 것이 있었습니다. 화면이 똑같아야 합니다.

고양이 50마리마다 기술 한 장면, 필살기 두 장면, 모두 150장면을 골랐습니다. 난수를 고정하고 같은 프레임을 두 방식으로 그린 뒤, 픽셀마다 비교했습니다. 색 값이 255 중 40 넘게 차이 나는 픽셀은 가운데 값이 0.03%였습니다. 가장 많이 달라진 장면도 1.03%였는데, 솜이 필살기의 광선 가장자리였습니다.

Canvas 2D와 WebGL 인스턴싱으로 같은 프레임을 그린 비교
왼쪽은 원래의 Canvas 2D, 오른쪽은 WebGL 인스턴싱입니다. 배경은 따로 그려지므로 빼고, 효과가 그려지는 층만 비교했습니다.

맞추는 동안 걸린 함정도 몇 가지 있었습니다.

  • 꺾인 선을 선분으로 하나씩 그리니, 이음매가 두 번 칠해져서 밝은 점이 생겼습니다. 이음매를 맞대어 이은 리본(miter)꺾인 곳을 비스듬히 잘라 맞대어, 끊김 없이 한 줄로 이은 모양이에요. 하나로 그립니다.
  • 크롬은 이동만 있고 크기 변화가 없는 drawImage를 정수 픽셀에 맞춰 그립니다. 0.5픽셀에 놓인 벽돌이 어긋나서, 똑같이 맞추게 했습니다.
  • 텍스처 배열같은 크기의 그림 여러 장을 한 묶음으로 GPU에 올려 두는 방법이에요.(texSubImage3D)에 캔버스를 올리면 캔버스를 다시 읽어 와서 그림 한 장에 1.2ms가 걸렸습니다. 평범한 2D 텍스처 페이지로 바꾸니 0.03ms가 됐습니다.

결과

Canvas 2D와 WebGL 인스턴싱의 한 프레임 시간 비교
PC 크롬에서 CPU를 느리게 해서 잰 숫자입니다. 같은 조건에서 두 방식을 번갈아 쟀습니다.
  • 전투: 2D 캔버스가 소프트웨어로 그려지는GPU의 도움 없이 CPU가 혼자 그림을 다 그리는 거예요. GPU를 쓸 수 없는 환경에서는 이렇게 돌아가요. 환경에서 한 프레임 8.8~9.0ms → 5.0~5.3ms
  • 전투: 2D 캔버스가 GPU 가속되는 환경에서 6.2 → 5.6ms, GPU 프로세스브라우저 안에서 GPU와 이야기하는 일만 따로 맡은 프로그램이에요. 메인 스레드 40% 감소
  • 타이틀 5.4 → 3.5ms, 로비 5.2 → 3.9ms (같은 방식을 타이틀과 로비의 고양이 편대에도 적용)

이번에는 규칙대로 했습니다. 스위치를 달아 테스트 앱에서 먼저 켰고, 10월에 60프레임을 10프레임으로 떨어뜨렸던 그 노트10+에서 먼저 돌려 봤습니다.

"너무 부드러워, 잘 돌아가."

그다음에는 50마리의 CPU 프로파일을 한 마리씩 떠서 남은 무거운 곳을 덜었습니다. 다각형 삼각분할울퉁불퉁한 모양을 GPU가 그릴 수 있게 작은 삼각형들로 잘라 내는 일이에요. earcut은 그 방법의 이름이에요.(earcut), 픽셀 원 그리기, 색을 바꾼 스프라이트 만들기 같은 곳입니다. 가장 무거웠던 눈송이 필살기는 두 번에 걸쳐 JS자바스크립트. 게임 코드가 쓰여 있는 프로그래밍 언어예요. 시간이 19%, 다시 25% 줄었습니다.

실제 서비스 빌드에서는 아직 꺼 두었습니다. 더 많은 기기에서 확인한 뒤 켤 생각입니다.

6. 숫자를 볼 때 조심한 것

  • 대부분의 숫자는 PC 크롬에서 CPU를 일부러 느리게 해서 잰 것입니다. 진짜 옛 폰의 숫자는 아닙니다. 특히 GPU 쪽은 CPU를 느리게 해도 흉내 낼 수 없었습니다.
  • 그래서 폰에서 확인한 것은 숫자가 아니라 실제로 돌려 본 느낌입니다. 10월의 60 → 10프레임도, 11월의 "너무 부드러워"도 같은 노트10+ 한 대에서 나왔습니다.
  • 바꾼 것마다 결과를 따로 쟀지만, 한 달 동안 게임 내용도 계속 늘었습니다. 그래서 "한 달 동안 몇 % 빨라졌다" 같은 한 줄짜리 숫자는 만들지 않았습니다.

정리: 배운 것 네 가지

  1. 고치기 전에 잰다. 그리고 같은 조건에서 번갈아 잰다. 판 하나를 통째로 재면 ±10%가 흔들립니다.
  2. 보이는 것보다, 계속 돌고 있는 것이 비싸다. 반짝이는 점 하나, 가려진 화면 하나가 매 프레임 비용입니다.
  3. PC 숫자는 GPU 경로에 대해 아무것도 보장하지 않는다. 옛 폰 한 대가 가장 좋은 시험대입니다. GPU를 건드리는 변경에는 스위치를 답니다.
  4. 빨라져도 그림이 달라지면 안 된다. 눈으로 보지 말고 픽셀로 비교합니다.

다음 숙제는 WebGL 인스턴싱을 실제 서비스 빌드에서도 켜는 것입니다. 로비에 남은 반복 애니메이션 20여 개도 줄여 볼 생각입니다. 지금은 손에 있는 옛 폰 한 대와 PC 시험대가 전부라서, 플레이어들의 폰에서 프레임이 어떻게 나오는지도 모아 볼 생각입니다.

마치며: 엔진을 직접 만든다는 것

폰이 뜨겁다는 말 한마디에서 시작해, 결국 전투를 그리는 방식을 통째로 바꾸기까지 한 달이 걸렸습니다. 엔진을 직접 만들었기에 할 수 있었던 일입니다. 어디가 무거운지 끝까지 들여다볼 수 있었고, 필요하면 그리는 방식부터 다시 짤 수 있었습니다.

성능을 손본 이유는 처음부터 하나였습니다. 보여 주고 싶은 연출을 포기하지 않기 위해서. 필살기의 불꽃도, 화면을 덮는 탄막도 그대로 두고, 옛 폰이 그걸 버티게 만드는 쪽을 택했습니다.

모바일은 넓고, 캐주얼 게임은 누구나 합니다. 그래서 옛 폰과 새 폰이 같은 장면을 보고 같은 손맛으로 놀 수 있어야 합니다. 새 폰은 블룸 같은 후처리라는 양념만 조금 더할 뿐입니다.

이 교훈은 냥냥비행단에서 끝나지 않습니다. 같은 방식으로 두 번째, 세 번째 게임도 최적화를 진행하고 있습니다.

유니티와 고도를 벗어나 게임 엔진을 직접 만드는 길은 쉽지 않습니다. 그래도 캐주얼 게임에서는 충분히 의미 있는 길이라고 생각합니다.

이번 한 달 동안 쌓은 도구와 규칙, 그리고 실패까지, 앞으로 만들 게임에도 큰 자산이 될 것 같습니다.

기대해 주세요.

덧붙여: 옛 폰에서 돌려 본 모습

2019년에 나온 갤럭시 노트10+에서 지금의 냥냥비행단을 돌려 찍은 영상입니다. 고양이 29마리가 한꺼번에 날며 필살기와 탄막을 쏟아 내는 판인데, 새 폰과 같은 연출 그대로 돌아갑니다.

갤럭시 노트10+ (2019년)에서 실제로 플레이하는 화면을 다른 폰으로 찍었습니다.

← 블로그 목록