|

Claude Code の1回のセッションで2.5Dパララックスアニメーションスタジオを構築:3つのコアコードでフレーム単位の決定論的MP4エクスポートを実現

Claude Code の1回のセッションで2.5Dパララックスアニメーションスタジオを構築:3つのコアコードでフレーム単位の決定論的MP4エクスポートを実現

作者注:プロンプト、被写界深度の補正式から Playwright+ffmpeg によるフレーム単位の書き出しまで、Claude Code を使って「書き出してもコマ落ちしない」2.5D パララックスアニメーションスタジオをどう作るのか、全体を分解して解説します。

AI コーディングアシスタントで初めてアニメーションを作ると、ブラウザ上のプレビューはなめらかなのに、動画として書き出した途端にカクつきやコマ飛び、尺のずれが起きる、という経験をした人は多いのではないでしょうか。原因はたいてい設計にあります。最初の段階で「書き出し」を設計の一部として考えていなかったのです。この記事では、純粋関数 renderAt(t) + フレームごとの seek スクリーンショット という一本の軸に沿って、Claude Code の1回のセッションで 2.5D パララックスアニメーションスタジオを組み上げ、MP4 を安定して書き出す方法を解説します。

よくある「AI にアニメーションを書いてもらおう」系のデモ記事ではありません。被写界深度の補正式の導出、典型的なオフセットのバグ、そしてリアルタイム再生とフレーム単位の書き出しの本質的な違いまで、丁寧に分解していきます。

この記事で得られるもの:再利用できるプロンプトの構成、再現可能な書き出しパイプライン、そして他のプログラム生成型の動画プロジェクトにも応用できる検証チェックリストが手に入ります。

claude-code-2-5d-parallax-motion-studio-deterministic-mp4-export-ja-image-0


Claude Code 2.5D パララックスアニメーションスタジオの要点

参考にした実践例は、2026-11-09 に公開された記事のものです。著者は Claude Code と Opus 5.5 を組み合わせ、たった 1 回のセッションで単一ファイルの HTML スタジオと、25 行ほどの Playwright エクスポートスクリプトを作り上げました。著者のノートPCでは、6 秒・720p の動画(180 フレーム)が約 20 秒で書き出せたそうです。ただし、ハードウェア構成は明かされていないため、この所要時間はあくまでおおよその目安として見てください。

要点 説明 価値
シーングラフ(Scene Graph) 各レイヤーが z 深度、サイズ、位置、HTML コンテンツを持つ 3D モデルがなくても奥行き感を出せる
タイムライン(Timeline) カメラとレイヤーのキーフレーム。区間ごとにイージングカーブを設定 カメラワークを編集・再利用できる
決定論的レンダラー renderAt(t) は任意の時刻 t に対して唯一の画面を出力する 書き出し結果がマシンの速度に左右されない
深度補正スケーリング サイズとオフセットの両方に 1 - z/perspective を掛ける 静止時は書いたとおりに表示され、動かすと自然に視差が生まれる
フレーム単位のエクスポートパイプライン Playwright が i/fps へ seek してスクリーンショットを撮り、ffmpeg にエンコードを任せる どのマシンでも同じ出力になり、コマ落ちしない

2.5D パララックスアニメーションの原理

2.5D は、平面的な 2D 素材を 3D 空間のさまざまな深さに配置し、その中を仮想カメラが通り抜けるという手法です。カメラに近いレイヤーは速く、遠いレイヤーはゆっくり動きます。人間の目はこの速度差を奥行きとして認識します。これがパララックス(視差)です。モデリングもライティングも 3D エンジンも必要なく、ブラウザの CSS perspective と preserve-3d だけで十分です。

「スタジオ」と「使い捨てのアニメーション」の違いは、3 つの部品が揃っているかどうかにあります。シーングラフ、タイムライン、そして決定論的レンダラーです。多くのチュートリアルは 3 つ目を飛ばしていますが、これこそ書き出した MP4 がカクつくかどうかを左右する部分です。そのため、最初のプロンプトを書く段階で、必須要件として明記しておく必要があります。

claude-code-2-5d-parallax-motion-studio-deterministic-mp4-export-ja-image-1


Claude Code 2.5D スタジオの環境とモデル設定

