AI에게 만드는 법 대신 실패하는 법을 묻다: 포토그래퍼의 수천 명 동시 접속 게임 만들기
들어가며
우아한형제들은 매년 우아콘부터 창립 기념 전사 행사, 외식업 종사자나 배달 라이더를 위한 콘퍼런스까지 다양한 행사를 주최합니다. 저는 포토팀으로서 그 행사들을 사진으로 남기는 일을 오랜 기간 해왔는데요. 행사를 다니다 보면 화면에 뜬 QR코드로 질의응답을 받거나, 퀴즈를 풀거나, 다 같이 게임을 하는 코너를 자주 만나게 됩니다. 저도 촬영자가 아닌 청중으로서 거의 매번 접속해 봤지만, 제대로 즐긴 기억이 거의 없습니다. 워낙 많은 사람이 한 장소에서 동시에 접속하다 보니 로딩이 안 되거나, 진행되다가도 다음 화면으로 넘어갈 때 끊기곤 했습니다. 주변을 둘러보면 누구는 되고, 누구는 행사가 끝날 때까지 접속조차 안 되던 모습을 자주 접했습니다.
지난 7월 초 본격적인 무더위가 시작되기 전, 우아한형제들 창립 16주년 전사 행사가 진행되었습니다. 이번 행사에도 모든 구성원이 QR코드로 접속해서 진행하는 코너가 있었고요. 저는 올해도 게임을 제대로 즐기지 못했습니다. 왜냐하면 전사 구성원 모두가 동시에 접속했던 그 게임은 저와 같은 팀 동료 한 명이 함께 만들었고, 행사 당일엔 실시간 운영을 해야 했거든요.

행사장 전경. 정면의 큰 화면이 앞으로 이야기할 전광판입니다.
게임의 조건과 구현 범위
제작 요청과 3주라는 제약
저는 2026년 초 디자인실에 신설된 ‘크리에이티브AI파트너팀’에 합류했습니다. AI로 창작 실험과 디자인 AX를 주도하는 팀입니다. 이 게임은 포토그래퍼인 저와 팀 동료인 BX 디자이너 두 사람이 함께 만들었습니다.
전사 행사를 두어 달 앞둔 어느 날, 행사 주관부서에서 연락이 왔습니다. 전사 행사 게임 제작에 도움을 받고 싶다는 연락이었어요. 올 초 저희 팀이 결성되자마자 ‘AI로 이런 것도 됩니다’ 정도의 느낌으로 고슴도치를 굴리는 게임을 만들어 디자인실에 선보인 적이 있었는데, 아마 그걸 보고 연락을 주신 것 같았습니다. 처음엔 조언을 구하는 것인 줄 알았는데, 연락을 주고받다 보니 저희 팀이 이 게임을 만드는 방향으로 이야기가 흘러가더라고요. 그사이 시간이 훌쩍 지나 행사까지 고작 3주가 남아 있었습니다.
다행히도 조언을 해주실 DevEx팀(사내 개발자들의 개발 환경과 생산성을 돕는 팀) 개발자 두 분을 만나게 됐습니다. 첫 온라인 미팅이 아직도 기억납니다. 어떤 도움을 받고 싶은지 제게 여쭤보셨는데, 한참을 고민하다 답변을 드렸습니다.
“제가 저 스스로를 믿을 수 있도록, 자기 확신이 들게 해주세요.”
AI로 이것저것 만들어오다 보니 어렵긴 하겠지만 만들 순 있겠다는 감은 있었습니다. 고슴도치 굴리기처럼 게임은 만들면 되고, 다른 사내 도구처럼 배포하면 되겠다는 막연한 믿음도 있었어요. 다만 똑같은 게임이지만 규모가 남달랐고, 수천 명이 실시간으로 접속해야 했습니다. 제가 감당 가능한 수준인지 확신이 들지 않았습니다.
만들어야 했던 게임의 조건
만들어야 했던 게임의 조건부터 정리해 보겠습니다.
-
수천 명이 같은 시각, 같은 공간에서 각자의 폰으로 접속합니다.
-
MC의 진행 멘트에 맞춰 모든 참가자가 동시에 게임을 시작하고 종료합니다.
-
접속 QR 코드부터 게임 설명, 결과 집계까지 주요 장면이 무대 위 전광판에 나타납니다.
-
100% 라이브이기에 한순간도 되돌릴 수 없고, 실행이 멈추는 상황이 나와서도 안 됩니다.
아래는 최종적으로 만들어진 게임을 소개하는 40초짜리 영상인데, 이어질 이야기를 읽기 전에 한 번 보고 가시면 훨씬 이해하기 쉬울 거예요.
가장 먼저 포기한 것, WebSocket
동시 접속 인원이 워낙 많다 보니, 뭘 만들든 서버가 버텨야 한다고 생각했습니다. 예전에 사이드 프로젝트로 WebSocket을 사용한 멀티플레이 게임을 만든 적이 있어요. 전사 행사 게임도 비슷해 보였습니다. 전광판에 점수를 실시간으로 집계하고, 서버에서 게임의 시작과 종료를 일괄로 관리하는 모습이 떠올랐거든요.
문제는 이 시점에 사내 배포플랫폼에서 WebSocket을 쓰기 어려운 상황이었다는 점입니다. 방법이 아예 없진 않았지만, 남은 3주 안에 해낼 자신이 없었어요. 제가 이미 해본 범위에서 가능한 방법을 먼저 찾아보고, 그 틀 안에서 돌아갈 수 있는 게임만 만들어야겠다는 생각이 들었습니다.
그렇게 WebSocket 없이도 굴러갈 방법을 찾다가, 어떤 게임을 만들든 게임의 결괏값만 서버에 보내 집계하는 방식을 사용하기로 결정했습니다.
이걸로 서버가 하는 일은 단 두 가지로 줄었습니다. 게임 중 어떤 단계가 시작되는지 참가자들에게 알려주는 것, 그리고 참가자가 보낸 결과를 받아 합치는 것. 서버는 게임 결과를 받을 때까지 기다리기만 하면 됩니다.

