멀티플레이 환경에서 동작하는 인터랙션 오브젝트 설계
4년 전에는 인터랙션 오브젝트를 하나로 통합하라고 권했습니다. 지금은 생각이 조금 달라졌습니다.
블로그의 접속 기록을 가끔 들여다보는데 몇 해 전에 써 둔 인터랙션 오브젝트 이야기가 요즘도 꾸준히 읽히고 있다는 것을 최근에 알았습니다. 그 글들에서 저는 인터랙션 오브젝트의 설계 요령과 실제 사용 사례를 제법 자세히 풀어 보려 했습니다. 다만 그때는 다니던 회사와의 계약을 깨지 않으려고 표현을 조심하느라 정작 중요한 대목을 에둘러 갈 수밖에 없었고 하고 싶은 이야기를 여러 편에 잘게 나눠 싣는 바람에 설계 요령과 주의사항을 한자리에서 함께 익히기도 어려웠습니다. 그렇게 흩어 둔 글이 지금까지 읽히는 것을 보니 이 주제는 한 번 제대로 손봐서 다시 정리해 둘 때가 되었다는 생각이 들었고 그래서 이 글을 씁니다.
언젠가 회의실에서 주니어님 한 분이 이런 질문을 했습니다. "인터랙션 오브젝트가 정확히 뭔가요?" 자리에 있던 시니어들 셋이 각자 답을 했는데 셋의 답이 미묘하게 달랐습니다. 한 명은 "플레이어가 상호작용할 수 있는 모든 것"이라고 했고 다른 한 명은 "월드에 배치되는 물체 가운데 몬스터가 아닌 것"이라고 했으며 마지막 한 명은 "여러 기능을 조립해 만드는 컴포넌트 덩어리"라고 했습니다. 셋 다 틀린 말은 아니었습니다. 그런데 셋 다 같은 것을 가리키고 있다고 보기도 어려웠습니다. 주니어님은 고개를 끄덕였지만 아마 더 헷갈렸을 겁니다. 그 장면을 보면서 책상 서랍 안에 굴러다니는 맥가이버칼을 떠올렸습니다.
맥가이버칼은 칼이면서 가위이고 드라이버이면서 병따개입니다. 하나만 들고 다니면 어지간한 상황은 넘길 수 있어 든든합니다. 그런데 정작 종이를 자르려고 그 안의 작은 가위를 펼쳐 보면 손가락이 들어가지 않아 영 불편하고 나사를 조이려고 드라이버를 꺼내 보면 길이가 짧아 힘이 제대로 들어가지 않습니다. 모든 것을 할 수 있지만 그 어느 것도 잘 하지는 못하는 도구입니다. 제가 한참 동안 인터랙션 오브젝트라고 부르며 설계해 온 물건이 바로 이 맥가이버칼을 닮아 가고 있었다는 것을 꽤 오랜 시간이 지나서야 깨달았습니다.
이 이야기를 처음 꺼낸 것은 4년쯤 전이었습니다. 그때 모바일 MMO에 자주 등장하는 가상의 인터랙션 오브젝트를 처음부터 끝까지 설계해 보는 글을 세 편에 걸쳐 썼습니다. '인터랙션 오브젝트 설계 (1)' '인터랙션 오브젝트 설계 (2)' '인터랙션 오브젝트 설계 (3)'입니다. 그 무렵 저는 디아블로 이모탈을 한참 플레이 하고 있었는데 필드를 돌아다니다 보면 뼈무더기가 나타나고 가까이 다가가 인터랙션 하면 뼈무더기가 무너지며 작은 보상을 토해냅니다. 이 뼈무더기를 예제로 삼아 인터랙션 오브젝트가 멀티플레이 환경에서 어떻게 동작해야 하는지를 차근차근 그려 나갔습니다.
세 편의 글에서 제가 다룬 내용은 지금 다시 봐도 골격이 크게 틀리지 않았습니다. 첫 글에서는 뼈무더기에 '무너지기 전'과 '무너진 후'라는 두 상태가 있고 각 상태마다 겉모양을 결정하는 애니메이션과 인터랙션 가능 여부가 따라붙는다는 것을 정리했습니다. 상태와 상태 사이를 잇는 전환상태가 있고 그 사이에 무너지는 애니메이션이 재생된다는 것도요. 둘째 글에서는 플레이어캐릭터가 일정 반경 안에 들어오면 인터랙션 버튼이 뜨고 버튼을 누르면 정해진 시간 동안 캐스팅이 진행되며 그 도중 피격당하면 캐스팅이 끊긴다는 인터랙션의 흐름을 다뤘습니다. 셋째 글에서는 무너진 후 상태에서 보상을 떨어뜨리고 일정 시간이 지나면 원래 상태로 돌아가는 마무리를 붙였습니다. 그리고 이 모든 것의 바닥에는 멀티플레이 환경에서 상태와 보상을 동기화하는 문제 클라이언트를 믿어서는 안 되고 서버가 거리와 권한을 검증해야 한다는 원칙이 깔려 있었습니다.
여기까지는 지금도 유효합니다. 사실 이 정도의 골격은 그보다 더 전에 제가 다른 회사에서 직접 설계해 문서로 남겨 둔 것이기도 합니다. 그 회사에서는 이 물건을 인터랙션 오브젝트가 아니라 필드오브젝트라고 불렀는데 알고 보니 외국계 개발사를 인수하면서 그쪽 개발 환경을 통째로 가져왔고 그들이 몬스터가 아닌 상호작용 대상을 필드오브젝트라고 부르던 흔적이 곳곳에 남아 있었기 때문입니다. 이름이야 무엇이든 가리키는 대상은 같습니다. 레벨에 배치되어 여러 상태를 가지고 플레이어의 인터랙션 조작에 따라 상태가 전환되며 그 결과로 보상을 주거나 길을 열거나 어떤 사건을 일으키는 비전투 물체입니다.
문제는 그다음입니다. 세 편의 시리즈를 끝낼 무렵 한 가지 결론으로 글을 마무리했습니다. 비슷비슷한 물체들을 매번 따로 만들지 말고 공통된 동작을 정규화해 하나의 확장 가능한 규칙으로 통합한 다음 데이터만 바꿔 가며 돌려 쓰자는 것이었습니다. 문도 상자도 채집물도 결국 다가가서 인터랙션 하면 상태가 변하고 무언가를 내놓는다는 점에서 똑같으니 이들을 한 가지 기능으로 묶어 두면 새 물체가 필요할 때마다 데이터 한 줄로 찍어낼 수 있다는 이야기였습니다. 이것이 당연한 방향이라고 생각했고 그 뒤로도 여러 프로젝트에서 흩어져 있던 물체들을 하나의 인터랙션 오브젝트 시스템으로 통합하는 일에 기꺼이 참여했습니다. 맥가이버칼을 한 자루 더 정교하게 깎는 일이라고 여겼던 셈입니다.
그 통합이 어떻게 어긋나기 시작했는지를 이야기하려면 먼저 정규화라는 말이 가진 매력을 짚어야 합니다. 던전 끝에 보물상자를 놓고 뚜껑을 열면 아이템이 나오게 만드는 일과 필드에 나무를 세워 두고 도끼질을 하면 목재가 나오게 만드는 일은 코드를 짜는 입장에서 보면 거의 같은 일입니다. 일정 거리 안에 들어가 인터랙션을 시작하고 캐스팅이 끝나면 상태가 바뀌고 무언가를 떨어뜨립니다. 보물상자는 한 번 열리면 그만이고 나무는 가진 목재가 떨어질 때까지 여러 번 벨 수 있다는 차이가 있을 뿐 속을 들여다보면 같은 동작입니다. 그러니 서로 다른 시점에 서로 다른 사람이 보물상자와 나무를 따로 만들어 둔 것을 보면 경험 있는 엔지니어라면 누구나 이 둘을 합치고 싶은 충동을 느낍니다. 저 역시 그 충동에 완전히 동의했고 여러 번 그 손을 들어 줬습니다.
그런데 이렇게 합쳐 놓고 나면 이상한 일이 벌어집니다. 보물상자에만 필요했던 기능 하나가 통합된 시스템 전체에 영향을 끼치기 시작합니다. 예를 들어 누군가 보물상자를 클릭하면 상자 앞 정해진 위치까지 걸어가 여는 애니메이션을 재생하도록 만들고 싶어 합니다. 보기에 그럴듯하니 좁은 시야로 그 기능을 보물상자에 붙입니다. 그러면 어떤 사람들은 이 기능이 보물상자와 비슷하게 생긴 나무나 문이나 신단에서도 당연히 동작할 거라고 기대합니다. 그래서 각 물체에 맞는 플레이어 애니메이션 에셋을 정성껏 제작해 두지만 정작 그 기능은 보물상자에만 들어가 있어 나머지에는 적용할 방법이 없다는 사실을 뒤늦게 깨닫고 실망합니다. 반대 방향의 사고도 일어납니다. 나무에만 쓰던 '인터랙션 가능 횟수' 같은 옵션을 누군가 실수로 보물상자에 입력하면 한 번만 열려야 할 보물상자가 여러 번 열리는 우스운 물건이 되어 버립니다. 보물상자가 처음부터 보물상자 전용으로만 만들어져 있었다면 결코 일어날 리 없는 실수입니다. 맥가이버칼의 가위로 손가락을 베는 것과 같습니다. 가위 전용 가위였다면 손잡이가 그렇게 생기지 않았을 겁니다.