参考資料では、最初のビルドには Opus 5.5 を使うことが推奨されています。Anthropic 公式のモデル概要では、このモデルは「長時間稼働するエージェント型のプログラミングとナレッジワーク」向けと位置づけられており、コンテキストウィンドウは 1M トークン、最大出力は 128K です。公式の価格は入力 / 出力 100 万トークンあたり 4 / 20 ドルですが、実際の価格は必ず公式ページでご確認ください。

プロジェクトの途中でエイリアスが切り替わって挙動が変わるのを避けるため、起動時に具体的なモデル ID を固定しておくのがおすすめです。

claude --model claude-opus-5-5 --effort high

Opus 5.5 のデフォルトは medium effort です。最初のビルドでは、エージェントが投影の数学、キーフレームエンジン、エクスポートパイプラインを一度で正しく書き上げる必要があるため、high に上げておくことをおすすめします。その後、キーフレームの数値や色を変えるといった小さな修正だけであれば、medium に戻してトークンを節約できます。

フェーズ 推奨 effort 代表的なタスク 理由
最初のビルド high 投影の数学、キーフレームエンジン、エクスポートスクリプト 複数のサブシステムを一度に整合させる必要がある
反復での微調整 medium キーフレーム調整、配色変更、レイヤー追加 変更範囲が小さく、ロジックはすでに安定している
「カメラ記述」インターフェース low(開始時) 固定スキーマの JSON キーフレームを返す タスクが短く、出力構造が固定されている

他のモデルで同等のビルドを行った場合の効果やコスト差については、参考資料に比較データがありません。モデルを切り替える場合は、同じプロンプトでそれぞれ実行し、後述の静止フレーム検証法で項目ごとに比較することをおすすめします。


Claude Codeで2.5Dスタジオを作るためのプロンプト構造

具体的に書いたプロンプト1つは、曖昧なプロンプト5つよりも良い結果を生みます。参考資料にある構築仕様は1つにまとまった長めのプロンプトでした。以下は、元の意図を保ったバージョンです。

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.

このプロンプトは、シーン、カメラ、トラックとイージング、インタラクションUIの4つの層に分けられ、最後にエクスポートの契約が加わります。結果を本当に左右するのは、最後の2行です。renderAt(t) を t に対する純粋関数にすること、「描画が終わってから返る」__seek を公開すること、UIを隠す ?capture モードを用意することを求めています。こうすると、エクスポート機能がアーキテクチャ上の制約になり、エージェントはその制約に沿ってコードを設計するようになります。

自分のプロジェクトに応用するなら、この構造をテンプレートにできます。前半で「何を描くか」をはっきり書き、最後の2〜3行で「どうやって決定論的にフレームを取得するか」を厳密に指定してください。


Claude Codeが生成した2.5Dコアコードの解説

スタジオ全体で約150行ほどです。ポイントになるのは、次の3つのパートです。

キーフレームのサンプリングとイージング

各トラックは、時間順に並んだキーフレームの配列です。sample() はまず t の前後にある2つのキーフレームを探します。次に前のキーフレームのイージング関数で進行度を求め、すべての数値フィールドを補間します。

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];
}

この関数は、現在時刻を読み取りません。前フレームの状態にも依存せず、乱数も使いません。同じ t を渡せば、いつでも同じ数値が返ります。renderAt(t) が決定的に動作するのは、この性質のおかげです。

奥行き補正:2.5D視差の核となる数式

ここでの考え方は、カメラは動かさず、世界全体を逆方向に動かすというものです。各レイヤーは z 軸方向に平行移動するだけでなく、補正用の拡大率も掛け合わせます。

const PERSPECTIVE = 1200; // #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 の透視距離を p とすると、深度 z の点(z が負の場合は観察者から遠ざかる方向)は、画面上に投影される際に p / (p - z) 倍に拡大縮小されます。p = 1200、z = -900 を代入すると 1200 / 2100 ≈ 0.571 となり、レイヤーは元のサイズの約 57% にまで小さくなります。

この縮小を打ち消すには、逆数の (p - z) / p = 1 - z/p = 1 + 900/1200 = 1.75 を掛けます。コード中の comp がこの値です。0.571 × 1.75 = 1 なので、カメラが静止しているときは、どのレイヤーも設定したサイズと位置どおりに表示されます。

では、視差はどのように生まれるのでしょうか。カメラが動くと、world 全体が同じ dx だけ平行移動しますが、各レイヤーの画面上での移動量には、それぞれの p/(p - z) が掛かります。遠くのレイヤーは係数が小さいので移動量が少なく、近くのレイヤーは係数が大きいので移動量が多くなります。補正が整えるのは静止状態の構図だけで、動いているときの速度差には影響しません。2.5D の効果は、まさにこの速度差によって成り立っています。