Pod가 여러 대라서 생긴 일
저는 배포플랫폼을 통해 K8s(Kubernetes)에 서버를 올렸습니다. 서버를 올리기 시작하니 모르는 것투성이였고, 당시엔 처음 보는 용어 하나하나를 찾아가며 진행했습니다. 아래는 지금 제가 이해하고 있는 말로 작성했어요.
K8s는 여러 대의 서버를 묶어서 프로그램을 알아서 띄우고 관리해 주는 시스템이고, 그 프로그램이 도는 최소 단위를 Pod라고 부릅니다. 저는 당시에 이걸 서버 한 대쯤으로 이해했어요. 정확히는 서버 한 대에 해당하는 건 노드고 Pod는 그 위에서 도는 단위인데, 그건 나중에 알았습니다.
참가자가 접속하면 그중 하나의 Pod에 붙습니다. 이때 로드밸런서라는 게 알아서 여러 Pod로 참가자를 나눠줍니다. 부하가 한쪽으로 쏠리지 않기도 하고, Pod를 여러 개 만들 수도 있길래 솔직히 이걸로 됐다고 생각했습니다.
그런데 초기 백엔드를 배포하고, 운영용으로 여러 조작이 가능하도록 만들어둔 운영 패널에서 게임 단계를 변경해 보자 오직 한 대의 Pod에 붙은 참가자에게만 적용됐습니다.
게임 단계 변경도 결국 하나의 요청일 뿐이기에, 로드밸런서가 여러 Pod 중 한 대에만 전달하고 끝났던 겁니다. 그리고 그 Pod는 자기한테 붙어 있는 참가자에게만 이벤트를 보낼 수 있고요. 결국 나머지 Pod에 붙은 참가자들은 단계가 바뀐 줄도 모르는 상황이었습니다. 많으면 많을수록 안전하겠다고 생각했던 게 오히려 문제가 되어 돌아왔습니다.
운영 패널에서 변경하는 게임 단계를 모든 Pod가 같이 알 수 있는, 공지 사항이 올라올 그룹 채팅방 같은 게 필요했습니다. 이 과정에서 Redis를 알게 되었어요. 요청을 받은 Pod가 혼자 처리하고 끝내는 대신 Redis 채팅방에 그 내용을 올립니다. 나머지 Pod들도 그 방에 들어와 있으니 다 같이 알게 되고요. 그다음 각 Pod가 자기한테 붙어 있는 참가자들에게 SSE(Server-Sent Events)로 단계를 전달합니다. SSE는 참가자가 요청하지 않아도 서버가 먼저 이벤트를 보낼 수 있는 방식입니다.
이렇게 여러 Pod가 같은 상태를 보게 만들고 나서야, 모두가 같은 순간에 같은 단계를 진행하는 게임이 가능해졌습니다.
- 서버 → 클라이언트: SSE로 게임 단계와 카운트를 내려보냄
- 클라이언트 → 서버: REST POST로 결과 제출
- Pod 간: Redis 채널로 publish → 모든 Pod가 구독 → 자기 연결에 재전송

