Unity - '한 우산 아래'

맵툴 제작기 3 - 2000줄 넘은 Unity Editor Tool 코드 정리하기

망고와플 2026. 4. 8. 17:59

partial class로 나누고, 안 쓰는 경로를 걷어내기까지

  • 이번 글은 Unity Editor 툴 코드가 커지면서 발생한 유지보수 문제를 partial class와 역할 분리로 어떻게 정리했는지를 기록한 글이다.

맵툴을 처음 만들 때는 MapToolWindow.cs 하나에 전부 넣고 시작했다.

초기 단계에서는 그게 가장 빨랐기 때문이다. 기능도 아직 단순했고, 뭔가 하나를 추가할 때마다 파일을 나누는 것보다 그냥 한곳에 몰아두는 편이 훨씬 손에 잘 잡혔다.

하지만 기능이 늘어나면서 상황이 바뀌었다. Prefab 배치, Grid Snap, Height Level, preview mesh, Surface Snap, Neighbor Snap, Face Anchor, MeshCollider 변환, debug draw, material preset까지 들어가다 보니 한 파일 안에 UI, 입력, 수학, 디버그, 유틸리티가 전부 섞이기 시작했다.

이쯤 되니 더 이상 빠른 개발이 아니라, 어디를 열어야 하는지부터 찾는 시간이 늘어났다.

특히 에디터 툴은 일반 게임플레이 코드보다 더 쉽게 뭉개진다.

입력은 SceneView 이벤트 안에 있고, preview는 에디터 전용 인스턴스를 따로 관리해야 하고, 배치 로직은 물리 hit와 스냅 계산을 섞어 다룬다. 디버그 드로잉까지 한 파일에 얹히면, 기능 하나를 고치려고 해도 관련 없는 로직까지 같이 보게 된다.

실제로 맵툴이 커질수록, C++에서 하나의 클래스에 입력, 계산, 렌더, 디버그를 전부 때려넣은 것과 비슷한 느낌이 되어갔다.

그래서 처음에는 helper class까지 바로 빼기보다는, 비교적 안전한 partial class 방식부터 선택했다. 기능을 바꾸지 않고 파일만 나눌 수 있기 때문이다.

즉 구조는 정리하되, 동작까지 한 번에 크게 흔들지는 않는 방향이었다.

현재 구조는 다음처럼 나뉘어 있다.

파일 현재 역할
MapToolWindow.cs 필드, 메뉴, OnGUI, 프리셋/배치 UI
MapToolWindow.Scene.cs SceneView 입력, 단축키, preview 갱신 루프
MapToolWindow.Placement.cs 실제 배치 위치 계산, support surface 선정, prefab 배치 확정
MapToolWindow.Snap.cs Neighbor Snap 진입점과 Face / Edge / Vertex 분기
MapToolWindow.Snap.Helpers.cs projection, assist delta, auto align, axis fallback 같은 스냅 보조 계산
MapToolWindow.FaceAnchor.cs Face Anchor 메인 흐름, support face 재사용, front-most anchor hit 선택
MapToolWindow.FaceAnchor.Geometry.cs coplanar face 수집, face polygon 정렬, preview contact face 계산
MapToolWindow.Debug.cs wireframe, hit collider, anchor face, axis/line 디버그 표시
MapToolWindow.Preview.cs preview 인스턴스 생성/갱신, preview material 관리
MapToolWindow.Actions.cs material 적용, MeshCollider 변환, 선택 삭제, root/prefab 보조 유틸리티
MapToolWindow.Geometry.cs bounds corner, point cloud, 공통 geometry helper

현재 줄 수 기준으로 보면 구조가 얼마나 갈라졌는지도 바로 보인다.

 

파일  줄 수
MapToolWindow.cs 288
MapToolWindow.Scene.cs 249
MapToolWindow.Placement.cs 309
MapToolWindow.Snap.cs 151
MapToolWindow.Snap.Helpers.cs 314
MapToolWindow.FaceAnchor.cs 227
MapToolWindow.FaceAnchor.Geometry.cs 432
MapToolWindow.Debug.cs 248
MapToolWindow.Preview.cs 208
MapToolWindow.Actions.cs 203
MapToolWindow.Geometry.cs 122

MapToolWindow.FaceAnchor.Geometry.cs와 MapToolWindow.Snap.Helpers.cs는 여전히 수학 비중이 높은 편이지만, 적어도 지금은 UI와 디버그, 배치 처리와 한 파일에 뒤섞여 있지 않다.

즉 완벽하게 작고 예쁜 구조는 아니더라도, 문제를 찾을 때 열어야 할 파일이 분명해졌다는 점에서 이미 큰 차이가 났다.

