포트폴리오 제작기 7 - MVVM을 쓰지 않기로 한 이유
WPF에서 매력을 느꼈던 MVVM을 Unity에 적용해보려 했지만, 현재 프로젝트에서는 MonoBehaviour와 Model을 직접 엮는 구조로 바꾸게 된 이유를 정리했습니다.
MVVM을 왜 쓰게 되었나
이전에 WPF 프로젝트를 진행하면서 MVVM의 매력에 꽤 깊게 빠졌었다. View, ViewModel, Model의 역할이 나뉘고, 바인딩을 통해 화면과 데이터가 자연스럽게 연결되는 구조가 좋았다. UI 코드는 화면 처리에 집중하고, 상태와 동작은 ViewModel에서 다룰 수 있다는 점이 마음에 들었다.
그래서 Unity 프로젝트에서도 같은 방식을 이용해보고 싶었다. 게임 프로젝트라고 해도 UI는 계속 필요하고, 상태를 화면에 보여주는 일도 많다. 인벤토리, 설정, 스탯, 장비, 상호작용 UI 같은 기능을 생각하면 MVVM을 적용했을 때 코드가 깔끔해질 것이라고 기대했다.
UI Toolkit을 먼저 생각했다
Unity에는 UI Toolkit이라는 비교적 새로운 UI 시스템이 있다. 분명 나온 지는 꽤 되었지만, 내가 익숙하게 사용해온 UGUI와 비교하면 아직 새롭게 느껴지는 쪽이다. UI Toolkit에는 MVVM에 가까운 방식으로 데이터를 바인딩할 수 있는 흐름이 있어서, 처음에는 이쪽을 사용해볼까 생각했다.
그래서 Unity 커뮤니티와 여러 글을 돌아다니며 사용 경험을 찾아봤다. 그중 가장 공감이 갔던 말은 성능에 대한 부분이었다. UI Toolkit을 사용했을 때 성능을 어디에서 소모하고 있는지, 어떤 부분을 어떻게 조절할 수 있는지 감을 잡기 어렵다는 이야기였다.
포트폴리오 프로젝트에서는 기능을 만드는 속도도 중요하지만, 문제가 생겼을 때 내가 직접 원인을 좁혀갈 수 있어야 한다. 익숙하지 않은 시스템에서 성능 문제나 동작 문제를 만나면, 기능 구현보다 도구 자체를 이해하는 데 시간을 더 많이 쓰게 될 수 있다. 그래서 UI Toolkit을 바로 선택하기에는 조금 부담이 있었다.
익숙한 UGUI로 가되, 바인딩은 코드로
결국 익숙한 Unity GUI, 즉 UGUI를 이용하기로 했다. 대신 WPF에서 좋았던 바인딩 느낌을 어느 정도 가져오고 싶어서, 에디터 기반 바인딩이 아니라 코드로 직접 바인딩을 작성하는 방식으로 진행했다.
처음에는 나쁘지 않아 보였다. 화면에 표시되는 값과 Model의 데이터를 연결하고, 값이 바뀌면 UI를 갱신하는 구조를 만들 수 있었다. View가 모든 것을 직접 처리하지 않고, 중간에서 상태를 정리하는 계층을 두면 확장성도 좋아질 것 같았다.
하지만 실제로 작성하다 보니 문제가 보이기 시작했다. 모든 정보에 대해 하나하나 바인딩 코드를 작성해야 했다. WPF나 바인딩 에디터가 자동으로 해주는 역할이 생각보다 강력했다는 것도 이때 더 크게 느꼈다. 자동 바인딩 없이 직접 한땀 한땀 코드로 작성하려 하니, 코드 양이 늘어나고 연결 흐름도 복잡해졌다.
한땀 한땀 바인딩의 문제
수동 바인딩은 처음에는 명확해 보인다. 어떤 UI가 어떤 데이터를 바라보는지 직접 코드로 적기 때문이다. 하지만 항목이 늘어나면 이야기가 달라진다.
텍스트 하나, 버튼 하나, 토글 하나, 슬라이더 하나마다 연결 코드가 생긴다. 값이 바뀔 때 UI를 갱신해야 하고, UI 입력이 들어올 때 Model을 갱신해야 한다. 여기에 초기화 순서, 이벤트 해제, 중복 갱신 같은 문제까지 들어오면 단순했던 구조가 금방 복잡해졌다.
작성하다가 순간순간 헷갈리는 경우도 있었다. View인 줄 알고 작업했는데 실제로는 ViewModel을 만지고 있거나, ViewModel이라고 생각했는데 Model 쪽 책임을 건드리고 있는 식이었다. 구조를 분리하려고 만든 계층인데, 오히려 지금 어느 계층에서 무엇을 해야 하는지 계속 확인해야 했다.
이 지점에서 원래 기대했던 장점이 흔들리기 시작했다. MVVM을 쓰면 코드가 깔끔해질 것이라고 생각했지만, 자동 바인딩이나 에디터 지원 없이 직접 구현하는 상황에서는 계층이 늘어난 만큼 판단해야 할 것도 같이 늘어났다.
MonoBehaviour와 Model을 바로 엮기로 했다
그래서 이번 프로젝트에서는 MVVM을 고집하지 않기로 했다. 대신 MonoBehaviour <-> Model 구성으로 바로 엮는 쪽을 선택했다.
Unity에서는 MonoBehaviour가 씬 오브젝트와 자연스럽게 연결된다. 버튼 클릭, 오브젝트 상태 변경, Transform 조작, 입력 처리 같은 작업은 대부분 Unity의 생명주기와 씬 구조 안에서 움직인다. 이런 상황에서는 중간 계층을 억지로 끼워 넣는 것보다, 해당 컴포넌트가 필요한 Model을 알고 직접 갱신하는 편이 더 단순했다.
이렇게 바꾸니 코드가 좀 더 깔끔해졌다. 무엇보다 헷갈리는 부분이 줄었다. 어떤 UI가 어떤 Model을 다루는지 바로 보이고, 필요 없는 바인딩 계층을 따라가며 확인할 일이 줄어들었다.
물론 이 방식도 아무렇게나 작성하면 금방 복잡해질 수 있다. 그래서 기준은 필요하다. MonoBehaviour는 Unity 오브젝트와 이벤트를 다루고, Model은 실제 데이터와 규칙을 가진다. 다만 지금 프로젝트에서는 그 사이에 항상 ViewModel을 두지는 않기로 했다.
지금 프로젝트에 맞는 선택
MVVM을 쓰지 않기로 한 것은 MVVM이 나쁘다는 뜻이 아니다. WPF에서는 여전히 매우 좋은 구조라고 생각한다. 다만 지금 프로젝트에서는 Unity 쪽에서 MVVM을 지원하는 흐름, 특히 UI Toolkit을 본격적으로 사용하지 않으면서 문제가 생겼다. UGUI 위에서 직접 바인딩을 구현하려다 보니, MVVM의 장점보다 수동 연결의 복잡함이 더 크게 느껴졌다.
그래서 MVVM은 이번 프로젝트에서 억지로 밀고 가기보다 다음 기회로 미루기로 했다.
결론적으로 지금은 MonoBehaviour <-> Model 구조를 기본으로 가져가기로 했다. 이번 프로젝트에서는 이 방식이 덜 헷갈리고, 빠르게 수정하기에도 더 맞다고 판단했다.
다음에 다른 프로젝트에서 UI Toolkit에 도전할 일이 생긴다면, 그때 MVVM을 다시 도입해볼 생각이다. 지금은 MVVM을 포기했다기보다, 현재 프로젝트에 맞는 구조를 선택한 것에 가깝다.