レイヤー z 投影スケール p/(p−z) 補正係数 comp カメラ平行移動時の相対速度
0(タイトルカードの平面) 1.000 1.000 基準
-300 0.800 1.250 やや遅い
-900 0.571 1.750 かなり遅い
-1800 0.400 2.500 背景がほぼ静止して見える

(表中の数値は p = 1200 で計算しています。-300 と -1800 の行は例として導出したものです。)

覚えておきたいバグ:サイズだけ補正して、オフセットを補正し忘れた

最初のバージョンのコードでは、レイヤーに scale(comp) を掛けただけで、x/y のオフセットには comp を掛けていませんでした。その結果、遠景の山のオフセット量が遠近法によって圧縮され、構図全体がずれてしまいました。山がどんどん高くなり、空のおよそ3分の2を覆い隠してしまったのです。修正方法は簡単で、x と y にも comp を掛けるだけです。遠近法が位置とサイズに与える縮尺は、同じ係数だからです。

さらに注目したいのは、このバグがどうやって見つかったかです。作者はレンダリング結果を1フレーム見ただけで問題に気づきましたが、コードを読んでいるときには見抜けませんでした。そのため、カメラワークを信頼する前に、まず agent に開始・中間・終了の3枚の静止フレームをレンダリングさせましょう。

claude-code-2-5d-parallax-motion-studio-deterministic-mp4-export-ja-image-2


2.5D パララックスアニメーションをフレーム単位の決定論的処理で MP4 に書き出す

リアルタイム再生とフレーム単位エクスポートの本質的な違い

リアルタイムプレビューでは requestAnimationFrame を使いますが、マシンの負荷が高くなるとフレームが落ちます。プレビュー段階ならこれでも問題ありません。ただし、エクスポートではフレーム落ちが許されません。画面録画を使うと、録れるのは「実時間のなかでたまたま画面に表示されていたもの」です。1フレーム遅れるだけで、重複フレームやコマ抜けになってしまいます。

観点 リアルタイム再生(rAF / 画面録画) フレーム単位の seek エクスポート
時間の基準 システムクロック フレーム番号 i / fps
マシンが忙しいとき フレーム落ち、重複フレームが発生 処理が遅くなるだけで、映像は変わらない
マシン間の一貫性 一致しない どのマシンでも同じ出力になる
フレーム数 制御できない 厳密に duration × fps と一致
向いている用途 編集時のプレビュー、タイムラインのドラッグ 最終的な完成動画、CIでの自動書き出し

最小構成のエクスポートスクリプト

エクスポーターの時間は、フレーム番号で進めます。各フレームでまず seek し、描画が完了するのを待ってからスクリーンショットを撮り、その PNG を ffmpeg の標準入力へ書き込みます。

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) は、アニメーションフレームを2回待ってから resolve します。こうすることで、スクリーンショットには必ず新しい transform が反映されます。image2pipe を使うと、ffmpeg は stdin から PNG シーケンスを読み込めます。yuv420p を指定しておけば、生成されたファイルをブラウザで問題なく再生できます。Playwright にまだブラウザをインストールしていない場合は、先に npx playwright install chromium を一度実行してください。

__seek と静止フレーム検証の参考実装を見る(サンプルコード)
// studio.html 内:決定的な seek。2フレーム描画してから返す
window.__seek = (t) => new Promise(resolve => {
  renderAt(t);                       // 純粋関数:t のみに依存
  requestAnimationFrame(() =>
    requestAnimationFrame(resolve)); // スタイルが確定し描画されたことを保証
});
// stills.mjs:開始・中間・終了の3枚の静止フレームを出力し、カメラワークを信頼する前にまず画像を確認する
import { chromium } from 'playwright';
import { pathToFileURL } from 'node:url';

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 } = await page.evaluate(() => window.__scene);

for (const [name, t] of [['start', 0], ['mid', duration / 2], ['end', duration - 1e-3]]) {
  await page.evaluate(t => window.__seek(t), t);
  await page.screenshot({ path: `still-${name}.png` });
}
await browser.close();

上の2つのコードは、参考資料の考え方に沿って書いたサンプル実装です。実際には、エージェントがあなたのプロジェクト内で生成したコードを優先してください。