예를 들어:

  • preview material이 이상하면 Preview 쪽을 본다
  • 배치 위치가 어긋나면 Placement를 본다
  • face가 왜 그렇게 잡히는지 궁금하면 FaceAnchor와 FaceAnchor.Geometry를 본다
  • 스냅 각도나 delta 계산이 의심되면 Snap과 Snap.Helpers를 본다
  • 디버그 라인과 wireframe은 Debug만 보면 된다

예전에는 스냅 하나를 고치려고 해도 같은 파일 안에서 UI, 디버그, 미리보기, 수학 로직을 전부 같이 뒤적여야 했다.

지금은 적어도 “문제가 어느 층위에 있는가”부터 분리해서 생각할 수 있다.

또 한 가지 좋았던 점은, 파일을 나누는 과정에서 죽은 코드가 생각보다 많이 보였다는 것이다.

초기에는 Face, FaceExact, Edge, Vertex를 전부 다르게 굴려보려고 했다가, 나중에는 실제로 같은 경로를 타거나 한 번 이름만 거쳐가는 wrapper 메서드만 남아 있는 경우가 생겼다.

한 파일에 다 들어 있을 때는 이런 것들이 잘 눈에 안 띄는데, 부분별로 쪼개 놓으니 어느 분기가 실질적으로 안 쓰이는지 훨씬 잘 보였다.

예를 들면:

  • 실질적으로 같은 경로를 타던 enum 옵션
  • 다른 메서드를 한 번 감싸기만 하던 wrapper
  • 이미 더 이상 쓰지 않는 예전 Face 스냅 경로
  • 디버그용으로만 남아 있던 오래된 상태값

같은 것들은 이번 정리 과정에서 먼저 걷어낼 수 있었다.

이번에 같이 정리한 또 하나는 주석이었다.

예전에는 일단 돌아가게 만드는 것이 우선이라서, 복잡한 계산도 이유를 남기지 않고 지나가는 부분이 많았다.

지금은 최소한 아래와 같은 지점에는 의도를 남기도록 했다.

  • 왜 MapRoot를 두는지
  • 왜 primitive collider를 MeshCollider로 바꾸는지
  • 왜 point cloud를 따로 수집하는지
  • 왜 triangle 하나가 아니라 coplanar face를 묶는지
  • 왜 Face와 Edge를 다른 정렬 흐름으로 보는지

이런 주석은 코드 설명이라기보다, “왜 이 구조를 택했는지”를 남기는 용도에 가깝다.

나중에 다시 열었을 때 가장 먼저 헷갈리는 건 문법이 아니라 의도이기 때문이다.

물론 partial class로 나누었다고 해서 구조가 완전히 해결된 것은 아니다.

여전히 MapToolWindow.cs 안의 UI는 길고, FaceAnchor.Geometry나 Snap.Helpers는 수학 로직이 많다.

즉 현재 상태는 “큰 한 덩어리를 실용적인 단위로 나누는 데 성공했다” 정도에 가깝고, 이후에는 정말 C++ 스타일로 helper class나 solver 클래스로 빼는 것도 충분히 고려할 수 있다.

예를 들면 장기적으로는:

  • MapToolSnapSolver
  • MapToolFaceAnchorSolver
  • MapToolPreviewRenderer
  • MapToolColliderUtility

같이 역할 단위 객체로 더 분리하는 편이 더 좋을 수 있다.

다만 현재 단계에서는 무조건 많이 쪼개는 것이 목표는 아니다.

중요한 것은 지금 당장 작업을 계속할 수 있을 만큼 구조를 정리하는 것이다.

즉 리팩토링 자체가 목적이 아니라, 다음 기능을 덜 무섭게 붙일 수 있는 상태를 만드는 것이 목적이다.

이번 작업을 하면서 특히 인상 깊었던 건, 에디터 툴도 결국 게임 코드와 똑같다는 점이었다.

처음에는 돌아가기만 하면 된다고 생각하지만, 기능이 늘어나는 순간부터는 읽기 쉬운 구조가 곧 개발 속도가 된다.

그리고 구조를 나누는 과정은 단순히 보기 좋게 만드는 것이 아니라, 죽은 코드와 중복 분기를 찾아내는 과정이기도 했다.

지금 맵툴은 아직 발전 중이다.

그래도 적어도 이제는 기능을 추가할 때 덜 겁나고, 문제를 고칠 때 어디를 열어야 할지 알 수 있는 구조가 되었다.

그 자체로 이번 리팩토링은 꽤 의미가 있었다고 생각한다.