Opus 5.5를 사용하면 Claude Code 세션 한 번으로 2.5D 모션 스튜디오를 만들 수 있습니다. 평면 레이어를 서로 다른 깊이에 쌓고, 이징이 적용된 키프레임 카메라가 그 사이를 이동하는 단일 HTML 파일 하나, 그리고 모든 프레임을 캡처해 ffmpeg로 파이프하는 25줄짜리 Playwright 스크립트면 충분합니다. 우리 빌드는 노트북에서 6초 길이의 720p MP4(180프레임)를 약 20초 만에 렌더링했습니다.
이 가이드에서는 사용한 프롬프트, 에이전트가 작성한 코드, 마주친 버그, 그리고 Apidog CLI로 “샷 묘사” 기능 뒤에 있는 Opus 5.5 API 호출을 검증하는 방법까지 소개합니다.
2.5D 모션 스튜디오란?
2.5D 모션 스튜디오는 평면 2D 레이어(하늘, 언덕, 도시, 타이틀 카드, 전경 수목)를 3D 공간의 서로 다른 깊이에 배치한 뒤 가상 카메라가 그 사이를 이동하는 애니메이션 도구입니다. 카메라에 가까운 레이어가 먼 레이어보다 빠르게 움직이며, 이를 통해 3D 모델 없이도 시차 효과와 입체감을 얻을 수 있습니다.
일회성 애니메이션이 아닌 “스튜디오”가 되려면 세 가지 요소가 필요합니다:
- 씬 그래프:
z깊이, 크기, 위치, HTML 콘텐츠를 가진 레이어들. - 타임라인: 카메라용 키프레임(돌리, 트럭, 오빗)과 각 레이어용 키프레임(투명도, 스케일, 위치), 각각에 이징 커브가 적용됩니다.
- 결정론적 렌더러: 임의의 시간
t에 대해 정확한 프레임을 그려내는renderAt(t)함수. 덕분에 내보내기 결과가 재생 속도에 좌우되지 않습니다.
세 번째 항목은 대부분의 튜토리얼이 생략하는 부분이지만, MP4가 끊기는지 아닌지를 결정짓는 요소입니다.
Opus 5.5에서 Claude Code 설정하기
이런 작업에는 Opus 5.5부터 시작하는 것이 좋습니다. Anthropic의 모델 개요는 이를 “장시간 실행되는 에이전트 코딩과 지식 작업을 위한 모델”로 소개하며, 100만 토큰 컨텍스트 창, 128K 최대 출력, 백만 입력/출력 토큰당 4달러/20달러의 가격을 명시합니다.
프로젝트 도중 별칭 변경으로 모델이 바뀌지 않도록 모델을 고정하세요:
claude --model claude-opus-5-5 --effort high
effort 문서에 따르면 Opus 5.5는 기본적으로 medium effort를 사용합니다. 첫 빌드 시, 즉 에이전트가 투영 수학을 정확히 맞춰야 하는 시점에는 high로 올리세요. 이후 키프레임과 색상을 조금씩 수정할 때는 medium으로 되돌리면 됩니다. 모델을 먼저 알고 싶다면 Claude Opus 5.5란 무엇인가와 프롬프트 캐싱이 비용에 미치는 영향을 읽어보세요.
스튜디오를 만들어내는 프롬프트
구체적인 프롬프트 하나가 막연한 프롬프트 다섯 개보다 낫습니다. 다음은 하나의 프롬프트로 작성한 우리의 빌드 명세입니다:
Build a 2.5D motion studio as one file, studio.html, with no dependencies.
- Scene: 6 layers (sky, moon, hills, city skyline, a title card "Ship it.",
foreground foliage), each at its own z depth, using CSS perspective and
preserve-3d.
- Camera: keyframes for x, y, z (dolly) and yaw (orbit around the card).
- Per-layer tracks: opacity, scale, y. Easings: linear, easeInOutCubic,
easeInOutSine, easeOutBack.
- A timeline bar with keyframe markers, a playhead, click to scrub,
space to play or pause.
- renderAt(t) must be pure in t. Expose window.__seek(t) that resolves after
the frame paints, and a ?capture mode that hides the UI.
Then write export.mjs: Playwright opens ?capture, seeks every frame at 30 fps,
screenshots it and pipes PNGs to ffmpeg as H.264.
마지막 두 줄이 가장 중요합니다. 내보내기를 추가 기능이 아니라 설계의 일부로 만들기 때문입니다.
에이전트가 작성한 코드
스튜디오는 약 150줄입니다. 핵심은 세 부분입니다.
키프레임과 이징
각 트랙은 정렬된 키프레임 목록입니다. sample()은 t를 둘러싼 두 키프레임을 찾아, 나가는 키프레임의 이징을 적용하고 모든 숫자 필드를 보간합니다:
function sample(keys, t) {
if (t <= keys[0].t) return keys[0];
for (let i = 0; i < keys.length - 1; i++) {
const a = keys[i], b = keys[i + 1];
if (t <= b.t) {
const p = (easings[a.ease] || easings.linear)((t - a.t) / (b.t - a.t));
const out = {};
for (const k in b) if (typeof b[k] === 'number' && k !== 't') out[k] = a[k] + (b[k] - a[k]) * p;
return out;
}
}
return keys[keys.length - 1];
}
깊이 트릭
카메라는 움직이지 않고, 세계가 반대 방향으로 움직입니다. 그러면 모든 레이어는 z 방향 translate에 보정 스케일을 곱해 받게 됩니다:
const PERSPECTIVE = 1200; // must match #viewport perspective
world.style.transform =
`translate3d(${-cam.x}px, ${-cam.y}px, ${cam.z}px) rotateY(${-cam.yaw}deg)`;
const comp = 1 - L.z / PERSPECTIVE;
el.style.transform = `translate(-50%,-50%) ` +
`translate3d(${x * comp}px, ${y * comp}px, ${L.z}px) scale(${comp * s})`;
CSS perspective를 1200px로 설정하면 z = -900 위치의 레이어는 1200 / 2100, 즉 원래 크기의 57%로 렌더링됩니다. 레이어의 스케일과 x/y 오프셋에 1 + 900/1200 = 1.75를 곱하면 이 효과가 상쇄됩니다. 결과적으로 카메라가 정지해 있을 때는 모든 레이어가 제작자가 의도한 크기와 위치로 나타나고, 카메라가 움직일 때는 여전히 각자의 속도로 움직입니다. 이것이 바로 이 기법의 전부입니다.

