Maple Tech

메이플스토리 프로그래머가 말하는 콘텐츠 개발 A to Z

2022.01.07

메이플스토리에서는 유저들이 참여할 수 있는 다양한 전투 콘텐츠가 존재합니다. 그중에서도 다른 유저들과 힘을 모아 강력한 몬스터를 처치하는 보스 콘텐츠는 게임의 핵심이라고 해도 과언이 아닌데요.
이번 글에서는 보스 콘텐츠 개발을 담당하는 개발자의 업무 진행 사례를 통해 실무 협업 프로세스와 개발 과정, 기술적으로 고민했던 지점들을 소개해 드리고자 합니다. 

개발팀에서 협업하는 내부 프로세스 전반

일반적으로 기획자가 먼저 보스 콘텐츠를 구상하면서 스토리는 어떻게 이어 나갈 것인지, 공격 패턴은 어떻게 구성할 것인지 등 대략적인 아이데이션을 진행하는데요. 이 과정에서 개발자를 비롯한 조직 구성원 전체가 의견을 제시할 수 있는 채널이 있어 열정 넘치는 분들의 개성 있는 아이디어가 채택되기도 합니다. 이후 어느 정도 기획의 틀이 잡히면 개발 일정과 구현 방식, 가능성에 대한 회의를 진행합니다. 각 개발자의 실무 경험을 토대로 기획자에게 의견을 전달하면 이를 조율해 기획서를 수정하고, 개발 일정을 결정하게 됩니다.
다음으로는 개발 작업을 잘게 나누어 리스트업하고 각 개발자에게 할당합니다. 각자 맡은 일을 진행하면서 미처 생각 못 했던 이슈가 발견되면 이를 공유하면서 의사결정을 하고, 중간 작업물에 대해선 기획자에게 피드백을 받고 수정하는 것을 반복합니다. 이후 콘텐츠를 성공적으로 릴리즈하고 난 다음에는 유저들의 동향을 살피며 버그는 없는지, 직관적이지 못하여 불편한 점은 없는지 체크하고 유지, 보수하고 있습니다.

 실제 사례로 들여다보는 콘텐츠 개발 과정

보다 자세한 개발 과정을 소개하기 위해, 2021년 여름 새롭게 추가된 보스인 가디언 엔젤 슬라임의 사례를 얘기 드리려 합니다. 구체적으로는 구현 난이도가 높다고 판단했던 지면을 따라 흐르는 물줄기 패턴 가디언 웨이브 개발 과정을 위주로 말이죠.
가장 먼저 했던 고민은 어떻게 하면 가디언 웨이브가 물줄기가 흐르는 듯한 모습의 연출을 할 수 있을지에 대한 것이었습니다. 가디언 웨이브의 위치에 따라서 가로로 길게 늘어져 있거나, ㄱ 모양으로 꺾여 있는 모습 등 다양한 형태가 가능했기 때문입니다. 그렇기에 가디언 웨이브를 하나의 객체로 보기는 하되 내부적으로 조각을 잘게 나누어 따로 관리할 필요가 있다고 판단했습니다.

그 다음 가디언 웨이브 조각의 움직임을 구현하기 위해 상태를 좌측으로 이동, 우측으로 이동, 자유낙하, 상승 네 가지로 구분했습니다. 가디언 웨이브가 상승하는 경우는 기획적으로 존재하지 않지만 상태만 변경한다면 하늘로 솟구치는 연출 또한 가능하도록 유연성 있게 작업했는데요. 참고로 메이플스토리의 월드 매트릭스는 DirectX의 왼손 좌표계를 사용하므로 유저가 보는 화면을 기준으로 우측이 +x축, 자유낙하를 하는 방향이 +y축입니다.
가디언 웨이브는 정지해 있는 경우가 없기 때문에 항상 어딘가로는 움직여야 하고, 방향에 대한 상태를 정의했습니다. 속력의 경우 게임 기획자가 레벨 디자인을 편리하게 할 수 있도록 데이터 파일에서 읽어 들인 값을 사용하도록 했고, 런타임 환경에서 언제든지 변경할 수 있도록 인터페이스를 제공했습니다. 이렇게 현재 상태에 따라 직선으로 움직이는 것까지는 완료했지만 그 다음에는 방향 전환이라는 큰 산이 더 있었습니다.
메이플스토리는 오랜 시간 서비스된 게임이기에 최신 상용화 엔진에 비하면 월드 내 게임 오브젝트에게 통용되는 물리법칙의 퀄리티가 다소 떨어지는 것은 부정할 수 없습니다. 이에 기존 코드를 개선하거나 새로 필요한 부분은 확정하여 구현해야 합니다. 그래서 발판 지면을 흐르다가 발판이 끊어졌을 때 낙하하는 등의 움직임을 하나하나 경우를 따져가며 구현했는데요, 먼저 가디언 웨이브 조각이 방향을 전환해야 하는 경우를 모두 따져 보았습니다.
  • 좌측 또는 우측으로 이동하던 중 발판이 끊어졌을 때
  • 수직으로 자유낙하 하던 중 발판에 충돌했을 때
  • 맵 가장자리에 충돌했을 때
  • 크리스탈에 충돌했을 때
  • 홀리 게이트에 충돌했을 때
 위 경우에서만 가디언 웨이브의 방향 전환이 가능하기 때문에 매 Update Tick마다 현재 상태에 따라 위치를 변경한 뒤 위의 경우에 해당하는지 확인하고, 해당된다면 상태를 변경했는데요. 이렇게 방향 전환을 하고 났을 때 각 가디언 웨이브 조각이 살짝 떨어지는 현상이 발생했습니다.
