Voxel アニメーション
ブロック構築ゲームのようなシーンを、そのゲームの資産を一切使わずに作成します。 すべてのテクスチャはコードで実行時に生成され、キャラクターは独自デザインです。 出力は単一の自己完結型HTMLページ(インタラクティブ、共有可能)と、必要に応じて そのページからレンダリングされたフレーム単位の動画です。
このスキルに含まれるファイル
assets/voxel-kit.js: キット(テクスチャ、マテリアル、アプリループ、オービットカメラ、 昼夜のある空、島地形、スタジオプラットフォーム、キャラクターリグ、パーティクル、ポップイン、爆発、 松明、ルーティングヘルパー、レコーダー、キャプチャモード)。シーンを書く前に必ず読んでください。 以下のAPIリファレンスは要約であり、ファイルが正本です。assets/template.html: キットマーカー、島、キャラクター、小さなタイムラインを含む開始ページ。examples/house-builder.html: ビルダーがブロック単位で家を設置し、歓声、爆発、ループ。examples/melting-crystal.html: 物理の例(6×6×6の氷格子のリンデマン融解、ボクセル温度計、 水たまり、再凍結)。科学解説の構造をコピーしてください。scripts/inline_kit.py: ページ内の<!-- VOXEL_KIT -->をキットに置き換え、単一ファイルにします。scripts/render.py: PNGフレームおよび.mov/.mp4/.webmへのフレーム単位レンダリング。
このSKILL.mdのみが利用可能でフォルダがない場合は、ビルド前に https://github.com/normix93/voxel-animations から完全なスキルを入手してください。
ワークフロー
- アイデアを一文で固めます: 何を構築し、変換し、説明するか、ループがどう終わるか (リセット、爆発、フリーズ、カメラのフライアウト)。出力先を決めます: インタラクティブページ、編集用動画(透明背景あり/なし)、縦型ショート(1080×1920)。
- タイムラインとしてストーリーボード: 3~6ビートとその尺、例:
0–2秒 空き地 → 2–40秒 建設 → 40–44秒 歓声 → 44–49秒 爆発 → ループ。各ビートはapp.onUpdateステートマシン内の一つのmodeです(家の例を参照)。 assets/template.htmlから開始し、キットマーカーを維持し、SCENEセクションを記述します。 キットの部品を再発明せずに再利用します。すべてを1ページに収めます。- ページをビルド:
python3 scripts/inline_kit.py scene.src.html scene.html。 - 確認: 開く(または
render.pyで数フレームをフォルダにレンダリングして確認)。 以下の点を確認: ブロックの浮遊や重なり、キャラクターが壁を通り抜ける、カメラが木にクリップする、 最初の数秒が空、透明ブロックのちらつき。 - 納品: 環境がサポートする場合はページをアーティファクトとして公開し、.htmlファイルも 引き渡します; 依頼があれば動画をレンダリングします(エクスポート を参照)。
スタイルルール
- オリジナルのみ。 既存のゲームのテクスチャ、サウンド、ロゴ、モブ、キャラクター、名前を 決して使用しないでください。出力を「Minecraft」と呼んではいけません。voxel、ブロックワールドと 言ってください。キット内のプロシージャルテクスチャとヘルメットをかぶったビルダーは安全に使用・ 変更できます。
- ピクセルパーフェクト: 16×16テクスチャ、
NearestFilter使用(キットが行います)。 強い光、明確な影、マテリアルあたりの色数は少なく、小さなノイズ。 - モーションには重み: ブロックはオーバーシュートでポップイン(
app.pop)、デブリは重力 (約20–22 blocks/s²)と減衰バウンスを使用、キャラクターは約5 blocks/sで歩き、スムーズに 旋回します。物理的なものにリニアトゥイーンは避けてください。 - 一目で読める: 中央に1つの主要被写体、カメラはゆっくり軌道(
autoSpeed0.05–0.1)、 背景は落ち着いたもの(スカイまたはstudio色)。 - テキスト: ページにタイトルとステータス行を付けられます。動画の場合は
?cleanで UIなしでレンダリングします。動画内にラベルが必要な場合は、非常に短い単語を使用; 説明はナレーションまたはページに属し、フレームには含めません。
キットAPI (要約)
const app = VK.createApp({
title, subtitle, hint, // UIテキスト (?clean またはキーHで非表示)
sky: 'day' | 'night' | 'studio', // studio = フラットカラー (studioColor)、フォグなし
cycle: false, dayLength: 48, tod: .27, // 時刻 0..1 (.25 夜明け側, .75 夜)
camera: { radius, phi, theta, target: [x,y,z], auto: true, autoSpeed: .07 },
speed: 1, seed: 7, fileName: 'my-scene', shadowSize: 28,
});
app.M.grass | dirt | stone | sand | planks | roof | log | leaves | glass | water | ice | snow | cobble | brick | lava
VK.solid(0xff8800, { glow, opacity, noise, border }) // 色付きピクセルブロックマテリアル
VK.block(app, 'planks' | material, x, y, z, { pop: true }) // 単一メッシュ (移動/ポップ)
VK.instanced(app.scene, material, [[x,y,z], ...]) // 静的ブロック、1回のドローコール
const world = VK.island(app, { seed, radius: 18, flatRadius: 7.5, trees: 10, water: -1, snowLine })
// → world.groundTopAt(x, z), world.hAt(ix, iz)
VK.platform(app, { size: 16, top: .5, mat: 'stone', topMat: 'cobble' }) // スタジオ床
const hero = VK.character(app, { ground: world.groundTopAt, position: [x,0,z], yaw, colors: { shirt:[r,g,b], hat: 0xf5c518 | null, ... } })
hero.goTo([[x,z], ...]) · hero.arrived · hero.face(x, z, dt) · hero.jump(v)
hero.pose = 'idle' | 'reach' (set hero.reach 0..1) | 'cheer' | 'panic' | 'wave'
hero.lookTarget = {x,y,z} | null
VK.standOutside(x, z, { halfX, halfZ, cx, cz }) // ブロック/ボックスに作業するための立ち位置
VK.routeAround(from, to, { halfX, halfZ, cx, cz }) // そのボックスを迂回するウェイポイント
VK.explode(app, meshes, { center, power, ground, onDone })
VK.torch(app, x, y, z) // 夜に光が強くなる
app.puff(position, material, n, power) · app.pop(mesh) · app.clearParticles()
app.onUpdate((dt, time) => { ... }) // dt は speed で既にスケーリング済み; キャプチャモードでは決定論的
app.rand() // シード付き乱数: Math.random の代わりにこれを使用
app.status(html) · app.button({ label, key, toggle, on, onClick }) · app.standardButtons({ cycle })
app.start()
シーンを正しく保つルール:
app.rand()(シード付き) を使用し、決してMath.random()を使わない。レンダリングを再現可能にします。- すべてのモーションは
app.onUpdate内でdtを使用して配置; アニメーションにsetTimeout/setIntervalを使わない(キャプチャモードが壊れます)。 - ブロックは1×1×1で整数座標に中心; 高さ
hの柱は上面がh + 0.5。キャラクターはgroundTopAt(x, z)に立ちます。 - 静的な背景 →
VK.instanced; 移動、ポップ、爆発するもの →VK.block。 - 大量のカウント: 移動メッシュは約1500以下に保つ; それを超える場合は InstancedMesh でアニメーション。
レシピ
- 建設シーケンス: 順序付き計画
[{type,x,y,z}]を作成(層ごとに、周囲を歩き、 屋根は蛇行行)。各アイテムに対して:standOutsideにルート、pose='reach'に設定、 リーチの約45%でスポーン、次へ。examples/house-builder.htmlを参照。 - ブロックで物理を説明: 量を何か見えるものにマッピング。温度 → ジッター振幅 ∝ √T (等分配則);
圧力 → ブロック衝突; エネルギーレベル → 塔の階層; 場 → ブロックの矢印; エントロピー →
順序だった壁のシャッフル; 重力 → 支えがないと倒れる積み重ね。画面上の数字ではなく、ボクセルゲージ
(
melting-crystal.htmlの温度計)で制御変数を表示。 - 変換:
mesh.materialを変更(水水↔氷、石→溶岩)、scale.yで平らに、2つの位置間を イージング付きlerpでスライド。 - 原子/分子: 粒子には
VK.solid(色)の立方体、電子や光子にはglow: true、 小さな立方体 (mesh.scale.setScalar(.3)) を軌跡に。 - ループするエンディング: 爆発 (
VK.explode) → リセット、またはフリーズ+スローカメラ、 または夜が来る (app.sky.cycle = true)+松明。
エクスポート
URLパラメータ (キットで構築された任意のページで動作):
?clean UI非表示 · ?alpha 透明背景(スカイ、フォグなし) · ?seed=3 ·
?speed=2 · ?night · ?capture&fps=30 レンダラーが使用するフレーム単位モード。
キー: H UI非表示 · Space 一時停止 · V クイックリアルタイムWebM録画
(ローカルファイルから開いた場合に動作; ホストされたプレビューではダウンロードがブロックされる可能性あり)。
フレーム単位動画 (編集に推奨):
pip install playwright && python3 -m playwright install chromium # 一度だけ、ffmpegも必要
python3 scripts/render.py scene.html --seconds 12 --fps 30 --out scene.mov # ProRes 422 HQ
python3 scripts/render.py scene.html --seconds 12 --alpha --out overlay.mov # アルファ付き ProRes 4444
python3 scripts/render.py scene.html --size 1080x1920 --warmup 3 --out short.mp4 # 縦型 H.264
python3 scripts/render.py scene.html --seconds 5 --out frames/ # PNGフレームのみ
--warmup は録画開始前のシーン秒数をスキップ; --params "seed=3&night" はURLパラメータを追加;
--headed は可視ブラウザを使用(実際のGPU、ほとんどのラップトップで高速)。--alpha でも
カメラは軌道運動します; 静的なオーバーレイの場合は camera: { auto: false } を設定。
納品前に
- ページがコンソールエラーなしで開く; 最初のフレームですでに被写体が表示される。
- ループが少なくとも2回動作する(リセットがメッシュ、ライト、パーティクルをクリアする)。
- キャラクターが壁を横切らない; 意図していない限り何も浮かない。
- 動画の場合:
?cleanでレンダリング、正しいサイズ/FPS、アルファが要求されていれば確認済み。 - サードパーティのゲーム名、テクスチャ、キャラクターがコード、タイトル、ファイル名のどこにもない。