Claude Code CLI로 Unity 레거시 스크립트 컴포넌트 자동 리팩토링 파이프라인 구축하기
Claude Code CLI와 Unity 2022.3 LTS의 Unity Test Framework를 연결해 레거시 MonoBehaviour의 의존성과 직렬화 리스크를 분석하고 C# 코드를 안전하게 수정·컴파일·테스트하는 자동 리팩토링 파이프라인을 명령과 예제로 정리합니다.

핵심 요약
Claude Code CLI를 Unity 프로젝트의 코드 분석·수정 도구로 사용하려면 AI에게 한 번에 전체 코드를 맡기기보다 분석, 리팩토링 계약 정의, 작은 단위 수정, Unity 컴파일과 테스트, diff 검토를 분리해야 합니다. 특히 MonoBehaviour의 생명주기와 씬·프리팹 직렬화 데이터는 일반적인 C# 리팩토링과 다르게 취급해야 합니다.
이 글에서는 Unity 2022.3 LTS와 Roslyn C# 9.0을 기준으로 CLAUDE.md, claude -p, Unity Test Framework의 EditMode·PlayMode 테스트를 연결하는 안전한 자동 리팩토링 파이프라인을 구성합니다.
Unity 레거시 스크립트 리팩토링은 왜 일반적인 C# 리팩토링과 다른가?
Unity의 MonoBehaviour는 소스 코드만으로 동작이 결정되지 않습니다. 씬과 프리팹에 저장된 컴포넌트 상태, Inspector에서 연결한 참조, Awake·OnEnable·Start·Update의 호출 순서, 다른 컴포넌트가 호출하는 public API가 함께 동작을 구성합니다.
Unity 2022.3 LTS의 기본 C# 컴파일러는 Roslyn이며 C# 9.0을 사용합니다. 다만 Unity 직렬화 시스템은 C#의 모든 기능을 동일하게 지원하지 않으므로 record 같은 타입을 직렬화 대상에 도입하는 방식은 피해야 합니다. 자세한 컴파일러 제약은 Unity 2022.3 C# 컴파일러 문서를 기준으로 확인합니다.
| 위험 요소 | 자동 리팩토링에서 발생할 수 있는 문제 | 검증 방법 |
|---|---|---|
| 직렬화 필드 이름 | Inspector 값과 프리팹 데이터가 초기화됨 | FormerlySerializedAs, 프리팹 로드 테스트 |
| 생명주기 메서드 | 초기화 순서나 이벤트 타이밍이 변경됨 | PlayMode 스모크 테스트 |
| public API | 다른 스크립트의 호출이 컴파일 또는 런타임에서 깨짐 | 전체 컴파일, 참조 검색 |
| 씬·프리팹 참조 | 새 필드가 비어 동작하지 않음 | 씬·프리팹 YAML 및 Editor 검증 |
| 매 프레임 작업 | Find, GetComponent, LINQ로 인한 비용이 남거나 증가함 | Unity Profiler, ProfilerRecorder |
핵심 원칙은 간단합니다. Claude Code는 변경 후보와 코드를 빠르게 만들 수 있지만 Unity 프로젝트의 실제 상태를 판정하는 최종 기준은 Unity Editor의 컴파일 결과와 테스트 결과여야 합니다.
전체 자동 리팩토링 파이프라인은 어떻게 구성하는가?
다음 파이프라인은 변경 범위를 작게 유지하면서도 반복 실행할 수 있도록 구성한 기본 형태입니다.
flowchart TD
A[대상 MonoBehaviour 선정] --> B[Baseline 컴파일 및 테스트]
B --> C[Claude Code 정적 분석]
C --> D[리팩토링 계약과 영향 범위 확정]
D --> E[작은 단위 코드 수정]
E --> F[Unity batchmode 컴파일 및 테스트]
F --> G{검증 통과?}
G -- 아니오 --> H[로그와 diff 분석 후 수정]
H --> E
G -- 예 --> I[사람의 diff 검토]
I --> J[커밋 또는 다음 컴포넌트로 진행]
한 번의 실행에서 여러 컴포넌트를 동시에 바꾸지 않는 것이 좋습니다. 컴포넌트 하나 또는 서로 강하게 결합된 소수의 파일을 한 배치로 묶고 각 배치가 독립적으로 컴파일되도록 만듭니다.
Step 0. Unity 프로젝트의 기준 상태를 고정한다
자동화 전에 먼저 기준 상태를 기록합니다. 다음 항목이 준비되어 있어야 AI가 만든 변경과 기존 문제를 구분할 수 있습니다.
ProjectSettings/ProjectVersion.txt의 Unity Editor 버전Packages/manifest.json과Packages/packages-lock.json- 현재 Git 작업 트리 상태
- 기존 컴파일 오류와 테스트 실패 목록
- 리팩토링 대상 씬, 프리팹, 스크립트 목록
PowerShell에서는 다음처럼 시작할 수 있습니다.
git status --short
rg --files Assets | rg "\\.cs$"
git diff --check
기존 컴파일 오류가 있다면 자동 리팩토링의 성공 기준을 먼저 정해야 합니다. 예를 들어 기존 오류를 별도의 목록으로 기록하고 이번 변경으로 오류 수가 증가하지 않았는지를 검사할 수 있습니다.
Step 1. CLAUDE.md에 Unity 전용 규칙을 정의한다
Claude Code는 프로젝트의 CLAUDE.md를 프로젝트 지침으로 활용할 수 있습니다. 프로젝트에 적용할 규칙과 검증 명령을 문서화하면 매번 긴 프롬프트를 반복하지 않아도 됩니다. 현재 세션에서 어떤 메모리 파일이 로드되었는지는 /memory로 확인할 수 있습니다.
다음은 Unity 레거시 컴포넌트용 예시입니다.
# Unity refactoring rules
- Unity version: 2022.3 LTS
- Language: Roslyn C# 9.0
- Preserve MonoBehaviour public methods and Unity message signatures unless the task explicitly allows an API change.
- Do not rename a serialized field without adding UnityEngine.Serialization.FormerlySerializedAs.
- Do not change scene or prefab references automatically.
- Do not add packages or change ProjectSettings during a script refactoring task.
- Prefer explicit initialization and cached references over GameObject.Find and repeated GetComponent calls.
- Do not claim performance improvement without a profiler or allocation measurement.
- Change one component or one tightly coupled batch at a time.
## Verification
- Run git diff --check.
- Run Unity EditMode tests.
- Run relevant PlayMode smoke tests when lifecycle, prefab, scene, physics, or input behavior changes.
- Report changed files, tests executed, failures, and unresolved risks.
이 파일의 역할은 AI에게 정답 코드를 지시하는 것이 아니라 변경해서는 안 되는 경계를 제공하는 것입니다. 특히 직렬화 필드와 public API 보존 규칙은 반드시 프로젝트 특성에 맞게 조정해야 합니다.
Step 2. Claude Code CLI로 영향 범위를 먼저 분석한다
Claude Code는 대화형 모드뿐 아니라 -p 또는 --print를 이용한 비대화형 실행을 지원합니다. --output-format json을 사용하면 결과와 세션 메타데이터를 자동화 스크립트에서 처리할 수 있습니다. 실행 옵션은 설치된 버전에 따라 달라질 수 있으므로 실제 환경에서는 claude --help도 함께 확인합니다.
첫 단계에서는 파일을 수정하지 않고 분석만 요청합니다.
New-Item -ItemType Directory -Force artifacts | Out-Null
claude -p "Analyze Assets/Scripts/Combat/EnemyHealth.cs and its references. Return JSON with: serialized fields, Unity lifecycle methods, public API callers, scene and prefab risks, per-frame allocations, proposed safe refactoring steps, and tests required. Do not edit files." --permission-mode plan --output-format json --max-turns 3 | Set-Content artifacts/EnemyHealth.analysis.json
분석 결과에는 최소한 다음 항목이 있어야 합니다.
| 분석 항목 | 확인할 내용 |
|---|---|
| 직렬화 상태 | public, [SerializeField], SerializeReference 필드와 기존 이름 |
| 참조 관계 | 다른 스크립트의 호출, UnityEvent, 애니메이션 이벤트 |
| 생명주기 | Awake, OnEnable, Start, Update, OnDisable의 역할 |
| 외부 데이터 | 씬, 프리팹, ScriptableObject, Addressables 참조 |
| 성능 후보 | 매 프레임 검색, 할당, 반복되는 문자열 비교와 로그 |
| 테스트 전략 | EditMode로 가능한 부분과 PlayMode가 필요한 부분 |
분석 단계의 목적은 코드를 예쁘게 만드는 것이 아니라 변경 범위를 정확하게 정하는 데 있습니다. 분석 결과가 씬이나 프리팹 참조를 확인하지 못했다면 바로 수정하지 말고 사람이 해당 에셋을 추가로 확인해야 합니다.
Step 3. 리팩토링 계약을 작성하고 작은 단위로 수정한다
AI에게 다음과 같이 리팩토링 계약을 명시하면 의도하지 않은 기능 변경을 줄일 수 있습니다.
대상: Assets/Scripts/Combat/EnemyHealth.cs
허용:
- 매 프레임 GameObject.Find 호출을 제거하고 명시적 참조 또는 캐시된 참조로 교체
- 반복되는 GetComponent 호출을 초기화 시점에 캐시
- private 메서드 추출과 변수명 개선
- 기존 직렬화 데이터를 보존하는 FormerlySerializedAs 추가
금지:
- public 메서드와 Unity message 이름 변경
- 씬과 프리팹 파일의 자동 수정
- 새 패키지 설치
- 테스트가 없는 상태에서 동작 보장 주장
- 한 번의 작업으로 다른 시스템의 파일까지 정리
완료 조건:
- 변경 파일 목록과 각 변경 이유를 출력
- git diff --check 통과
- Unity 컴파일 성공
- 관련 EditMode 또는 PlayMode 테스트 결과 보고
실제 수정 명령에는 허용 도구와 최대 에이전트 턴을 제한할 수 있습니다.
claude -p "Read artifacts/EnemyHealth.analysis.json and CLAUDE.md. Apply only the approved refactoring contract to Assets/Scripts/Combat/EnemyHealth.cs. Do not edit scenes, prefabs, packages, or project settings. Show a concise summary after editing." --allowedTools "Read" "Edit" "Bash(git diff:*)" --max-turns 4
--dangerously-skip-permissions처럼 권한 확인을 우회하는 옵션은 자동화 서버에서 특히 신중하게 다뤄야 합니다. 가능한 경우 읽기, 편집, diff 확인처럼 작업에 필요한 최소 권한만 허용하고 커밋과 원격 전송은 파이프라인 밖의 승인 단계로 남겨 두는 편이 안전합니다.
Unity MonoBehaviour 리팩토링에서 직렬화 필드를 어떻게 보존할까?
다음은 레거시 필드 이름을 바꾸면서 Inspector와 프리팹에 저장된 값을 보존하는 패턴입니다. Unity는 Inspector에서 C# 프로퍼티의 getter와 setter가 아니라 직렬화된 필드에 직접 접근하므로 단순히 public 필드를 프로퍼티로 바꾸면 기존 값이 사라질 수 있습니다.
기존 코드의 필드가 다음과 같다고 가정합니다.
using UnityEngine;
public sealed class EnemyHealth : MonoBehaviour
{
public int hp = 100;
private void Update()
{
var player = GameObject.Find("Player");
if (player != null && Vector3.Distance(transform.position, player.transform.position) < 2f)
{
player.GetComponent<PlayerController>()?.ApplyDamage(1);
}
}
}
hp를 maxHealth로 바꾸면서 저장된 값을 이어받으려면 다음처럼 작성할 수 있습니다.
using UnityEngine;
using UnityEngine.Serialization;
public sealed class EnemyHealth : MonoBehaviour
{
[FormerlySerializedAs("hp")]
[SerializeField] private int maxHealth = 100;
[SerializeField] private Transform target;
[SerializeField] private float triggerDistance = 2f;
private PlayerController playerController;
private void Awake()
{
if (target != null)
{
target.TryGetComponent(out playerController);
}
}
private void Update()
{
if (target == null || playerController == null)
{
return;
}
if (Vector3.Distance(transform.position, target.position) < triggerDistance)
{
playerController.ApplyDamage(1);
}
}
}
FormerlySerializedAs는 필드 이름 변경으로 기존 직렬화 값을 잃지 않도록 돕는 Unity 특성입니다. 공식 동작은 Unity Serialization.FormerlySerializedAsAttribute 문서에서 확인할 수 있습니다.
하지만 이 예제는 완전한 의미 보존을 자동으로 보장하지 않습니다. 기존 코드는 이름이 Player인 오브젝트를 매 프레임 검색했지만 새 코드는 target을 Inspector에서 연결해야 합니다. 따라서 실제 리팩토링에서는 대상 씬과 프리팹을 열어 참조가 연결되었는지 확인하는 단계가 반드시 필요합니다.
Step 4. Unity Editor의 컴파일과 테스트를 CLI에서 실행한다
Claude Code가 변경한 파일은 일반 C# 컴파일러만으로 검증하지 않습니다. Unity의 어셈블리 정의, 플랫폼별 컴파일 심볼, 패키지 버전, 씬과 프리팹의 로딩 상태가 함께 반영되어야 하기 때문입니다.
Unity Test Framework는 EditMode와 PlayMode 테스트를 제공합니다. 순수한 계산, 상태 전이, 유틸리티 로직은 EditMode 테스트로 빠르게 검증하고 실제 MonoBehaviour 생명주기, 물리, 입력, 프리팹 연결은 PlayMode 스모크 테스트로 확인합니다.
Windows PowerShell 기준의 기본 실행 예시는 다음과 같습니다.
$unity = "C:\Program Files\Unity\Hub\Editor\2022.3.XXf1\Editor\Unity.exe"
$project = (Get-Location).Path
New-Item -ItemType Directory -Force artifacts | Out-Null
& $unity -batchmode -nographics -quit `
-projectPath $project `
-runTests `
-testPlatform EditMode `
-testResults artifacts\editmode-results.xml `
-logFile artifacts\unity-editmode.log
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Unity 2022.3의 테스트 CLI에서는 -runTests, -testPlatform, -testResults, -testFilter 같은 인자를 사용할 수 있습니다. 프로젝트의 Unity 버전과 Test Framework 패키지 버전에 따라 지원 범위를 확인해야 하며 자세한 인자는 Unity Test Framework 커맨드 라인 인자 문서와 해당 버전의 문서를 기준으로 조정합니다.
리팩토링 대상이 프리팹 또는 씬의 초기화 순서와 관련 있다면 PlayMode 테스트도 별도로 실행합니다.
& $unity -batchmode -nographics -quit `
-projectPath $project `
-runTests `
-testPlatform PlayMode `
-testResults artifacts\playmode-results.xml `
-logFile artifacts\unity-playmode.log
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
테스트 결과가 실패하면 Claude Code에 실패 로그 전체를 전달하기보다 관련 오류, 스택 트레이스, 변경 diff를 함께 제공하는 것이 좋습니다. 로그 파일에는 프로젝트 경로와 환경 정보가 포함될 수 있으므로 외부 서비스로 전송하기 전에 민감한 정보가 있는지 확인합니다.
Step 5. 컴파일 성공과 리팩토링 성공을 구분한다
컴파일이 성공했다고 해서 리팩토링이 완료된 것은 아닙니다. 다음 검증을 단계별로 분리하면 결과를 더 명확하게 판정할 수 있습니다.
- 정적 검증:
git diff --check, 변경 파일 목록, public API 변경 여부를 확인합니다. - Unity 컴파일: batchmode 로그에서 컴파일 오류와 어셈블리 로드 오류를 확인합니다.
- EditMode 테스트: 순수 로직과 데이터 변환을 검증합니다.
- PlayMode 테스트: 생명주기, 프리팹, 물리, 입력과 같은 런타임 동작을 검증합니다.
- 에셋 검증: 변경된 직렬화 필드와 씬·프리팹 참조를 확인합니다.
- 성능 검증: 개선을 주장할 경우 Unity Profiler에서 GC Alloc, 호출 횟수, 프레임 시간 변화를 비교합니다.
성능 측정은 다음처럼 표로 기록하면 리뷰어가 결과를 빠르게 확인할 수 있습니다.
| 지표 | 기준값 | 변경 후 | 측정 조건 |
|---|---|---|---|
EnemyHealth.Update 호출 시간 | 측정 필요 | 측정 필요 | 동일 씬, 동일 프레임 구간 |
GameObject.Find 호출 수 | 측정 필요 | 0 또는 목표값 | PlayMode 스모크 시나리오 |
| GC Alloc/frame | 측정 필요 | 측정 필요 | Development Build 또는 Profiler |
| 관련 테스트 통과율 | 기준 기록 | 기준 이상 | 동일 Test Framework 버전 |
측정값을 얻지 못했다면 “최적화했다” 대신 “반복 검색을 제거하도록 구조를 변경했으며 성능 검증이 필요하다”고 기록하는 것이 정확합니다.
CI에서 Claude Code CLI를 어떻게 안전하게 호출할까?
CI에 적용할 때는 자동 수정보다 자동 분석과 검증을 먼저 도입하는 것이 좋습니다. 권장 순서는 다음과 같습니다.
- Pull Request에서 변경된
.cs파일 목록을 추출합니다. - Claude Code를
--permission-mode plan과--output-format json으로 실행해 분석 리포트를 생성합니다. - 리포트에 직렬화 필드 변경, public API 변경, 씬·프리팹 영향이 있으면 사람 승인을 요구합니다.
- 승인된 변경만 별도 작업 브랜치에서 적용합니다.
- Unity batchmode 컴파일과 EditMode·PlayMode 테스트를 실행합니다.
- 테스트 로그와 Claude Code의 요약을 아티팩트로 보관합니다.
Anthropic CLI 문서에는 비대화형 실행, JSON 출력, 최대 에이전트 턴, 허용·차단 도구 옵션이 정리되어 있습니다. 실제 운영 파이프라인은 Claude Code CLI 레퍼런스의 설치 버전과 옵션을 기준으로 고정해야 합니다.
자동 커밋, 자동 머지, 씬과 프리팹의 일괄 덮어쓰기는 별도 승인 없이는 수행하지 않는 편이 좋습니다. 특히 Unity 에셋은 텍스트 직렬화 설정이나 GUID 관계에 영향을 받을 수 있으므로 변경 diff를 사람이 확인할 수 있어야 합니다.
자주 묻는 질문 (FAQ)
Claude Code가 Unity 씬과 프리팹도 함께 수정하게 해도 될까?
초기 파이프라인에서는 권장하지 않습니다. 스크립트만 수정하고 씬·프리팹은 영향 목록을 생성한 뒤 사람이 검토하거나 별도의 에셋 마이그레이션 단계에서 처리하는 것이 안전합니다.
FormerlySerializedAs를 항상 추가해야 할까?
기존에 Unity가 직렬화하던 필드의 이름을 바꿀 때 필요합니다. 새 필드이거나 저장된 에셋 데이터와 무관한 내부 필드라면 필요하지 않습니다. public 필드를 private [SerializeField] 필드로 바꾸는 경우에는 기존 직렬화 이름과 참조 범위를 먼저 확인해야 합니다.
EditMode 테스트만 통과하면 충분한가?
아닙니다. EditMode 테스트는 순수 로직 검증에 적합하지만 씬 로딩, 프리팹 연결, Unity 생명주기, 물리와 입력은 PlayMode 테스트가 필요할 수 있습니다. Unity Test Framework는 두 실행 모드를 제공하므로 코드가 의존하는 실행 환경에 맞춰 나눠야 합니다.
정리
Claude Code CLI를 이용한 Unity 레거시 스크립트 자동 리팩토링의 핵심은 AI에게 큰 작업을 한 번에 위임하는 것이 아니라 변경을 검증 가능한 배치로 쪼개는 데 있습니다. CLAUDE.md로 프로젝트 규칙을 고정하고 claude -p로 분석과 수정을 자동화하며 Unity batchmode 컴파일과 Unity Test Framework로 결과를 판정하면 반복 가능한 파이프라인을 만들 수 있습니다.
특히 MonoBehaviour의 public API, Unity lifecycle, serialized field, 씬·프리팹 참조는 일반적인 C# 코드 정리보다 높은 위험을 가집니다. 따라서 자동화의 완료 조건은 “코드가 바뀌었다”가 아니라 “의도한 변경만 적용되었고 Unity에서 컴파일·테스트·에셋 검증을 통과했다”로 정의해야 합니다.