AI에게 물어본 실패하는 방법들
판단을 넘기지 않기 위해 정한 원칙
AI에게 "수천 명이 실시간으로 접속하는 게임을 만들어줘"라고 할 수도 있었습니다. 아마 어찌저찌 돌아가는 코드가 나왔을 거예요. "이건 어떻게 동작하는 거야?"라고 물으면 설명도 해줍니다.
문제는 그때 제가 할 수 있는 게 AI의 답변을 듣는 것뿐이라는 겁니다. 어떤 방식으로 통신할지, 접속이 끊기면 어떻게 할지, 같은 사람이 두 번 보내면 어떻게 처리할지. 그 판단들은 이미 AI가 제시해줬고, 저는 설명을 들어도 그게 우리한테 맞는 선택이었는지까지는 판단할 수가 없습니다.
그러면 나중에 문제가 생겼을 때도 마찬가지예요. 어떤 구조인지 모르니 뭐가 잘못됐는지도 모르고, 결국 원인을 찾고 고치는 일까지 통째로 AI에게 맡기게 됩니다. AI에게 판단을 한 번 넘기면 그다음 판단도 넘길 수밖에 없습니다.
AI가 계획을 먼저 보여주고 시작하기도 합니다. 그래도 그건 "이렇게 만들겠다"는 목록입니다. 저는 그 전에 "이런 게 잘못될 수 있다"를 확인하고 싶었습니다. 만들 방법을 검토하는 것과, 무엇을 걱정해야 하는지부터 아는 건 다르니까요.
그래서 거의 모든 상황에서 이런 식으로 물었습니다.
“어떤 때에 우리가 만들 서버가 터질까?”
이렇게 물으면 제가 이해할 수 있는 문장들로 답변이 옵니다. 예를 들면 이런 것들이었어요. 서버가 죽었다 살아날 때 모두가 한꺼번에 다시 접속하려 들면서 오히려 상황이 더 나빠진다는 것. 게임이 동시에 끝나니 결과 제출도 한꺼번에 몰린다는 것.
그럼 이건 어떻게 막느냐고 다시 물었습니다. 이번에도 방법이 목록으로 왔고, 그중에 제일 단순해 보이는 걸 골랐어요. 다시 접속할 때도, 점수를 보낼 때도 몇 초씩 랜덤하게 지연을 줘서 요청이 한꺼번에 몰리지 않게 하는 방법이었습니다. 상상한 대로 동작하지 않는 것들을 계속 이런 식으로 다듬었습니다. 개념을 이해한 상태에서 직접 판단하다 보면 코드를 몰라도 게임이 어떻게 맞물려 굴러가는지 파악할 수 있게 됩니다.
코드 세션과 검증 세션 분리
여기에 한 가지를 더했는데요. 코드를 작성하는 세션과 개념을 정리하고 검증하는 세션을 완전히 분리했습니다. 구현은 Claude Code로 하고, 개념을 확인하거나 판단이 필요할 때는 GPT 모델을 따로 열어서 물었습니다.
같은 세션 안에서는 방금 만든 것에 대해 AI가 스스로 잘 판단하지 못하더라고요. 자신이 제시한 방법을 적절하지 않았다고 바로 번복하는 꼴이 되니까요. 아예 다른 모델에 물으면 그럴 일이 없고요. 물론 검증 세션의 답이 정답이라는 보장도 없습니다. 다만 서로 다른 모델의 답변을 취합해 보면 판단에 쓰일 재료가 풍부해집니다.
수천 개의 봇과 그 한계
직접 겪은 상황으로 만든 부하 테스트
여기까지 하고 나니 또 다른 불안이 엄습해왔습니다. 이게 진짜로 돌아가는 걸까?
실제로 전사 구성원들에게 지금 접속해 보시라고 할 수도 없는 노릇이었습니다. 그래서 봇을 만들어보기로 했습니다. 사람과 비슷한 환경에서 비슷한 동작을 하는 봇을 돌려보면, 조금은 믿을 수 있게 될 것 같았어요.
처음에는 제 맥북에서 봇을 돌렸습니다. 수천 개를 동시에 연결하자 맥북의 네트워크가 먼저 버티지 못했어요. 게임 서버가 아니라 테스트를 보내는 제 쪽이 문제였던 겁니다. Wi-Fi 회선 하나에 수천 개 봇이 활동하니 당연한 일이었습니다.
봇을 맥북이 아닌 다른 곳에 두어야 했습니다. 배포플랫폼에서 Job이라는 걸 쓸 수 있다는 걸 알게 됐어요. 작업을 한 번 실행하고 끝내는 건데, 이걸로 봇을 사내 서버에서 돌릴 수 있었습니다. 제 맥북 회선이 아니라 각자 다른 서버에서 접속하게 되니 그제야 실제 상황에 가까워졌어요.
이렇게 만들어진 봇은 어떤 행동을 해야 할까요? 제가 기억하는 참가자들은 안내된 순서대로만 움직이지 않았습니다. 그래서 제가 행사에서 직접 겪거나 본 행동들을 봇이 모방하게 만들었습니다. 게임 중 전화가 오거나, 게임 도중 탭을 실수로 닫거나 이전 화면으로 스와이프 해버리고, 접속이 안 될 땐 답답해서 새로고침을 반복하고, 네트워크가 몇 초 끊기고, 접속이 안 되니 동시에 여러 탭에서 접속하는 상황들을 넣었습니다.

