유니티 사화 리팩토링

출시까지 마친 유니티 사화를 Unity 6 프로젝트로 옮겨 코드를 처음부터 다시 설계한 개인 프로젝트입니다.

기능을 더 넣는 것이 목적이 아니라, 같은 게임을 유지보수 가능한 구조로 다시 짜는 것이 목적이었습니다. 시스템 경계를 문서로 확정하고, 그 규칙을 AI가 검사하게 만들어 384개 커밋을 쌓았습니다.

유니티 사화 리팩토링 대표 이미지 (임시)

프로젝트 개요

장르 및 플랫폼

  • 3D 액션 어드벤처
  • PC(Windows)
  • Mobile(Android)

엔진 및 언어

  • Unity 6 (6000.3.11f1)
  • C#

개발 기간 및 인원

  • 2026.04 ~ 2026.09 (1차 마무리)
  • 개인 프로젝트 (1인)
  • 설계 · 구현 · 배포 전담

작업 방식

  • Claude Code 기반 AI 협업
  • 컨벤션 · 평가 규칙을 커맨드로 고정
  • 커밋 384건

배포

  • Steam 업데이트
  • Google Play 업데이트

대상

  • 출시작 유니티 사화의 전체 코드
  • 기믹 · 몬스터 등 타인 작성 코드는 이식만

리팩토링 전후 비교

출시 당시 저장소와 현재 저장소를 같은 기준으로 직접 측정한 값입니다. 게임 코드만 세었고 외부 플러그인은 제외했습니다.

출시 당시 (레거시) 리팩토링 이후

Assets 루트에 흩어진 파일

▼ 100%
71 개
0 개

스크립트 · 프리팹 · 터레인이 루트에 그대로 쌓여 있었습니다. 지금은 코드 · 레벨 · 콘텐츠 · 플러그인 · 데이터 5개 폴더로만 진입합니다.

파일당 평균 길이

▼ 60%
140 줄
56 줄

전체 줄 수는 19,893 → 16,317줄로 줄었지만 파일 수는 142 → 288개로 늘었습니다. 한 파일이 한 가지만 하도록 쪼갠 결과입니다.

가장 긴 클래스

▼ 41%
1,154 줄
680 줄

Before는 AnimalMaskSkill, After는 Enemy입니다. Enemy는 제가 작성하지 않은 코드라 이식만 하고 리팩토링 대상에서 제외했습니다.

300줄이 넘는 클래스

▼ 81%
16 개
3 개

싱글톤 static Instance

▼ 91%
11 개
1 개

남은 1개는 의존성 주입기 자신입니다. 나머지 참조는 전부 [Inject] 어트리뷰트로 대체했습니다.

플레이어 시스템 코드량

▼ 60%
8,854 줄
3,542 줄

캐릭터 3종의 스킬 클래스를 통째로 복제하던 구조를, 상태 데이터 에셋과 하나의 StateRunner로 합쳤습니다.

Android 빌드 용량

▼ 29%
550.73 MB
389.69 MB

Google Play Console의 500MB 제한을 넘겨 업로드가 막혔던 문제를 계측 기반으로 해결했습니다.

문서화된 시스템 규칙

▲ null%
0 개
12 개

시스템마다 역할 · 진입점 · 외부 연결 · 설계 규칙을 SystemUsageGuide에 확정해 두고, 코드를 쓰기 전에 그 항목을 먼저 읽습니다.

주요 활동

01

시스템 경계 재정의

출시작에는 '어디까지가 한 시스템인가'라는 기준이 없어, 필요할 때마다 매니저를 하나씩 늘리는 식으로 코드가 자랐습니다. 폴더 하나를 시스템 하나로 못 박고, 시스템마다 역할·진입점·외부 연결·설계 규칙을 문서로 확정한 뒤에만 코드를 쓰도록 순서를 바꿨습니다.

  • 12개 시스템의 규칙을 SystemUsageGuide로 확정, 코드 작성 전 참조 대상으로 고정
  • Assets 루트에 흩어져 있던 71개 파일을 5개 최상위 폴더로 정리
02

의존성 주입 시스템 자체 구현

