[Astro #96] CreditBoard汎用化・VRM回転機能追加
はじめに
WIT(グラディウス風STG)で実装済みだったクレジット看板(WITCreditBoard.tsx)を汎用コンポーネントに切り出し、ADV/RPGとパズルゲームにも展開。あわせてトップページのVRMアバターにY軸回転機能を追加。
1. CreditBoard汎用化
課題
WITCreditBoard.tsxがWIT専用のJSON(Accessories, Enemies, Sounds, Avatars)を直接importしており、他プロジェクトで使い回せなかった- クレジット情報がindex.astroのHTMLタグに手書きで列挙されており、メンテが煩雑だった
設計方針
- クレジットデータの構造を
CreditSection[]({ header: string; items: string[] }[])としてprops化 - Canvas描画・UVスクロール・Aフレーム看板の3Dジオメトリはそのまま維持
- 各プロジェクトごとに薄いラッパーを作り、JSONからcreator/authorを抽出して渡す
作成ファイル
| ファイル | 役割 |
|---|---|
CreditBoard.tsx | 汎用コンポーネント本体(R3F + CanvasTexture) |
WITCreditBoard.tsx | WIT専用ラッパー(既存APIを維持) |
ADVCreditBoard.tsx | ADV/RPG専用ラッパー |
PuzzleCreditBoard.tsx | パズル専用ラッパー(木箱GLB付き) |
技術ポイント
CreditSectionはTypeScript interfaceのため、import typeで分離しないとVite + Astro環境でランタイムexportエラーになるCreditBoardのprops:credits,title,scrollSpeed,position,rotation,scale- パズル版は木箱GLBモデル(
low_poly_wood_box.glb)をpuzzleConfig.jsonのmodels配列から自動取得し、その上に看板を配置
2. ADV/RPG — STAGE_MY_ROOMにクレジット看板設置
抽出元JSON → セクション
| セクション | 抽出元 | フィールド |
|---|---|---|
| STAGE MODEL | Stages.json, BattleConfigs.json | author |
| CHARACTER MODEL | Avatars.json | author or creator |
| AUDIO | SoundEffects.json | creator |
ADVManager.tsxへの組み込み
{currentStageId === 'STAGE_MY_ROOM' && (
<ADVCreditBoard
position={[2.37, -0.1, 5.0]}
rotation={[0, 0, 0]}
scale={1.5}
/>
)}
効果
- index.astroの手書きクレジットHTMLを大幅削減(トップページ専用アセットの3件のみ残し)
- JSONの
creator/authorフィールドを書くだけで看板に自動反映される運用に統一
3. パズルゲーム — 木箱+クレジット看板
背景
- エンディングまでプレイするとクレジットが出るが、難易度が高くクリア困難
- 素材提供クリエイターのクレジットが実質見られない状態だった
抽出元JSON → セクション
| セクション | 抽出元 | フィールド |
|---|---|---|
| CHARACTER MODEL | puzzleConfig.json stages[] | creator |
| 3D MODEL | puzzleConfig.json models[] | creator |
PuzzleManager.tsxへの組み込み
<PuzzleCreditBoard
position={isXR ? [0, -1.0, 0.3] : [0, -1.0, 1.5]}
scale={0.55}
/>
- VR/PCでカメラ位置が異なるため、
isXRでZ座標を分岐 - 看板の向きが逆だったため
rotation={[0, Math.PI, 0]}で180°回転
4. VRMアバター Y軸回転機能
課題
- 外部VRMの中にはデフォルトの向きが逆(背面向き)のモデルがある
- VROID STUDIO出力時のエクスポート設定差異が原因と推測
実装
3ファイルに変更を追加:
WiredScene.tsx
avatarRotYstate追加(localStorage永続化)handleRotYChangeハンドラ定義- WiredAvatar, VRMenuManager, WiredAvatarMenu の両方にpropsを透過
WiredAvatarMenu.tsx
- SYSタブに
ROTATION_Yスライダー追加(0〜360°、内部はラジアン管理)
WiredAvatar.tsx
WiredAvatarCoreのreturn内でprimitiveを内側の<group rotation={[0, avatarRotY, 0]}>で包む- 外側の
groupRef(箒飛行・STG制御用)とは独立して回転
ハマったポイント
- primitiveの重複レンダリング: 新しい回転groupを追加した際、元の
primitiveを消し忘れて2体描画されスライダーが効かないように見えた - PC用メニューへのprops渡し漏れ: VRMenuManagerにはpropsを渡していたが、PC用の
WiredAvatarMenu(右クリックメニュー)へのavatarRotY/onRotYChangeが欠落。onRotYChange?.()のオプショナルチェインでサイレントに無視されていた
5. 外部VRM保存の仕組み確認
結論
外部VRMはIndexedDBに正しく永続保存されていた。
保存フロー
handleFileLoad→blob:xxx#name=ファイル名&size=サイズを生成getCachedAssetUrl(assetCache.ts)がblob URLをfetch → バイナリ取得LOCAL_VRM_${fileName}_${fileSize}をキーにdb.modelsへ保存localStorage('wired_model_path')にキー名を記録
リロード時の復元フロー
localStorage→LOCAL_VRM_xxxキーを復元getCachedAssetUrl→table.get(cacheKey)でIndexedDBからBlob取得URL.createObjectURL()で新しいblob URLを生成 → 表示
DevToolsでの確認方法
- IndexedDB → WiredAssetCache → models → ページネーション(▶ボタン)で後半ページに
LOCAL_VRM_*エントリが並ぶ