예상 참가 인원 두 배 수량의 봇을 실행했고, 앞서 언급한 상황 외에도 네트워크 지연을 랜덤하게 넣는다든지 여러 리전에서 접속시킨다든지 하는 것들을 더했습니다.
봇을 돌려놓고 로그를 보면서도 솔직히 이게 맞는 방법인지, 봇이 사람 대신 움직이는 걸 믿어도 되는 건지 잘 몰랐습니다. 계속 제가 만든 것들을 못 믿는 상황이 반복됐습니다. 그래도 하나는 확실했어요. 모든 활동 로그가 깨끗하진 않았습니다. 끊기는 봇도 있었고 80~90% 정도만 제대로 작동하는 것처럼 보였습니다. 전부 잘 됐다면 오히려 더 의심스러웠을 겁니다.
봇이 주어진 시나리오대로 움직인다 한들, 진짜 사람이 접속한 상황을 대신할 수 있는지는 모를 일입니다. 그저 제가 할 수 있는 게 이것뿐이었고, 여러 번 테스트 방식을 바꾸고, 봇을 3배, 4배로 더 넣어보며 변화하는 값들을 보고 믿을 수밖에 없었습니다. DevEx팀에서도 봇을 실행해 봤지만, 제가 만든 봇을 제가 정한 시나리오로 돌려보는 거였어요. 그럼에도 다 같이 돌려봤다는 걸로 스스로에 대한 믿음이 조금씩 커져갔습니다.
Redis 단일 구성에서 드러난 문제
봇 테스트를 끝내고 20분쯤 지났을 때, Redis 네트워크 사용량이 임계치를 넘었다는 경보가 떴습니다. 봇은 이미 종료됐는데 봇이 열어둔 연결이 계속 살아 있었고, 그 상태에서 새 배포를 올린 게 문제였어요. 기존 연결이 제대로 닫히지 않은 채 새 Pod가 붙으면서 Redis가 감당해야 할 양이 순간적으로 튄 겁니다.
DevEx팀과 상황을 재현해 보고 Redis 스펙을 올렸습니다. 같은 상황에서도 안정적으로 돌아가더라고요. 그리고 이 과정에서 Redis가 단일 구성이라 죽으면 백업 플랜이 없다는 것도 알게 됐습니다. 별도 AZ에 Replica를 두고, Redis 읽기가 실패해도 직전 값으로 계속 응답하도록 서버 메모리에 캐시를 뒀어요.
봇 테스트로 "이제 괜찮다"는 확신을 얻긴 어려웠지만, 이런 걸 미리 알게 된 것만으로 충분한 값을 했다고 생각합니다.