출시작은 시스템 간 참조를 싱글톤 11개와 인스펙터 드래그로 연결하고 있었습니다. 외부 DI 프레임워크를 얹는 대신, 어트리뷰트 한 줄로 인터페이스를 주입받는 컨테이너를 직접 만들었습니다. 리플렉션 비용은 어셈블리 로드 직후 1회 캐싱으로 걷어냈습니다.

  • 싱글톤 static Instance 11개 → 1개(주입기 자신)로 축소
  • [Inject] 필드 79곳이 구체 클래스가 아닌 인터페이스로 연결됨
03

플레이어 상태 시스템 재설계

출시작은 캐릭터 3종의 스킬 클래스를 통째로 복제해 2,864줄을 쓰고 있었습니다. 상태를 상속 계층으로 짜다가, 자식 클래스에 로직이 없다는 사실을 확인하고 상속 자체를 걷어냈습니다. 상태는 데이터 에셋이 되고, 실행은 StateRunner 하나가 맡습니다.

  • 플레이어 시스템 8,854줄 → 3,542줄, 스킬 추가가 코드 작성이 아닌 에셋 편집이 됨
  • 상태를 아는 쪽과 반응하는 쪽을 이벤트 채널로 분리해 상태 추가 시 기존 코드 수정 불필요
04

AI를 품질 게이트로 세운 협업 방식

AI에게 코드를 대신 짜게 하는 대신, 제가 정한 기준으로 제 코드를 검사하게 만들었습니다. 컨벤션·클래스 평가·시스템 평가·근거 조사를 각각 슬래시 커맨드로 고정하고, 사용자 편을 들지 말라는 규칙을 최우선으로 박아 넣었습니다. 1인 프로젝트에 코드 리뷰어를 붙인 셈입니다.

  • 판정 기준을 문서로 고정해 8종 커맨드로 운영, 리뷰가 취향이 아닌 대조 작업이 됨
  • 혼자 5개월 · 커밋 384건 규모의 전면 재설계를 완주

그 외 활동

빌드 용량 감축

Play Console이 500MB 제한을 넘겼다며 업로드를 막았습니다. 추측으로 에셋을 지우는 대신 build-report-inspector로 용량 분포를 먼저 계측했고, 상위 항목이 전부 비압축 텍스처임을 확인해 일괄 압축 도구를 만들었습니다.

550.73MB → 389.69MB (-161MB), Play Console 500MB 제한 통과

렌더링 최적화 도구 제작

출시작은 셰이더 팝인을 막으려고 씬 시작 시 카메라로 맵 전체를 훑고 있었습니다. 기준이 '화면에 보였는가'라 풀링 오브젝트를 커버하지 못했습니다. ShaderVariantCollection으로 실사용 조합만 수집하도록 바꾸고, 배칭을 깨뜨리는 중복 머티리얼을 찾는 에디터 툴을 만들었습니다.

커버리지 기준을 '화면 노출'에서 '실제 사용된 variant'로 교체, 풀링 오브젝트까지 포함

모바일 GPU 병목 분석

Galaxy S10에서 40 FPS를 못 지켰습니다. Unity Profiler는 모바일 GPU 타이밍을 안 주므로 Arm Streamline으로 하드웨어 카운터를 실측했고, 보스 이펙트 프레임의 74.9%가 '왜곡 강도 0인 셰이더의 화면 전체 복사'였음을 찾아냈습니다.

GPU 19.2ms(프레임의 74.9%)를 차지하던 무의미한 Scene Color 복사 제거 — 시각적 변화 없음

가림 오브젝트 투시 기능

카메라와 플레이어 사이의 나무가 시야를 가리는 문제가 있었습니다. 머티리얼에 직접 값을 쓰는 방식과 Raycast 판정 두 가지를 차례로 폐기하고, 전역 셰이더 변수와 픽셀 깊이 비교로 다시 짰습니다. 왜 이 방법이 되었는지를 코드 주석에 그대로 남겼습니다.

나무 종류·개수가 늘어도 코드 수정 없이 동작하는 구조로 정착

스토어 업데이트 배포

리팩토링은 유저가 볼 수 없는 작업이라 그대로 두면 저장소 안에서 끝납니다. 재설계한 빌드를 이미 출시된 Steam·Google Play 채널에 업데이트로 올려, 실제 유저 환경에서 도는 것까지 확인했습니다.

Steam · Google Play 업데이트 배포로 리팩토링 결과를 출시 환경에서 검증