그 이유는 매 Update Tick마다 웨이브는 지정된 속력만큼의 거리를 이동해야 하지만 방향 전환 시 그것을 고려하지 않았었기 때문인데요, 이를 해결하기 위해 전체 이동 거리에서 이동 시작 위치와 방향 전환 시작 위치의 차이를 뺀 거리만큼 다음 진행 방향에 맞게 보정하는 작업을 했습니다. 각 웨이브 조각의 이전 조각, 다음 조각과의 거리는 같이 진행 방향일 때 기준으로 항상 유지되어야 하기 때문입니다.

메이플스토리처럼 실시간으로 다수의 유저가 빠르게 움직이며 고난도의 조작을 요구하는 게임은 1프레임의 오차도 용납되지 않는 경우가 많습니다. 따라서 지금까지 이야기했던 가디언 웨이브의 행동들을 유저가 인지할 수 있는 단위의 시퀀스로 세분화할 필요가 있었고, 그것을 만족시키기 위해 함수별로 모듈화를 진행하여 순서에 맞게 수행하도록 했습니다. 물론 이와 같이 게임의 조작감에 영향을 미치는 부분은 기획자와 끊임없는 커뮤니케이션을 통해서 결정되어야 합니다.
  • 홀리 게이트 충돌
  • 각 가디언 웨이브 조각의 상태에 따른 위치 이동
  • 무적 상태, 탈출 판정 및 휩쓸리는 중인 캐릭터 위치 이동
  • 캐릭터 충돌 판정 체크
  • 가디언 웨이브 방향 전환
  • 가디언 웨이브 렌더링

오차를 줄여 나가기 위한 고민들

다음으로 생각해야 할 문제는 모든 유저에게 가디언 웨이브가 동일한 위치에 보이고 동일한 판정을 내릴 수 있도록 최대한 오차를 줄이는 것이었습니다. 오차가 생기는 주된 원인으로는 네트워크 지연이나 클라이언트 프리징 등이 있는데요 (때때로 복잡하고 오래된 코드 속에서 개발자의 필연적인 실수로 인하여 발생하기도 합니다).
메이플스토리의 경우 캐릭터 위치 동기화를 위해 각 클라이언트가 자신의 이동 정보를 서버에게 보내고, 서버는 이를 검증한 뒤 모든 클라이언트에게 브로드캐스트를 하는 방식을 취합니다. 아마 대부분의 액션 게임들이 조작감을 위해 ‘일단 나는 움직이고 본다’는 식의 동기화 방식을 사용할 것입니다. 이러한 캐릭터 위치 동기화 방법이 네트워크 지연 등의 문제가 발생해도 괜찮은 이유는 PVP 요소가 전혀 없고, 게임 자체적인 특성상 함께 협동하는 유저의 위치에 대한 정확도가 크게 높지 않아도 되기 때문입니다. 따라서 동기화의 주체가 어떤 문제로 인해 동기화가 어긋났을 때 다른 유저들은 해당 유저가 멈춰 있거나 약간 어색하게 움직이는 모습으로 보이고, 문제가 생긴 유저는 문제가 생긴 동안에 있었던 사건들을 수행하는 과정에서 다소 불리한 판정을 받을 뿐입니다.
하지만 가디언 웨이브의 경우에는 약간 상황이 다른데요. 특정 유저가 가디언 웨이브의 움직임 동기화를 담당하는 호스팅 방식을 사용할 경우, 해당 유저에게 문제가 생겼을 때 게스트 유저는 멀쩡한 환경에서 비정상적으로 움직이는 가디언 웨이브를 보게 될 것이고 불합리한 판정이 내려졌다고 생각할 수밖에 없을 것입니다. 모든 유저가 가디언 웨이브의 위치 동기화를 담당하고, 특정 클라이언트에게 문제가 생겼을 경우 다수의 데이터를 따라가도록 보정하거나 보간하는 P2P 방식을 사용할 수도 있지만 개발 비용과 유지보수 비용이 높다고 판단해, 이벤트 동기화 방법을 선택했습니다.
서버가 가디언 웨이브를 생성하라는 신호를 보내면 각 클라이언트는 해당 응답을 처리하여 객체를 생성합니다. 각 클라이언트는 지정된 움직임 규칙에 따라 가디언 웨이브를 이동시키고, 홀리 게이트, 크리스탈, 유저 등 상호작용이 가능한 게임 오브젝트들에 대한 판정을 내립니다. 네트워크 지연이 발생한다고 하더라도 가디언 웨이브의 움직임 자체는 클라이언트 자체적으로 처리하기 때문에 전혀 영향을 받지 않으며, 클라이언트 프리징이 걸렸다고 하더라도 프리징이 풀렸을 때 밀린 프레임을 처리하는 과정에서 올바른 판정을 내릴 수 있기 때문에 전혀 문제가 되지 않습니다. 이렇게 하여 가디언 웨이브의 움직임은 클라이언트가 알아서 시뮬레이션 하도록 완전히 위임하는 것입니다. 물리 법칙이 적용된 움직임을 계산하는 것은 자원을 크게 사용하기 때문에 서버의 부하를 줄이는 데에도 도움이 될 수 있습니다.