전광판 연출과 3,000개의 민트 피자
3막으로 확장된 게임
초기 기획은 ‘판 뒤집기’라는 단판 게임이었어요. 붉은 판을 터치하면 민트색으로 바뀌고, 모든 영역이 민트로 물들면 승리한다는 한 줄이 전부였습니다. 저는 요청받은 대로 게임을 만들고, 이 게임이 행사장에서 잘 굴러가기만 하면 된다고 생각했습니다.
그런데 함께 제작하던 BX 디자이너의 생각은 달랐고, 그 판단으로 단판 게임이 3개의 막으로 늘어났습니다. 구현해야 할 양이 3배 가까이 늘었지만 서버가 할 일은 크게 늘어나지 않았습니다. 앞에서 "결과만 보내 집계" 하는 방식으로 구현 방향을 좁혀둔 덕분입니다.
3막에서 참가자들은 각자의 폰에서 게임을 하며 민트 피자를 모읍니다. 게임이 끝나면 폰 화면에 자기가 모은 피자가 쌓여 있습니다. 그걸 손가락으로 위로 밀어 올리면 피자들이 줄줄이 따라 올라가며 전광판으로 날아가요. 빛을 잃고 까맣게 꺼져 있던 전광판이 참가자들이 보낸 피자로 다시 채워지는 게 이 게임의 마지막 장면이었습니다.
여기서 가장 고민했던 건 그 전광판에 피자를 몇 개나 놓을 것인가였습니다.
채워야 할 전광판 화면
전제가 하나 있었어요. 전광판은 반드시 꽉 채워져야 했습니다. 3막의 결말이 빛을 되찾는 장면이기도 했고, 애초에 ‘판을 다 민트로 물들이면 승리’라는 초기 기획이 여기까지 온 것이기도 했습니다. 절반만 채워진 채로 끝나면 우리가 이긴 게 아니라 어정쩡하게 실패한 게 됩니다. 무대 위에서 MC가 "우리가 해냈습니다!"라고 외치는데 화면이 반쯤 비어 있으면 안 되는 거죠.
처음엔 접속한 사람 수만큼 자리를 만들어두고, 게임에 성공하면 자기 자리가 채워지는 방식을 생각했어요. ‘내가 저 전광판 안에 있고, 저 화면을 채우는 데 기여했다’라는 소속감을 주고 싶었거든요.
그런데 이 방식은 매우 위험했습니다. 참가자가 접속만 하고 게임을 안 할 수도 있고, 하더라도 성공할지 알 수 없고, 그렇다고 실패가 없는 게임을 만들면 재미가 없고, 다 잘해도 결과 제출이 안 될 수 있습니다. 아니면 아예 접속을 못 할 수도 있습니다. 한 사람의 이탈이 화면에 그대로 하나의 구멍이 되는 구조인 거죠.
반대로 접속 인원보다 훨씬 적게 배치하는 것도 아니라고 생각했습니다. 고작 몇백 개만 채우면 되는 상황이라면 ‘일부만 성공해도 게임을 승리했다!’라는 시시한 이야기가 되어버리니까요.

