실행 중인 게임에서 게임플레이 영상 및 광고 영상 제작
두 가지 계층:
- 캡처: 실제 실행 중인 게임을 mp4로 녹화 (
scripts/capture-video.cjs) — 화면, 선택적으로 사운드, 그리고 각 프레임의 타임라인. - 편집: 컷 리스트를 기반으로 광고 영상으로 편집 (
scripts/ad-edit.cjs) — 또는 원본 클립을 NLE(비선형 편집기)에 넘깁니다.
이 두 단계 사이에는 광고에 적합한 푸티지를 만드는 요소들이 있습니다: 재현 가능한 실행, 사람처럼 보이는 자동 조종(항상 클리어), 그리고 (선택적으로) 녹화 전용 레벨 스크립트로 스테이지가 좋은 리듬으로 특징을 보여주도록 합니다.
npm run dev # 게임 실행, 예: http://localhost:5173
node ${CLAUDE_SKILL_DIR}/scripts/capture-video.cjs game.capture.cjs 40 auto out take1
node ${CLAUDE_SKILL_DIR}/scripts/ad-edit.cjs ads/a.json out # 컷 리스트 -> out/<list.out>.mp4
node ${CLAUDE_SKILL_DIR}/scripts/probe-trajectory.cjs probe.cjs 30 t.json # 자동 조종 튜닝 (헤드리스, 빠름)
python3 ${CLAUDE_SKILL_DIR}/scripts/plot-trajectory.py t.png t.json
각 렌더링 결과가 생성되면 즉시 사용자에게 열어줍니다 (macOS에서 open <file>) — 검토가 반복 과정의 핵심이므로 경로를 직접 입력하게 하지 마세요.
1. 캡처 (scripts/capture-video.cjs)
canvas.captureStream(fps)는 캔버스의 백킹 버퍼를 녹화합니다 — 창이나 DOM이 아닙니다. 따라서:
- 커서 / HUD가 자동으로 제외됩니다. DOM 오버레이(점수, 버튼, 개발자용 손가락 커서)는 비디오에 나타나지 않습니다. 광고에는 보통 깨끗한 화면이 좋습니다; 텍스트는 편집 단계에서 추가하세요.
- 캡처 해상도는 창과 독립적입니다. 엔진의 내부 렌더링 크기를 광고 크기로 높이거나 — 2배로 해서 편집 시 다운스케일(슈퍼샘플링이 엔진의 안티앨리어싱보다 나은 결과) — 창은 작게 유지합니다(분리 참고).
- 사운드 (
audio: true): AudioContext의 스피커에 연결된 모든 연결을MediaStreamDestination으로 분기하여 자체 MediaRecorder로 녹음하고, ffmpeg가 믹싱합니다.beforeRecord에서 게임의 AudioContext를 잠금 해제하세요 (Chromium은--autoplay-policy=no-user-gesture-required로 실행). - 타임라인 (
timeline: () => ({state, score, beat, ...})): 페이지에서 매 프레임마다 샘플링하여<name>.timeline.json({t, ...}) 파일로 기록합니다. 이 파일을 기준으로 컷을 만듭니다: 각 게이트 통과 시간, 레벨 클리어 시간, 모든 음악 비트 위치. 게임에 음악 비트가 있다면 정수beat로 노출하세요. - 헤드드 또는 헤드리스. 헤드드가 안전한 기본값입니다. 헤드리스도 작동하며 포커스를 빼앗지 않습니다 (
headless: true는 GPU 플래그를 추가) — 하지만 먼저 짧은 테이크의 프레임 수를 확인하세요 (문제점 참고).
설정
| 필드 | 의미 |
|---|---|
url (필수) | 실행 중인 게임 URL, 게임 자체 캡처 쿼리(시드, 고정 스텝, 스크립트…) 포함 |
ready (필수) | 페이지 내 () => boolean |
target | 캡처 크기 {width,height} (기본값 1080×1920; 예: 800×1000 광고용 1600×2000) |
displayHeight | 화면상 캔버스 높이 (760) |
canvasSelector | 게임 캔버스 (canvas) |
setRenderSize / setDisplaySize | 페이지 내; 엔진의 렌더링 크기 증가 / CSS 축소 + 입력 재매핑 |
beforeRecord | 페이지 내, 녹화 직전 한 번 (오디오 잠금 해제, 디버그 오버레이 숨기기…) |
autopilot | 페이지 내, 녹화 시작 직후 한 번, auto 모드에서 |
audio, timeline, headless, settle (ms) | 위 참고 |
bitrate, fps, seconds, outDir, name | 재정의 (CLI: config seconds mode outDir name) |
함수는 직렬화(toString)되므로 자체 포함형으로 유지하세요. 출력: <name>.mp4 (H.264/AAC, 편집용), <name>.webm (+ .audio.webm, 마스터 — 보관), <name>.timeline.json.
캡처 해상도와 창 분리
엔진별 (창은 작게 유지; captureStream은 백킹 버퍼 녹화):
- Phaser:
g.scale.resize(w, h)후 씬 재시작 (게임은create()에서scaleFactor를 캐시); 수동 플레이를 위해g.canvas.style.width/height+g.scale.refresh()로 탭 매핑 보정. - Three.js:
renderer.setPixelRatio(w / cssW)후 CSS 크기로 게임 자체 리사이즈 (카메라 종횡비와 포스트FX 버퍼가 따라가도록). 세로비가 제한된 게임(최대 종횡비로 레터박스)은 4:5 같은 더 넓은 광고 종횡비를 위해 제한을 해제해야 함. - PixiJS:
renderer.resize(w, h)+ 캔버스 스타일.
모드
manual— 창에서 직접 플레이 (헤드드 전용). 가장 자연스러움.auto— 설정의autopilot이 구동. 광고의 경우 사람처럼 보이고 절대 놓치지 않아야 함 —references/autopilot.md참고.
2. 실행 재현 가능하게 만들기 (튜닝 전에 가치 있음)
컷, 자동 조종 또는 레벨을 튜닝하려면 여러 번 재녹화해야 합니다; 매번 다른 결과가 나오면 수렴할 수 없습니다. 게임에 캡처 전용 스위치 두 개를 추가하세요 (쿼리 파라미터; 출시된 게임은 무시):
- 시드 — 게임플레이 난수(장애물 배치 그리고 자동 조종의 선택)를
Math.random대신 시드 기반 PRNG로 제어. 순수 시각적 난수는 그대로 둡니다. - 고정 스텝 — 누산기를 사용하여 고정 1/60초 스텝으로 시뮬레이션 (프레임당 한 번 렌더링, 0..n 스텝 실행). 실제 프레임 시간은 지터가 있지만, 고정 스텝은 시뮬레이션을 프레임 속도(60 Hz, 120 Hz, 무거운 캡처에서 드롭된 프레임)와 관계없이 동일하게 만듭니다.
그러면 동일한 URL이 동일한 실행을 의미합니다 — 사용자가 브라우저에서 확인할 때, 프로브에서, 그리고 캡처에서 모두 동일합니다. 좋은 실행은 시드로 선택하세요 (프로브로 몇 개를 스위핑하여 측정 가능한 기준으로 선택), 캡처를 다시 돌리지 말고. 자세한 내용과 주의점: references/recording-scripts.md.
3. 편집 (scripts/ad-edit.cjs)
컷 리스트(JSON) → 광고 크기의 mp4 (lanczos 다운스케일), CRF 17, AAC, 음량 정규화 (기본값 −14 LUFS, 소셜 피드가 정규화하는 값). 주요 필드:
{
"out": "ad-a", "size": [800, 1000], "speed": 1.111,
"fade": 0, "fadeOut": 0, "audioFadeIn": 0.3, "audioFadeOut": 0.8,
"cuts": [{"take": "stage1", "from": 20.3, "to": 24.6, "passAt": 2}, {"take": "stage3", "from": 37.2, "to": 41.2}],
"music": {"take": "music4", "snap": 4, "gates": true, "speedRange": [1, 1.25], "passAt": 2, "align": 4}
}
speed는 재생 속도. After Effects의 "time stretch 85%"는 속도 1/0.85. 사운드는 피치 유지 (atempo). 빨라짐 → 60 fps로 기록 (프레임 드롭); 느려짐 → 60·speed fps로 기록 (프레임 반복 없음).music은 하나의 연속적인 배경 음악을 배치 (게임 효과음이 음소거된 테이크로 녹화)하여 하드 컷 아래에 깔기 — 몽타주에 컷마다 다른 노래 대신 하나의 노래가 사용됩니다.snap은 컷을 전체 마디로 자름;gates는 각 컷에 자체 속도와 시작을 부여하여 게이트 통과가 배경 음악의 비트에 맞도록 함;passAt은 첫 번째 통과를 컷 시작 후 N 비트에 배치;align은 배경 음악을 동일한 테이크의 컷과 정렬 (클리어와 노래의 케이던스가 일치).
컷이 "맞거나" "안 맞는" 느낌, 그리고 배경 음악 레시피: references/ad-editing.md.
4. 녹화 전용 레벨 스크립트 (선택 사항)
스테이지 자체가 광고에 너무 단조로울 때, 캡처 전용 레이아웃을 추가하세요: 비트별로 배치된 구멍/장애물 (명시적 높이와 특징 포함), 그리고 자동 조종만 통과하는 보이지 않는 웨이포인트 (추가 장애물 없이 추가 조작). 약 5초마다 새로운 특징이 등장하고 첫 번째 특징은 약 5초 내에 도달하도록 속도를 조절하세요. references/recording-scripts.md 참고.
문제점 / 작동하지 않은 것 ("이유" 유지)
- 비디오 + 오디오 트랙이 있는 하나의 MediaRecorder가 webm 헤더 직후 중단됨 (수십 바이트, ffmpeg "EBML header parsing failed"). 같은 틱에 시작된 두 개의 레코더를 ffmpeg로 믹싱하면 작동합니다. ffmpeg의 후행 "Error parsing Opus packet header"는 마지막 부분적 청크로 무해합니다.
- Ctrl-C로 webm은 남고 mp4는 없음. Playwright 자체 SIGINT/SIGTERM/SIGHUP 핸들러가 브라우저를 닫고 먼저 종료 —
handleSIG*: false로 실행하고, 영구process.on("SIGINT")사용 (once리스너는 스스로 제거하고 Node는 두 번째 신호에서 종료). - 헤드리스가 스폰 시 주제를 멈춤 — 하나의 블룸이 많은 Phaser 게임에서 발생; Three.js 게임에서는 GPU가 활성화되면 (
--use-angle=metal|d3d11|vulkan --enable-gpu --ignore-gpu-blocklist) 헤드리스가 정상 작동. 짧은 테이크의 프레임 수 계산 (ffprobe -count_frames) — ~60/s면 실제임. - 과도하게 큰 창. 창을 키우지 말고 내부 렌더링 크기를 높이세요.
- 재시작 없이 리사이즈 → 잘못된 게임플레이 스케일 (캐시된
scaleFactor). - H.264가 홀수 해상도를 거부 —
scale=trunc(iw/2)*2:trunc(ih/2)*2. - Node에서 100ms마다 게임 폴링 → 통과 시간 ±0.1초 — 비트에 맞춰 컷하기에 부적합. 페이지에서 매 프레임 샘플링 (
timeline). - 자동 조종을 다른 시점에 시작하면 같은 시드라도 다른 실행 (공유 PRNG가 레이아웃에 대해 소비되는 방식 변경). 프로브와 캡처에서 동일한 방식으로 시작하고, 스크립트/자동 조종 변경 후 항상 다시 프로브.
- 검은 사각형이 중간에 화면을 덮음, 캡처 해상도에서만. 포인트 스프라이트가 뷰 깊이로 나누어짐 (
size / -mv.z, 스트릭 꼬리xy / w); 카메라 평면을 교차하는 입자가 무한대/NaN 크기의 정사각형 스프라이트를 생성하고, 블룸의 시간적 히스토리가 NaN을 계속 유지. 양쪽 수정: 카메라 평면의 포인트를 건너뛰고 셰이더에서 크기를 클램프; 블룸 체인이 밝은 패스와 히스토리 혼합 시 비유한 값을 대체 (isnan/isinf→ 0, 클램프). 각 포스트 프로세스 버퍼를 읽어 (readRenderTargetPixels)0x7c00지수 하프플로트를 세어 진단. - 조기 중단으로 테이크 손실 — 청크가 매초 디스크로 스트리밍; webm 마스터는 중단된 트랜스코드에서도 생존:
ffmpeg -y -i take.webm -i take.audio.webm -r 60 -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" \ -c:v libx264 -preset slow -crf 16 -pix_fmt yuv420p -c:a aac -movflags +faststart take.mp4
재사용 가능 vs 게임별
- 재사용 가능 (이 스킬): 캡처 하네스 (사운드 분기, 타임라인, 리사이즈 분리, 중단 처리, 트랜스코드), 컷 리스트 편집기 (속도, 페이드, 배경 음악, 비트 고정 게이트, 음량), 궤적 프로브/플롯, 그리고
references/의 방법들. - 게임별 (설정 / 게임): URL 및 쿼리,
ready, 리사이즈 훅, 타임라인 샘플러, 자동 조종 자체, 시드 / 고정 스텝 스위치, 레벨 스크립트.