최고의 로그라이크는 단순히 플레이어를 죽이는 데 그치지 않습니다. 플레이어가 죽고 싶게 만듭니다.
이상하게 들릴 수도 있겠지만, 자정쯤에 플레이를 망치고 12시 3분에 다시 시작해 본 사람이라면 그 기분을 알 것입니다. 죽음은 쓰라립니다. 체력은 0으로 떨어지고, 화면은 실패의 흔적으로 가득 찹니다. 하지만 당신의 손가락은 이미 재생 버튼 위를 맴돌고 있습니다. 지난 20분 동안의 경험이 너무나 중요했기에, 게임을 그 상태로 끝낼 수 없는 것입니다.
이것이 바로 '한 판만 더' 루프이며, 이는 결코 우연이 아닙니다. 의도된 설계입니다. 만약 당신이 로그라이크나 영구적 죽음(permadeath)이 있는 게임을 만들고 있다면, 당신의 모든 임무는 상충하는 두 힘 사이의 균형을 맞추는 것입니다. 플레이어는 긴장감을 느낄 만큼 충분히 패배해야 하며, 희망을 품을 수 있을 만큼은 무언가를 유지해야 합니다.
실패의 화폐
최근 프로젝트인 Neon Survivor에서 저는 바로 그 긴장감을 구현하고 싶었습니다. 플레이어가 죽으면, 플레이 중 모은 모든 것이 사라지지만 단 한 가지, 골드만은 남습니다. 이 골드는 자동으로 저장됩니다. 메뉴로 돌아가면 플레이어는 이 골드를 사용하여 영구 업그레이드를 구매합니다. 그리고 이전보다 조금 더 강해진 상태로 다시 게임에 뛰어듭니다.
이 단순한 루프가 게임 전체를 지탱합니다. 이 루프가 없다면 죽음은 마침표가 됩니다. 지난 플레이에서 얻은 것이 아무것도 없기에 플레이어는 게임을 떠납니다. 하지만 이 루프가 있다면 죽음은 쉼표가 됩니다. 플레이는 일종의 파밍 여정이 됩니다. 상실감은 크지만, 그 대가로 내일을 위한 준비를 마친 셈이니까요.
비결은 간단합니다. 모든 것을 잃더라도 무언가는 남겨두는 것입니다. 관건은 도전 과제를 무너뜨리지 않으면서도, 남겨진 것이 의미 있게 만드는 것입니다. 업그레이드가 너무 약하면 플레이어는 관심을 끊게 됩니다. 너무 강력하면 게임이 알아서 돌아가 버립니다. 어느 쪽이든 루프는 붕괴합니다.
두 개의 시계, 혼란 제로
이를 제대로 구축하기 위해 저는 두 개의 별도 타임라인을 관리해야 했습니다.
Run Clock은 플레이를 누를 때마다 초기화됩니다. 체력, 현재 점수, 적 웨이브 수, 그리고 세션 중에 획득한 모든 임시 파워업을 추적합니다. 캐릭터가 죽으면 이 시계는 0으로 되돌아갑니다.
Meta Clock은 절대 초기화되지 않습니다. 모든 시도에서 획득한 총 골드, 역대 최고 웨이브, 그리고 구매한 모든 영구 업그레이드를 보유합니다. 이 시계는 브라우저를 아무리 새로고침해도 계속 흘러갑니다.
이 두 가지를 뒤섞으면 추적하기 어렵고 수정하기 고통스러운 버그가 발생합니다. 저는 개발자가 일상적인 씬 리셋 과정에서 정리 함수가 잘못된 데이터 저장소에 접근하는 바람에 플레이어의 진행 상황을 실수로 삭제하는 것을 본 적이 있습니다. Meta Clock 데이터가 증발해 버리는 것입니다. 플레이어는 골드 0, 업그레이드 0의 상태로 돌아갑니다. 그 시점에서 개발자와 플레이어 사이의 관계는 깨집니다. 그들은 새로운 게임을 시작하는 것이 아니라, 새로운 원한을 품게 되는 것입니다.
시계를 분리하는 것은 단순한 스타일의 선택이 아닙니다. 그것은 생존 전략입니다.
Phaser v4가 분리 처리를 다루는 방식
저는 Neon Survivor를 Phaser v4로 제작했으며, 이 프레임워크는 이 문제를 해결하기 위한 두 가지 특정 도구를 제공합니다.
Registry는 현재 세션을 위한 라이브 데이터를 메모리에 보유합니다. 빠르고 단순합니다. 하지만 플레이어가 페이지를 새로고침하는 순간 데이터는 증발합니다.
LocalStorage는 브라우저 자체에 데이터를 저장합니다. 탭을 닫거나 브라우저를 재시작해도, 심지어 정전이 되어도 데이터가 유지됩니다. 하지만 속도가 더 느리고 신뢰성이 떨어집니다. 브라우저는 저장 용량이 가득 차면 이를 차단하거나, 속도를 제한하거나, 삭제할 수도 있습니다.
저의 설계 원칙은 엄격했습니다. Registry는 게임 플레이 중 유일한 진실의 원천(source of truth)입니다. 게임은 Registry를 읽고, 쓰고, 전적으로 신뢰합니다. LocalStorage는 공동 저자(co-author) 역할을 하지 않습니다. 거울 역할을 할 뿐입니다.
흐름은 다음과 같습니다. 게임은 업그레이드 구매 내역을 Registry에 기록합니다. 단일 매니저 클래스가 Registry를 감시합니다. 적절한 시점에 해당 매니저는 Registry 데이터를 LocalStorage에 미러링합니다. 브라우저가 쓰기를 차단하더라도 게임은 끊기지 않습니다. 저장에 실패하더라도 현재 세션은 완벽하게 실행됩니다. 플레이어가 정확히 그 찰나의 순간에 탭을 닫는 경우에만 진행 상황을 잃을 수 있지만, 세션 자체가 충돌하는 일은 결코 없습니다.
이 패턴은 미묘한 재앙을 방지합니다. 모든 시스템이 LocalStorage에 직접 쓰도록 허용하면 취약한 API에 의존하게 됩니다. 개인정보 보호 설정이 매우 높거나 저장 공간이 부족한 기기를 사용하는 플레이어는, 백그라운드 함수가 통계 데이터를 저장하려 시도하는 과정에서 전투 중 게임이 느려지거나 멈추는 현상을 겪을 수 있습니다. Registry를 유일한 진실의 원천으로 만듦으로써, 액션은 빠르게 유지하고 리스크는 제한할 수 있습니다.
코드가 숨 쉬게 하라
또한 시스템 간의 결합도를 낮추기 위해(decouple) 이벤트를 사용했습니다. 런이 종료될 때, GameScene은 스스로의 장례식을 치르지 않습니다. 저장 함수를 호출하지도, 저장 유틸리티를 임포트하지도 않습니다. 그저 관련 데이터를 페이로드(payload)에 담아 "run-ended" 이벤트를 발생시킬 뿐입니다.
별도의 리스너가 데이터 관리를 담당합니다. 리스너는 이벤트를 수신하고, Meta Clock을 업데이트하며, 매니저에게 새로운 합계 수치를 LocalStorage에 반영하도록 지시합니다.
이러한 분리는 즉각적인 효과를 발휘합니다. 저장 시스템을 건드리지 않고도 GameScene 전체를 다시 작성하거나, 플레이어 캐릭터를 교체하거나, 카메라 각도를 변경하거나, 심지어 장르를 서바이벌에서 탄막 슈팅으로 바꿀 수도 있습니다. 시스템들은 서로 독립적입니다. 직접적인 함수 호출이 아닌 이벤트를 통해 소통합니다. 이는 병합 충돌과 버그가 줄어든다는 것을 의미하며, 6개월 뒤에도 코드베이스가 스파게티처럼 엉키지 않게 해줍니다.
게임의 판도를 바꾸는 업그레이드
기술적 근간이 탄탄하더라도 보상이 마치 스프레드시트의 숫자처럼 느껴진다면 아무런 소용이 없습니다. 저는 업그레이드가 실제 플레이 시 어떤 느낌을 주는지에 많은 시간을 투자했습니다.
어떤 업그레이드는 안전합니다. 이동 속도 증가, 추가 체력, 재장전 속도 향상 같은 것들입니다. 이런 업그레이드는 플레이어에게 실수를 만회할 여유를 줍니다. 안정감을 주며, 게임의 규칙을 바꾸지 않으면서 난이도를 낮춰줍니다.
반면, 규칙을 완전히 새로 쓰는 업그레이드도 있습니다. Neon Survivor에서는 "Piercing Rounds(관통 탄환)"를 추가했습니다. 이 업그레이드 전에는 총알이 첫 번째 적에게 부딪히면 멈췄습니다. 하지만 업그레이드 후에는 적을 관통하며, 단 한 발로 적의 줄 전체를 쓸어버릴 수도 있습니다.
그 차이는 극적입니다. 속도와 체력은 더 오래 살아남게 해줄 뿐이지만, Piercing Rounds는 플레이어의 위치 선정 방식을 바꿉니다. 적들을 일렬로 세우기 시작합니다. 가장자리에서 카이팅만 하던 방식에서 벗어나 중앙을 돌파하기 시작합니다. 게임의 의사 결정 폭이 확장되는 것입니다.
좋은 성장 시스템은 단순히 수치를 높이는 것이 아니라 플레이어의 결정을 바꿔야 합니다. 모든 업그레이드가 단순한 퍼센트 수치 상승에 불과하다면, 플레이어는 설명을 읽지 않게 됩니다. 그냥 클릭하고, 업그레이드하고, 잊어버립니다. 하지만 업그레이드가 전략을 다시 생각하게 만든다면, 플레이어는 그것을 기억합니다. 사람들과 이야기하고, 게임의 판도를 또 어떻게 뒤집을 수 있을지 확인하기 위해 다시 돌아옵니다.
진정한 결실
"한 판만 더" 루프는 단일 시스템이 아닙니다. 그것은 깔끔한 아키텍처와 의미 있는 보상을 바탕으로 구축된, 상실과 획득 사이의 관계입니다.
두 개의 별개 타임라인을 구축하고, Meta Clock을 플레이어의 신뢰를 담고 있는 것처럼 소중히 보호하십시오. 실제로 그렇기 때문입니다. 엔진의 도구를 사용하여 라이브 데이터는 빠르게, 저장 데이터는 안전하게 유지하십시오. 두려움 없이 반복적인 개선을 할 수 있도록 씬(scene)을 저장소로부터 분리하십시오. 그리고 업그레이드를 설계할 때는, 그것이 플레이어에게 더 많은 시간을 주는 것인지, 아니면 더 흥미로운 선택지를 주는 것인지 자문해 보십시오.
이것을 제대로 해낸다면, 플레이어는 단순히 죽음을 견디는 데 그치지 않을 것입니다. 오히려 죽음을 갈망하게 될 것입니다. 매 판은 다음 판을 위한 밑거름이 됩니다. 게임은 단순한 재시작의 연속이 아니라, 하나의 지속적인 상승 과정이 됩니다.