결국 어느 쪽이든 문제는 하나였습니다. 참가자가 총 몇 개의 피자를 획득할지 예측할 수 없다는 것. 예측할 수 없으니 "이만큼 모으면 꽉 찬다"는 숫자를 정할 수가 없고, 숫자를 못 정하니 자리를 몇 개 만들지도 정할 수 없다는 난관에 봉착했습니다.
그래서 생각을 바꿔봤습니다. 몇 개가 모일지는 여전히 몰랐지만, 일단 화면에서 가장 잘 표현되는 숫자를 먼저 골랐습니다.
화면에서 먼저 정한 숫자
민트 피자가 보일 전광판은 16:9 비율입니다. 정방형의 민트 피자를 여기에 촘촘히 깔았을 때, 너무 커서 듬성듬성해 보이지도 않고 너무 작아서 점처럼 보이지도 않는 개수가 대략 3,000개였어요.
보일 수량을 정하긴 했지만, 전광판이 비어 보일 수 있는 문제는 그대로였습니다. 한 사람이 피자를 하나씩만 얻는다면 채워질 자리는 참가자 수를 절대 넘지 못하고, 이탈이 생기는 만큼 그대로 비어 보일 테니까요.
그래서 두 가지를 동시에 바꿨습니다. 한 사람이 피자를 여러 개 획득하게 하고, 한자리에 여러 개가 겹쳐 쌓이게 합니다. 자리를 늘리는 대신 같은 자리 위에 그대로 포개는 거죠.
그런데 겹쳐놓기만 해선 아무 소용이 없었어요. 같은 자리에 열 개가 있든 하나가 있든 똑같이 보이니까요. 어렵게 얻은 피자가 그냥 사라지는 셈입니다. 그래서 고안해낸 방법이 breathing입니다.
전광판에 등록된 피자 이미지는 투명도가 0%부터 100%까지 10% 포인트씩 올랐다가 다시 0%로 내려오기를 반복합니다. 이 모습이 숨을 쉬는 것처럼 보여서 breathing이라 이름 붙였습니다.
피자가 딱 하나만 등록된 자리도 언젠가는 100%가 되는 순간이 옵니다. 그 순간에 ‘여기 채워져 있다’는 게 확실히 보이는 거죠. 같은 자리에 새로 피자가 등록될 때엔 깜빡임의 시작점을 랜덤하게 부여했습니다. 이로써 기존에 있던 피자 이미지와 다른 흐름으로 변화하게 됩니다.
이 방식으로 같은 자리에 피자 이미지가 쌓일수록 완전히 비어 보이는 순간은 점점 줄어들게 됩니다. 피자가 하나뿐일 때는 투명도가 0인 구간이 주기마다 돌아오지만, 두 개만 되어도 둘 다 동시에 0일 때만 비어 보이게 됩니다. 피자가 쌓일수록 그 확률은 계속 낮아지고, 열 개쯤 쌓이면 그 자리는 사실상 계속 밝게 유지됩니다.
참가자가 몇 개를 모을지는 여전히 알 수 없지만, 화면이 비어 보일 일은 없어졌습니다.
행사 전날의 판단과 당일의 결과
리허설에서 드러난 문제
행사 전날, 현장에서 사전 리허설이 있었습니다.
운영 대행사의 송출용 랩톱에 웹브라우저를 띄워 준비해둔 전광판 페이지를 여는 방식이었습니다. 송출에는 아주 좋은 성능의 Windows 랩톱을 쓴다고 안내받았습니다. 저는 그 말을 그대로 믿었고 따로 확인하지 않았습니다.
현장에서 마주한 건 6~7년 된듯한 외장 그래픽카드가 없는 저사양 Windows 랩톱이었습니다. 제 맥북에서는 잘 보이던 화면이 그 랩톱에서는 전혀 다르게 보였습니다. 이미지의 마스킹 처리가 들뜬다거나, 에셋이 동시에 여럿 보일 때엔 화면이 매우 끊겨 보였습니다.
전광판 화면 속의 민트 피자는 WebGL로 그리고 있었습니다. 화면이 뜨긴 하는데, 저희가 준비한 그래픽을 충분히 그려내지 못했어요. 민트 피자 수천 개에 파티클까지 얹히니 체감상 10fps 이하로 뚝뚝 끊겼습니다.
랩톱의 성능 설정을 이것저것 확인해 봤지만 이렇다 할 개선 방법을 찾진 못했습니다. 오래된 내장 그래픽의 한계겠거니 생각했고, 그 자리에서 더 붙잡고 있을 시간도 없었습니다. 당장 3막 전체와 엔딩 신을 뒤집어엎어야 할 판이었습니다.
이때가 밤 8시였습니다.
하드코딩이라는 선택
그날 밤, 리허설 현장 구석에서 랩톱을 열었습니다.
우선 전광판 렌더링부터 갈아엎었습니다. 내장 GPU를 쓰다 보니 WebGL은 안 쓰는 게 나을 것 같아 Canvas 2D로 바꿨어요. 이 과정에서 몇 가지 파티클 효과는 완전히 걷어냈습니다. 화질도 조금 떨어졌어요. 피자 테두리에 도트 같은 게 보이는 것 같기도 했고요.
그다음은 엔딩이었습니다. 엔딩 화면은 배경 이미지 위에 캐릭터들이 움직이는 구성이었습니다. 캐릭터 하나하나가 크로마키 배경을 깐 WebM 영상이었고, 브라우저가 그 초록색을 실시간으로 지워가며 배경 위에 얹는 방식이었습니다. 많을 때는 서너 개가 동시에 돌아갔고요. 맥북에선 아무 문제가 없었지만 그 랩톱에서는 어림도 없었습니다. 결국 엔딩 화면을 맥북에서 재생하며 통째로 녹화해서 영상 파일로 만들어 재생시켰습니다.
그리고 3막의 민트 피자 3,000개가 보일 전광판 문제가 여전히 남아 있었습니다.
3막의 핵심은 민트 피자가 전광판에 뿌려지면서 화면을 채우는 연출입니다. 서버가 집계한 수량을 전광판은 그리기만 하면 됩니다. 그런데 그 랩톱이 그래픽을 제대로 그려내지 못했습니다. 내일 오후 1시면 사람들 앞에서 돌아가야 하는데.
대행사에 다른 노트북을 쓸 수 있냐고 물었습니다. 다른 노트북은 없었고요. 그럼 우리 맥북을 연결할 수 있냐고 물었습니다. 송출 안전상의 이슈로 안 된다고 했습니다. 제 손에 쥔 카드가 아무것도 없는 기분이었어요. 이제 와서 피자가 채워지는 연출 자체를 없앨 수도 없었습니다. 3막 게임의 결말부터 엔딩까지 이어지는 장면이니까요.
고심 끝에 결정했습니다. 뿌리는 피자의 수량을 실제 집계와 관계없는 하드코딩된 값으로 채우기로.
계산은 이랬습니다. 전광판 속 민트 피자 자리는 3,000개고 각 자리는 10단계의 breathing으로 표현되니, 대략 30,000개를 뿌리면 화면이 가득 채워질 겁니다. 3-1막이 끝나면 피자의 일부를, 3-2막이 끝나면 나머지를 다 채운다. 실제로 몇 개가 모였는지와는 아무 상관 없었습니다.
민트 피자가 뿌려지는 연출도 같이 손봤습니다. 원래는 화면 가장자리에서 파티클과 함께 피자가 튀어 올라 자기 자리로 날아가는 방식이었어요. 맥북에서는 수천, 수만 개가 날아가도 문제가 없었습니다. 그런데 그 랩톱에서는 이게 부담이었고, 어차피 실제 수량과 상관없는 값을 뿌리는 판에 모든 피자 연출을 그려낼 이유도 없었습니다. 화면이 적당히 화려해 보일 정도의 피자 수량만 튀어 오르게 하고, 나머지는 채워져야 할 자리에서 바로바로 겹쳐지도록 수정했습니다.
전광판에 보일 피자의 수량, 숨 쉬게 만든 breathing 효과. 굳이 손가락으로 밀어 올리게 만든 동작. 이것들 전부 "내가 저기에 일조했다"라는 감각 하나를 위해서였습니다. 그 연결 고리를 제 손으로 끊은 셈입니다. 마치 모든 참가자들에게 거짓말을 하는 기분이었습니다.
자정이 넘어서 수정을 마친 화면은 맥북에선 당연히 잘 돌아갔고, 송출용 랩톱에선 더 확인해 보기 어려웠습니다. 다음 날 행사 시작 전에도 MC 리허설과 다른 순서 리허설로 꽉 차 있었거든요. 잠깐 비는 시간에 화면이 뜨는지 정도만 확인할 수 있었습니다. 녹화한 FHD 영상이니, 잘 뜰 수밖에 없었습니다.
행사 당일
시간이 됐습니다. 운영석으로 내려가는 시간이 어찌 그리 길고 먹먹하던지요. 세상이 음소거가 된 기분이었습니다.
전날 급하게 바꾼 화면이 잘 뜰까? 아니, 그전에 모두 접속은 잘 될까? 만약 절반 이상 못 들어오면 어떡해야 할까? 별별 생각이 다 들었습니다.
몇 명이 접속했는지 실시간으로 볼 수 있도록, 접속했을 때와 에셋 다운로드가 끝났을 때, 팀 선택까지 마쳤을 때 각각 서버로 신호를 보내도록 해뒀습니다. 그 숫자가 운영 패널에 뜨게 됩니다.

