내가 만든 Unity CLI를 벤치마크했더니, 내 도구의 문제가 먼저 보였다

v0.5.6 CLI로 실제 명령을 실행한 20초짜리 화면이다. 큐브를 만들고 머티리얼을 넣고 스크립트를 붙인 다음, Play Mode에 들어가 콘솔을 확인하고 멈춘다. JSON 출력은 보기 좋게 줄였다.
unity-cli-bridge는 Unity 에디터를 명령줄에서 다루는 도구다. 터미널에서 명령을 실행하면 열려 있는 에디터에 로컬 IPC로 전달되고, 결과가 JSON으로 돌아온다. 0.5.6 버전을 GitHub와 itch.io에 올려 두었다.
이 글에서는 이 도구를 왜 만들었는지, 다른 도구 세 개와 어떻게 비교했는지, 그 과정에서 내 도구의 어떤 문제가 드러났는지, 고친 뒤 결과가 어떻게 달라졌는지를 정리한다. 벤치마크는 내가 직접 돌렸으니 숫자는 그 점을 감안하고 봐 주면 좋겠다. 이 얘기는 끝부분에서 다시 한다.
왜 만들었나
스크립트나 AI 코딩 에이전트(Claude Code, Codex, Grok Build)가 셸을 쓰듯 Unity 에디터도 다룰 수 있으면 좋겠다고 생각했다. 명령을 실행하고, 결과를 읽고, 다음에 뭘 할지 정하는 흐름이다.
내가 본 에디터 자동화 도구는 대부분 둘 중 하나였다.
- 중간에 서버를 두는 방식. Python이나 Node 프로세스가 수십 가지 동작을 감싸고, 모든 호출이 이 서버를 거쳐 에디터로 간다. 띄워야 할 프로세스가 하나 늘고, 그만큼 고장 날 곳도 늘어난다.
- C#을 그대로 실행하는 방식. 에디터에 C# 코드를 보내고 로그에서 결과를 읽는다. 할 수 있는 일은 많지만 호출할 때마다 정확한 C#을 짜야 하고, 결과를 읽으려면 한 번 더 호출해야 할 때가 많다.
나는 그 중간을 원했다. 중간 서버 없이 에디터에 바로 붙고, 하고 싶은 일을 명령으로 선언하는 방식이다.
CLI ──── local IPC ──── Unity Editor (bridge)
(named pipe on Windows, Unix socket on macOS)
그래서 C#을 쓰는 대신 scene add-object --primitive Cube --position 3,0,0처럼 명령을 친다. 호출하는 쪽은 API를 고르는 게 아니라 옵션만 고르면 된다. 모든 명령은 결과 JSON을 stdout으로 바로 내보낸다. 에이전트는 읽는 토큰만큼 비용이 들기 때문에 --output compact나 --omit-defaults 같은 옵션으로 JSON을 짧게 줄일 수 있게 했다. 맞는 명령이 없을 때는 execute로 C#을 실행할 수도 있다.
에이전트용 스킬도 프로젝트에 같이 들어 있다. 상황에 맞는 명령을 고르고, 수정하기 전에 먼저 상태를 확인하고, 바꾼 뒤에는 read-console로 에러를 확인하도록 알려 준다.
벤치마크
설계 의도가 그럴듯한 것과 실제 프로젝트에서 에이전트에게 도움이 되는 건 다른 문제다. 그래서 비교 실험을 해 봤다.
- 도구 네 개. unity-cli-bridge, 공식 Unity CLI, 그리고 커뮤니티 도구 두 개다. 커뮤니티 도구는 Tool A, Tool B라고 부르겠다. Tool A는 C#을 빠르게 동적으로 실행하는 데 초점을 맞춘 도구이고, Tool B는 MCP 서버다.
- 과제 네 개. 프롬프트는 영어로 고정했다. 대상은 기존 코드가 있고 중첩 프리팹과 큰 직렬화 데이터가 들어 있는 중간 규모의 Unity 6 URP 프로젝트다. 빈 샘플 프로젝트가 아니다.
- 에이전트 하나. Claude Code에 Opus 5.5를 low effort로 붙여 headless로 돌렸다. 한 번에 한 회차씩, Mac mini 한 대와 Unity 6000.3.10f1에서 실행했다.
- 숨겨 둔 검증. 회차가 끝날 때마다 에이전트가 본 적 없는 EditMode/PlayMode 테스트로 결과를 검사했다. 필수 검사를 모두 통과하고 새 컴파일 에러가 없어야 통과로 쳤다.
- 격리. 회차마다 프로젝트를 새로 복사해서 시작했고, 에이전트에게는 해당 도구의 스킬이나 플러그인만 줬다. 권한 규칙과 hook으로 디스크에 있는 도구 소스 코드는 읽지 못하게 막았다.
과제는 씬 점검과 수정(s1c), C#을 금지한 같은 점검(s1q), 일시정지 메뉴 UI 만들기(s3), 프리팹 variant 20개 만들기(s4)다. 아래 숫자는 유효한 회차만 넣은 것이고, 어떤 회차를 왜 뺐는지는 위키에 적어 두었다.
내 도구에서 드러난 문제
C# 없이 명령만 쓰는 s1q를 처음 돌린 건 예전 main(04778d6)이었다. 에이전트가 통과는 했지만 느렸다. 2회 평균 332.8 s, 회당 $1.127이 들었고 도구 호출도 평균 40번이었다. 로그를 읽어 보니 이유가 보였다. 에이전트가 씬과 프리팹 YAML을 grep으로 뒤져서 직접 파싱하고 있었다. 이 과제에서 다른 도구를 쓴 회차들도 똑같이 이렇게 우회했다.
이 과제는 중첩 프리팹 안에서 꺼져 있는 렌더러를 찾아야 한다. 그런데 --omit-defaults를 켜면 scene inspect가 활성 상태와 중첩 프리팹 정보를 기본값으로 보고 빼 버려서, 출력에 그 정보가 아예 없었다. 게다가 스킬에 scene patch 형식 문서가 들어 있지 않아서, 에이전트가 형식을 알아야 할 때 찾아볼 곳이 없었다.
제외한 회차 중에 기억에 남는 게 하나 있다. 격리를 강화하기 전에 돌린 초기 기준 측정 2회는 버렸는데, 에이전트가 디스크에 있던 unity-cli-bridge 패키지 소스를 읽었기 때문이다. 그런데 소스를 읽은 회차가 오히려 더 빨랐다. 도구 출력에 빠진 정보를 에이전트가 코드를 읽어서 메우고 있었던 셈이다.
고친 내용: PR #205
PR #205에서 로그로 확인한 문제를 고쳤다.
scene inspect --omit-defaults가 이제 활성 상태와 중첩 프리팹 정보를 빼지 않는다.- scene patch 형식 문서를 스킬 안에 참고 파일로 넣었다. 에이전트가 필요할 때 열어 볼 수 있다.
scene patch에 모르는 operation이 들어오면 가장 비슷한 operation을 제안한다.prefab inspect가 모델 파일(.fbx)도 받는다.qa world-dump가 Edit Mode에서도 동작한다.
다 작은 수정이다. 로그에서 본 문제를 직접 겨냥한 건 앞의 두 개다.
다시 측정한 결과
같은 명령 전용 과제를, PR #205가 들어간 main(9f53c04)으로 다시 돌렸다. 이 커밋이 그대로 v0.5.6이 됐다.
| s1q (명령만 사용) | 횟수 | 통과 | 소요 시간 평균 | 비용 평균 | 도구 호출 평균 | 출력 토큰 평균 |
|---|---|---|---|---|---|---|
수정 전 (04778d6) | 2 | 2/2 | 332.8 s | $1.127 | 40 | 15,144 |
PR #205 이후 (9f53c04) | 5 | 5/5 | 119.9 s | $0.465 | 13 | 5,626 |
이 과제에서 소요 시간은 64%, 비용은 59% 줄었다. 이제 에이전트는 scene inspect --omit-defaults로 씬을 읽고, scene add-component와 scene patch로 고친다.
같은 과제를 도구별로 5회씩 돌린 결과는 이렇다.
| s1q | unity-cli-bridge | 공식 Unity CLI | Tool A | Tool B |
|---|---|---|---|---|
| 통과 | 5/5 | 5/5 | 4/5 | 5/5 |
| 소요 시간 평균 | 119.9 s | 224.1 s | 454.9 s | 216.2 s |
| 소요 시간 중앙값 | 84.7 s | 232.8 s | 250.8 s | 181.6 s |
| 비용 평균 | $0.465 | $0.897 | $1.659 | $1.977 |
C#을 허용한 나머지 세 과제에서는 차이가 훨씬 작았다. 네 도구 모두 전 회차를 통과했고, 어떤 도구를 줘도 에이전트는 결국 C# 코드 조각 1개에서 5개로 일을 끝냈다. 시간 차이는 대부분 회차마다 생기는 편차 안에 있었고, 비용은 주로 도구가 처음에 불러오는 컨텍스트 양에 따라 갈렸다. 첫 턴 컨텍스트는 unity-cli-bridge가 약 14.1k 토큰으로 넷 중 가장 작았다.
여기서 얻은 교훈은 이렇다. 씬 작업에서 생긴 차이는 대부분 도구가 읽어 주는 출력에 에이전트가 필요한 상태가 이미 들어 있느냐에서 나왔다. 그게 없으면 에이전트는 YAML을 뒤지기 시작하고, 시간과 비용이 몇 배로 불어난다. 고치기 전의 내 도구도 다른 도구와 다르지 않았다.
앞서지 못한 부분
- 코드 실행 속도. Tool A는 C# 한 번 실행이 대략 열 배 빠르다. Edit Mode에서 약 19 ms인데, unity-cli-bridge는 새 코드 조각을 아직 매번 컴파일하기 때문에 새
execute한 번에 약 0.5 s가 걸린다. Play Mode를 프레임 단위로 조작하는 것처럼 작은 코드 조각을 많이 실행하는 작업이라면, 위의 어떤 결과보다 이 차이가 더 중요하다. - C#을 허용한 과제. s1c와 s3에서는 Tool A가 가장 빨랐다. 에이전트가 어차피 C#을 짤 거라면 네 도구 중 무엇을 써도 괜찮다.
- 원인을 모르는 느린 회차. 내 s1q 중 r4 회차는 295.3 s가 걸렸다. 중앙값이 84.7 s인 걸 생각하면 한참 느리다. 대부분 도구 쪽에서 쓴 시간이었는데 아직 원인을 찾지 못했다. 평균이 중앙값보다 훨씬 높게 나온 것도 이 회차 탓이 크다.
한계와 편향
이 부분은 솔직하게 적어 둔다.
- 도구를 만든 것도, 벤치마크를 설계하고 돌린 것도, 두 측정 사이에 내 도구를 고친 것도 전부 나다. 다른 도구는 배포된 버전 그대로 한 번만 측정했고, 이 과제에 맞춰 조정할 기회가 없었다. 프롬프트를 고정하고, 검증을 숨기고, 소스를 격리해서 최대한 공정하게 하려고 했지만 편향이 있는 건 분명하다.
- 수정 전후 비교가 전반적인 속도 향상을 뜻하지는 않는다. 이번 벤치마크에서 본 실패를 겨냥해 고쳤고, 같은 과제로 다시 측정했다. "이 문제 하나는 해결됐다" 정도로 읽어 주면 된다.
- 표본이 적다. s1q는 도구당 5회, 나머지 과제는 3회, 예전 기준 측정은 2회다. 느린 회차 하나가 평균을 크게 흔들기 때문에 10%에서 20% 정도 차이는 노이즈로 보는 게 맞다.
- 조건이 좁다. 모델 하나와 effort 설정 하나, 클라이언트 하나, 머신 하나, Unity 버전 하나, 프로젝트 하나에서 과제 네 개만 돌렸다. 모델이나 클라이언트가 바뀌면 순위도 달라질 수 있다.
전체 표와 측정 방법, 제외한 회차는 위키에 있다.
앞으로 할 일
알려진 버그가 두 개 있고, 둘 다 아직 열려 있다.
- #210:
scene set-transform이 넘기지 않은 값을 초기화한다.--rotation 0,30,0만 넘겨도 position과 scale이 0이 된다. 당분간은 position, rotation, scale을 함께 넘겨야 한다. 데모 GIF를 찍다가 발견했다. - #208:
read-console --type error결과에 Exception 항목이 빠진다.
느렸던 r4 회차도 아직 들여다봐야 한다.
unity-cli-bridge는 개인 사이드 프로젝트라 남는 시간에 할 수 있는 만큼 관리한다. 버그 수정과 새 Unity 버전 대응을 먼저 챙긴다. 이슈와 PR은 언제든 환영하지만, 답이 늦을 수 있다는 점은 양해해 주면 좋겠다.
링크
- itch.io: yhkk.itch.io/unity-cli-bridge
- GitHub: github.com/yhc509/unity-cli-bridge
- 릴리스: v0.5.6
- 벤치마크: 위키의 Benchmark v0.5.6
- PR: #205