筆者の実測結果は次のとおりです。180フレーム、1280×720、30 fps、長さ6.0秒で、出力された MP4 は約 420 KB、所要時間は合計で約20秒でした。映像では、街のタワーの移動量が暗い前景の丘より小さく、前景の丘は最終的に画面の外へ完全に出ていきます。これがまさに視差効果です。

claude-code-2-5d-parallax-motion-studio-deterministic-mp4-export-ja-image-3


2.5Dスタジオに「ショット説明」機能を追加する

次は、脚本家がカメラショットの説明を一文入力すると、モデルがキーフレームのJSONを返す機能を作ります。参考資料では、Anthropic 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." }]
  }'

ここでつまずきやすいポイントが2つあります。1つ目は、effortを output_config.effort の中に書く必要があることです。2つ目は、Opus 5.5は常に適応型の思考(adaptive thinking)を使うため、thinking: {"type": "disabled"} を渡すと400エラーが返ることです。しかも思考トークンも max_tokens に含まれます。max_tokens を小さくしすぎると、JSONが配列の途中で切れてしまい、stop_reason が "max_tokens" になります。

途中で切れたレスポンスや余計な文章が混ざったレスポンスは、レンダリング時に sample() でエラーを起こします。そのため、この呼び出しにはコントラクトテストを用意しておくのがおすすめです。参考資料では、Apidog CLIを使い、同じClaude Codeセッション内でテストを作成して実行しています。アサーションは次のとおりです。

  • ステータスコードが200であること
  • stop_reason が "max_tokens" でないこと
  • 返ってきたテキストがJSONとしてパースできること
  • camera 配列の t が0から6まで増加していくこと

ほかのAPIテストツールや、自作のアサーションスクリプトでも同じ目的は達成できます。system promptやeffortを変更すると、どちらも返ってくる内容が変わるため、変更のたびにテストを再実行してください。


よくある質問 FAQ

Q1:2.5D パララックスアニメーションと本物の 3D アニメーションは何が違うのですか?

2.5D は、平面的な 2D 素材をそれぞれ異なる深度に配置し、動くカメラで撮影することで、速度差から奥行き感を生み出します。3D モデルやライト、3D エンジンは不要で、ブラウザの CSS だけで実現できます。その代わり、カメラが大きな角度でレイヤーの裏側へ回り込むことはできません。

Q2:画面録画ではなく、1 フレームずつ seek してスクリーンショットを撮るのはなぜですか?

画面録画は実時間に沿って進むため、どこか 1 フレームでも処理が遅れると、コマ落ちや重複フレームになります。`i / fps` で seek してからスクリーンショットを撮る方法なら、各フレームの内容はフレーム番号だけで決まります。そのため、どのマシンで実行しても同じ出力が得られます。

Q3:補正係数 1.75 はどのように求めたのですか?

透視距離が 1200、深度が -900 のとき、投影スケールは 1200/2100 ≈ 0.571 になります。補正にはその逆数 `1 – z/p = 1 + 900/1200 = 1.75` を使います。両者を掛けると 1 になるため、静止時にはレイヤーが設計どおりのサイズと位置に戻ります。

Q4:初回のビルドで Opus 5.5 と high effort を勧めるのはなぜですか?

初回ビルドでは、投影の数式、キーフレームエンジン、エクスポートパイプラインを 1 回のセッションでまとめて書き上げる必要があります。参考資料では、プロンプト 1 回と修正 1 ラウンドで動作するスタジオが完成しました。以降の小さな修正なら、medium 以下で十分です。他のモデルに切り替えた場合の効果については、現時点で公開されている比較データがないため、ご自身で検証してください。

Q5:API Key がなくてもこのスタジオを使えますか?

使えます。スタジオ本体も MP4 エクスポートもローカルで動作するため、Key は不要です。Messages API を呼び出す「カメラワークの説明」機能だけは、ご自身の Anthropic API Key が必要になります。なお、Claude Code でビルドする際に消費するトークンは、利用中のアカウントの課金方式に従って精算されます。

Q6:この方法には既知の制限がありますか?