MC의 게임 안내 멘트 이후 접속 QR이 뜬 전광판이 열렸습니다. 모든 참가자들이 동시에 스마트폰을 꺼내드는 그 장면을 잊을 수가 없습니다.
저는 운영 패널과 로그를 뚫어져라 보고 있었습니다. 접속자가 순식간에 500, 1,000명을 넘더니 1,500, 1,600, 1,700…
참석한 대부분의 인원이 집계되었다고 전달한 순간 운영 측으로부터 시작해도 되겠다는 사인이 떨어졌고, 떨리는 손으로 게임 시작 버튼을 눌렀습니다.
이후로도 긴장을 놓을 수 없었습니다. MC 멘트에 맞춰 여러 차례 게임 단계 변경을 직접 해줘야 했기 때문입니다.
1막 게임이 끝나고 타이머가 종료되던 순간, 이곳저곳에서 아쉬움의 탄성이 터져 나왔습니다. 그제야 긴장이 좀 풀렸어요.
"이제 안심해도 되겠다. 앞으로도 MC 멘트에 맞춰 게임 단계만 잘 바꾸면 아무 문제 없을 거야."
1막 게임의 팀별, 개인별 최종 랭킹도 잘 나왔고, 2막 영상까지도 잘 나왔습니다.
문제는 3-1막부터 시작됐습니다.
늦어지는 전광판
3-1막 게임 소개를 마친 후 게임 시작 버튼을 눌렀습니다. 그런데 전광판 화면이 바로 바뀌질 않았습니다.
1초, 2초, 3초… 10초가 지나도 전광판 화면은 3-1막 게임 소개에 멈춰있었습니다. 체육관을 가득 채운 게임 BGM, MC의 당황한 멘트, 저를 둘러싼 모든 것이 순식간에 얼어붙는 느낌이었습니다.
이때 참가자들은 뭘 하고 있지? 주변을 돌아봤습니다. 그런데 다들 이미 게임을 하고 있었어요. 전광판만 화면 전환이 늦었던 겁니다.
전광판이 10초 이상 지연됐어요. 이 여파로 참가자들은 게임이 끝났지만 전광판에선 여전히 게임이 진행 중으로 보였습니다. 강제로 전환시킬 방법이 없어 마냥 기다릴 수밖에 없던 세상에서 가장 길었던 시간이었습니다.
이후로 모든 장면이 매끄럽게 넘어가지 않았습니다. 점점 더 오랜 시간이 걸렸어요. 3-2막 게임 시작할 때에도 참가자들은 바로 게임을 시작했는데, 전광판만 점점 더 느리게 전환됐습니다.
모든 게임이 종료된 후엔 전광판의 모든 빈 공간이 민트 피자로 채워지게 됩니다. 이 장면은 1분 가까이 지연된 후에야 채워지기 시작했습니다. 송출용 랩톱은 Canvas 2D로 그리는 몇 개의 민트 피자도 버거워했습니다.
그 후로 이어질 엔딩 시퀀스는, 전날 밤 그렇게 녹화해둔 그 영상은, MC가 마무리 멘트를 다 마칠 때까지도 나오지 않았습니다.
제 첫 실시간 게임 데뷔는 그렇게 끝났습니다.
맺으며
나 자신을 믿게 된 순간을 찾아봤는데, 사실 없었습니다
글을 쓰면서 그토록 바라던 ‘내가 나를 믿게 된 순간’이 언제였나 돌아봤는데, 없더라고요.
만들어둔 서버가 봇 테스트를 버텼을 때도 아니었고, 서버 다운 없이 행사가 끝났던 날도 아니었습니다. 개발 시작부터 행사 종료까지 저는 계속 제 자신의 판단을 믿으려고 애쓰는 중이었고, 조금씩 나아지고 있었던 것이지 정말로 스스로를 믿고 있진 않았습니다.
대신 제 자신 외에 동료들을 믿었습니다. 이 정도면 서버는 괜찮을 것 같다는 DevEx팀이 준 확신. 게임 경험을 설계한 동료 BX 디자이너의 판단. 전사 행사 게임을 만들고 있다는 말 한마디에 더 묻지도 따지지도 않고 인프라 세팅을 도와준 스토리지서비스팀과 배포플랫폼팀 담당자분들에게서 뜨거운 지지를 느꼈습니다.
이 과정에서 믿을 수 있는 동료가 여러 명 생겼고, 그분들이 저 대신 저를 믿어준 덕분에 끝까지 밀어붙일 수 있었습니다.
어떻게 만들었냐고 물으신다면
저는 AI가 짜준 코드를 직접 타이핑해가며 수정하진 않고 그대로 배포했습니다. 그럼 모든 걸 AI가 만든 걸까요? 그건 전혀 아니라고 생각합니다.
개념과 작동 원리는 반드시 이해하고 넘어갔고, 코드를 쓰는 것 외에는 전부 제가 판단했습니다. WebSocket을 포기할지, 피자를 몇 개 그릴지, 행사 전날 밤엔 무엇을 버리고 무엇을 남길지.
그럼에도 여전히 알지 못하는 부분도 있습니다.
운영 패널에서 다음 게임으로 넘어갈 때마다 접속자가 점점 줄어드는 걸 봤습니다. 마지막 단계에선 초기 접속자의 절반 정도로 줄어들더라고요. 지루해서 나간 건지 네트워크가 끊긴 건지, 로그를 촘촘히 남기지 않아 지금도 진실은 모릅니다.
그래서, "이 게임을 다시 만들면 모든 참가자가 끝까지 게임을 즐길 수 있는가?"라는 질문을 받는다면 솔직히 지금도 잘 모르겠습니다. 하지만 "접속한 참가자들이 즐거운 경험을 했는가?"라는 질문이라면 동료 BX 디자이너의 경험 설계가 그 대답이 될 것이고, "더 많은 참가자도 버티겠는가?"라는 질문이라면 자기 일처럼 두 팔 벌려 나섰던 여러 부서의 수많은 담당자분들이 그 대답이 될 것 같습니다.
행사가 끝나고 며칠 뒤 행사 사진이 올라왔습니다. 이번엔 제가 찍지 않았죠. 포토팀 동료들이 촬영한 사진 속엔 게임을 하며 즐거워하는 사람들이 있었습니다. 서로의 화면을 보여주며 자랑하는 모습도 있었고요.
게임이 안 된 분들도 많았다는 것 역시 알고 있습니다. 카메라를 들고 있다가 참여해 보려니 접속이 안 되던 제 상황, 그게 싫어서 잘해보고 싶었는데 여전히 공백은 남았습니다. 예전의 저처럼 ‘큰 행사에서 하는 게임은 원래 안 돼’라고 넘겨버린 분도 있었을 겁니다.
현장 네트워크나 전광판 이슈는 아쉬웠지만, 게임을 즐긴 사람은 그 어느 때보다 많았다고 자부합니다. 그리고 다음에 또 만든다면, 그땐 조금 더 잘할 수 있겠죠!