예상되는 문제를 끊임없이 해결하기

이러한 과정에서 문제가 되는 부분은 프리징이 걸린 상태에서 서버가 이벤트 트리거 패킷을 보냈을 경우입니다. 메이플스토리도 결국 윈도우 응용 프로그램이기 때문에 메시지 큐에 쌓인 데이터를 순차적으로 처리합니다. 그렇기에 프리징 걸린 상태에서 처리되지 못한 메시지가 큐에 쌓여가고 있는 중간에 패킷이 도착했다고 하더라도 먼저 들어온 메시지를 처리하고 나서 패킷 처리를 하는 경우가 생길 수 있습니다. 밀린 프레임을 건너뛰는 방법으로 이런 문제를 회피할 수 있지만 얻는 이점에 비해 개발 및 QA 자원이 기하급수적으로 증가하기 때문에 고려대상에서 제외했습니다.
그렇다면 이런 일이 발생했을 경우 어떤 상황이 벌어질까요? 서버의 이벤트 트리거 동작 시간과 큰 차이가 나지 않는 대부분의 클라이언트는 모두 동일한 화면을 바라보고 있겠지만, 운 나쁘게도 위와 같은 상황이 발생한 클라이언트의 경우에는 몇 프레임 정도 늦은 시점의 화면을 보고 있을 것입니다. 안타깝게도 동기화가 깨진 상태입니다. 다행이게도 이런 상황은 거의 발생하지 않습니다. 확률의 문제이지만 직관적으로 충분히 예측 가능한 정도이며, 업데이트 이후 유저 동향을 봐도 거의 일어날까 말까 한 일입니다. 하지만 그럼에도 불구하고 누군가는 겪을 수밖에 없는 버그인 것은 명백합니다. 이런 버그가 발생했을 경우 또다른 이벤트에 의해 다시 동기화를 맞춰주는 방식을 사용했습니다.
크리스탈에 충돌하는 경우, 발판에 충돌하는 경우, 홀리 게이트에 충돌하는 경우 등 랜드마크가 되는 특정 이벤트를 기점으로 만약 위치가 크게 어긋나는 클라이언트가 있다면 위치를 보정하여 주는 것입니다. 이렇게 함으로써 잠깐 동안의 어색함이 있었다가 곧 정상화되기 때문에 자연스럽게, 마치 없었던 일처럼 넘어갈 수 있게 됩니다. 서버가 너무 클라이언트에게 개입을 많이 할 경우 연출적으로 어색함이 있을 수도 있고 처리하는 과정에서 사이드 이펙트가 발생할 수 있기 때문에 실제로 위치 보정은 홀리 게이트 진입 완료 시점에만 적용했습니다.
이처럼 최근 보스 콘텐츠 사례를 중심으로 개발 과정을 소개해 드렸는데요. 개인적으로는 처음 만들어 보는 보스 몬스터여서 완벽하게 만들기 위해 많은 고민을 했습니다. 해당 콘텐츠를 개발하면서 문제 상황에 대비하는 것도 중요하지만, 게임의 조작감을 향상시키고 성능의 안정성을 가져가는 것이 때때로 더 이득을 볼 수 있다는 것을 경험했는데요. 제 업무 경험과 고민들이 메이플스토리 조직에 합류를 고민하는 분들에게 조금이나마 도움이 되었기를 바라며, 저와 함께 메이플 월드의 전투 콘텐츠를 만들어 나갈 동료를 기다리면서 글을 마치겠습니다.
 
7년차 콘텐츠개발 프로그래머 조병우