1 フレームずつ PNG でスクリーンショットを撮る方式は、解像度やページの複雑さによって速度が変わります。参考資料にある 20 秒という所要時間は、ノート PC 1 台での 1 回分の結果にすぎません。また、CSS 3D は、大きな yaw 角度や半透明レイヤーの重なりの描画が、ブラウザのバージョンによって異なる場合があります。モデル ID、API バージョンヘッダー、effort パラメータには有効期限があるため、使用前に公式ドキュメントで確認してください。

Q7:「決定論的レンダリング + 静止フレーム検証」を他のプログラム生成動画プロジェクトに応用するには?

次のチェックリストで、項目ごとに確認できます。

– レンダリング関数は t だけを受け取り、システムクロックを読まず、前のフレームの状態にも依存せず、シードを固定していない乱数も使わない
– 「描画が完了してから返る」seek インターフェースを用意する
– UI を隠す capture モードと、固定のビューポートサイズを用意する
– 出力フレーム数を `round(duration × fps)` に厳密に一致させる
– カメラワークを変更したら、まず開始・中間・終了の 3 枚の静止フレームをレンダリングして確認し、問題がなければ全編をエクスポートする
– モデルが生成した JSON のキーフレームに対して、パース可能であること、時刻が単調増加していること、途中で切れていないことを確認するコントラクトテストを追加する


Claude Code 2.5D パララックスアニメーションの重要ポイント Key Takeaways

  • エクスポートも設計の一部:プロンプトの最後の数行に、純関数 renderAt(t) と seek の契約を明記しておく
  • 奥行き補正の公式:comp = 1 − z/perspective。サイズと x/y のオフセットの両方に掛ける
  • パララックスはどこから生まれるのか:補正が整えるのは静止状態の構図だけ。動かすと、各レイヤーは奥行きに応じて異なる速度で見える
  • カメラワークを信じる前に静止フレームを見る:開始・中間・終了の3枚を確認すると、コードを読むだけでは気づけないバグが見つかる
  • 時間はフレーム番号で駆動する:Playwright の seek とスクリーンショットを使い、ffmpeg の image2pipe で H.264 にエンコードする
  • effort は段階的に調整する:初回構築は high、微調整は medium、構造化 JSON インターフェースは low から始める
  • モデル出力には契約テストを加える:max_tokens による出力の切り詰めで、キーフレームの解析が失敗するのを防ぐ

まとめ

Claude Code で 2.5D パララックスアニメーションスタジオを作るうえで、核になるポイントは次の3つです。

  1. アーキテクチャ:シーングラフ、タイムライン、決定論的レンダラーの3点セット。完成した映像の品質を左右するのは3つ目です
  2. 数学:遠近補正係数 1 - z/p をサイズとオフセットの両方に適用します。静止時は構図が正確になり、動かせば自然にパララックスが生まれます
  3. パイプライン:i/fps に従ってフレームごとに seek し、スクリーンショットを ffmpeg に渡してエンコードします。出力はマシンの性能に左右されません

まずは本記事のプロンプト構成をなぞって、6秒のサンプルを一通り動かしてみてください。そして「カメラワークを変えるときは、まず静止フレーム3枚を確認する」という習慣をつけましょう。その後は、レイヤーを増やしたり、「カメラワークを記述する」インターフェースをつないだり、同じ決定論的な考え方を Remotion などの他のプログラマブルな動画制作の仕組みに応用したりできます。


📚 参考資料

  1. How to Build a 2.5D Motion Studio With Opus 5.5:本記事の実践の元ネタ
  2. リンク: apidog.com/blog/build-2-5d-motion-studio-opus-5-5
  3. 説明: 完全なプロンプト、スタジオのコード、エクスポート用スクリプト、「カメラワークを記述する」APIの例が掲載されています

  4. Anthropic のモデルと effort に関するドキュメント:モデルの位置づけ、コンテキスト、料金、effort パラメータの説明

  5. リンク: docs.anthropic.com
  6. 説明: claude-opus-5-5 のモデルID、APIバージョンヘッダー、output_config の使い方を確認できます

  7. ffmpeg フォーマットドキュメント:image2pipe などの入力フォーマットの説明

  8. リンク: ffmpeg.org/ffmpeg-formats.html
  9. 説明: stdin から PNG シーケンスを読み込む方法と、H.264 のエンコードパラメータを理解するのに役立ちます

  10. Playwright ドキュメント:ブラウザ自動化とスクリーンショットAPI

  11. リンク: playwright.dev
  12. 説明: page.evaluate、page.screenshot、ブラウザのインストールコマンドを確認できます

関連記事

類似投稿