알아둘 가치가 있는 버그
첫 버전에서는 각 레이어의 스케일만 조정하고 오프셋은 조정하지 않았습니다. 그 결과 언덕이 솟아올라 하늘의 3분의 2를 가려버렸습니다. 해결책은 x와 y에도 comp를 곱하는 것이었습니다. 우리는 코드를 읽는 것이 아니라 렌더링된 프레임 한 장에서 이 문제를 발견했으므로, 카메라 움직임을 신뢰하기 전에 시작·중간·끝 지점의 정지 화면을 렌더링하도록 에이전트에게 요청하세요.
타임라인을 MP4로 내보내기
실시간 재생은 requestAnimationFrame을 사용하므로, 기기가 바쁠 때 프레임이 드랍됩니다. 내보내기는 그래서는 안 됩니다. 그래서 내보내기 도구는 시간을 프레임 인덱스에서 직접 구동하고, Playwright 스크린샷을 찍기 전에 각 프레임이 그려질 때까지 기다립니다:
import { chromium } from 'playwright';
import { spawn } from 'node:child_process';
import { pathToFileURL } from 'node:url';
const out = process.argv[2] || 'out.mp4';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
await page.goto(pathToFileURL('studio.html').href + '?capture');
const { duration, fps } = await page.evaluate(() => window.__scene);
const ffmpeg = spawn('ffmpeg', ['-y', '-f', 'image2pipe', '-framerate', String(fps),
'-i', '-', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', out],
{ stdio: ['pipe', 'ignore', 'inherit'] });
const frames = Math.round(duration * fps);
for (let i = 0; i < frames; i++) {
await page.evaluate(t => window.__seek(t), i / fps);
ffmpeg.stdin.write(await page.screenshot({ type: 'png' }));
}
ffmpeg.stdin.end();
await new Promise(r => ffmpeg.on('close', r));
await browser.close();
window.__seek(t)는 두 애니메이션 프레임이 지난 후에 resolve되므로, 매 스크린샷은 새로운 트랜스폼을 반영합니다. image2pipe(ffmpeg 포맷 문서 참고)는 stdin에서 PNG를 읽고, yuv420p는 파일이 브라우저에서 재생 가능하도록 유지합니다.
우리의 실행 결과: 180프레임, 1280x720, 30fps, 6.0초, 420KB MP4, 벽시계 기준 20초. Playwright에 브라우저가 아직 없다면 npx playwright install chromium을 한 번 실행하세요.

도시의 빌딩들은 어두운 전경 언덕보다 덜 이동합니다. 전경 언덕은 화면에서 완전히 벗어나기까지 하죠. 이것이 바로 시차 효과입니다.
Opus 5.5 API로 “샷 묘사” 기능 추가하기
다음으로, 작가가 샷 설명을 입력하면 키프레임을 받아볼 수 있게 해봅시다. 스튜디오는 Messages API를 직접 호출합니다:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2026-10-21" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 4096,
"output_config": { "effort": "low" },
"system": "Return only JSON: {\"camera\": [{\"t\",\"x\",\"y\",\"z\",\"yaw\",\"ease\"}], \"tracks\": {}}. Times in seconds, 0 to 6.",
"messages": [{ "role": "user", "content": "Slow push-in, slight orbit right, title fades up in the first second." }]
}'
effort 문서에서 여기서 중요한 두 가지 세부사항이 있습니다. 첫째, output_config.effort가 Messages API 요청에서 effort 수준을 지정하는 위치입니다. 둘째, Opus 5.5는 항상 어댑티브 thinking을 사용하며(thinking: {"type": "disabled"} 요청은 400을 반환), thinking 토큰이 max_tokens에 포함됩니다. 값을 너무 낮게 설정하면 stop_reason: "max_tokens"와 함께 JSON이 배열 중간에서 잘려나갑니다. 샷 목록 생성은 사양이 명확한 짧은 작업이므로 low effort가 합리적인 첫 설정입니다. 단, 확정하기 전에 직접 비교해 보세요.
Claude Code가 Apidog CLI로 API 호출을 검증하게 하기
응답이 잘리거나 장황하면 렌더링 시점에 sample()이 깨지므로, 이 호출에는 계약 테스트가 필요합니다. 같은 Claude Code 세션이 Apidog CLI로 이 테스트를 작성하고 실행할 수 있습니다.
한 번만 설치하고 로그인하세요:
npm install -g apidog-cli@latest
apidog auth login --with-token <YOUR_TOKEN>
apidog skill install
그리고 에이전트에게 프롬프트 하나를 줍니다:
In Apidog project <projectId>, create an endpoint for POST /v1/messages and a
test case that sends the "describe a shot" request above with model
claude-opus-5-5. Assert: status 200, body.model is "claude-opus-5-5",
stop_reason is not "max_tokens", and content[0].text parses as JSON with a
non-empty camera array whose t values rise from 0 to 6. Use an environment
with base URL https://api.anthropic.com and the key in a variable. Run it and
summarize failures.
에이전트는 스키마를 추측하는 대신 페이로드 스키마를 읽고, 파일을 검증한 뒤 모든 것을 생성하고 실행합니다:
apidog cli-schema get endpoint-create
apidog cli-schema get test-case-create
apidog cli-schema validate test-case-create --file case.json
apidog environment create anthropic --project <projectId> --base-url https://api.anthropic.com
apidog endpoint create --project <projectId> --file endpoint.json
apidog test-case create --project <projectId> --file case.json
apidog run --test-case <caseId> --project <projectId> -e <envId> -r cli,json
키가 프로젝트에 저장되지 않도록 실행 시점에 --env-var "api_key=$ANTHROPIC_API_KEY"로 전달하세요. 시스템 프롬프트나 effort 수준을 바꿀 때마다 테스트 케이스를 다시 실행하세요. 둘 다 응답 결과에 영향을 주기 때문입니다. -r junit 옵션을 붙인 동일한 apidog run은 CI에 바로 연동됩니다. Apidog CLI CI/CD 가이드와 Claude Code에서의 Apidog CLI를 참고하세요. Apidog의 Free 플랜은 최대 4명의 사용자까지 지원합니다.
스튜디오의 다음 확장 방향
- 같은 수학으로 레이어 더 추가하기. 비, 두 번째 타이틀, 로고 등을 추가하세요. 각각
z값만 주면 보정 로직이 나머지를 처리합니다. - 렌더러 교체하기. 씬을 React 컴포넌트로 작성하고 싶다면 Remotion과 함께하는 Claude Code가 그 방법을 다룹니다. 이 글의 단일 파일 방식은 빌드 단계가 없습니다. Claude Code 개요는 세션과 권한에 대해 설명합니다.
FAQ
모션 그래픽에서 2.5D란 무엇인가요? 평면 2D 아트웍을 서로 다른 깊이에 배치하고 움직이는 카메라로 촬영한다는 뜻입니다. 3D 모델, 라이팅, 3D 엔진 없이도 시차 효과와 깊이 감각을 얻을 수 있습니다.
이 빌드에 더 작은 모델 대신 Opus 5.5를 쓰는 이유는? 첫 빌드는 투영 수학, 키프레임 엔진, 캡처 파이프라인을 한 번에 다룹니다. Anthropic은 Opus 5.5를 장시간 에이전트 코딩용으로 포지셔닝하고 있으며, 우리 세션은 프롬프트 한 개와 수정 한 번 만으로 동작하는 스튜디오를 만들어냈습니다. 이후의 사소한 수정에는 더 낮은 effort 수준이면 충분합니다.
프레임을 캡처하는 대신 화면을 녹화하면 안 되나요? 화면 녹화는 실시간을 따르므로 느려진 프레임은 드랍되거나 중복됩니다. i / fps로 시크하고 각 프레임을 캡처하면 어떤 기기에서든 결과물이 동일해집니다.
API 키 없이 “샷 묘사” 기능을 사용할 수 있나요? 아니요. 이 기능은 Anthropic API 키가 필요한 Messages API를 호출합니다. 스튜디오의 나머지 부분과 MP4 내보내기는 키 없이 로컬에서 실행됩니다.
샷 목록 호출에는 어느 effort 수준을 써야 하나요? low부터 시작하세요. 작업이 짧고 출력 스키마가 고정되어 있기 때문입니다. Apidog 테스트에서 형식이 잘못되었거나 의도에서 벗어난 키프레임이 발견될 때만 올리면 됩니다.
