はじめに
Webアプリ上でLive2Dキャラクターを表示し、音声に合わせてリップシンク(口パク)や感情による表情切り替えをリアルタイムに行う実装方法を解説します。Next.js環境でPixiJSを使用して、低遅延かつ自然な表現を目指します。
技術スタック
| カテゴリ | ライブラリ | 役割 |
|---|---|---|
| Framework | Next.js (App Router) | フロントエンド基盤 |
| Rendering | PixiJS v7.x | 2D描画エンジン |
| Live2D Lib | pixi-live2d-display-lipsyncpatch | モデル表示 + リップシンクパッチ |
| Runtime | live2dcubismcore.min.js | Cubism 5 SDK |
全体構成
音声入力から描画までのデータフローは以下の通りです。
- AudioContext (AnalyserNode):マイク入力を解析し、音量レベル(0〜1)を取得。
- React Component (useRef):再レンダリングを避け、値を保持。
- PixiJS Ticker:毎フレーム描画。
- Live2D Model Update:モデルのパラメータ(
ParamMouthOpenY等)を直接上書き。
1. パッケージのインストール
まずは必要なライブラリをプロジェクトに追加します。
pnpm add pixi.js@^7 pixi-live2d-display-lipsyncpatch注意: Cubism Core本体はnpmパッケージに含まれないため、後述のCDNから読み込みます。
2. Live2Dモデルの配置と設定
Live2D Cubism Editorから書き出したデータを public/models/live2d/[キャラクター名]/ に配置します。
リップシンク設定の確認
model3.json 内の Groups セクションで、リップシンク対象となるパラメータID(通常は ParamMouthOpenY)が指定されているか確認してください。
{
"Groups": [
{
"Target": "Parameter",
"Name": "LipSync",
"Ids": ["ParamMouthOpenY"]
}
]
}
3. 実装のポイント:Next.jsでのLive2D表示
Next.js(App Router/Client Component)でPixiJSを扱う際の最大のポイントは、SSR(サーバーサイドレンダリング)の回避とCubism Coreの動的読み込みです。
基本コンポーネント構造
useEffect 内でライブラリを動的インポート(Dynamic Import)することで、ブラウザ環境でのみ実行されるようにします。
"use client";
import { useEffect, useRef } from "react";
const CUBISM_CORE_URL = "https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js";
export function Live2DAvatar({ modelUrl }: { modelUrl: string }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
async function init() {
// Cubism CoreをCDNからロード
if (!(window as any).Live2DCubismCore) {
await new Promise<void>((resolve) => {
const script = document.createElement("script");
script.src = CUBISM_CORE_URL;
script.onload = () => resolve();
document.head.appendChild(script);
});
}
// PixiJSを動的インポート
const PIXI = await import("pixi.js");
const { Live2DModel } = await import("pixi-live2d-display-lipsyncpatch/cubism4");
(Live2DModel as any).registerTicker(PIXI.Ticker);
const app = new PIXI.Application({
view: canvasRef.current!,
autoStart: true,
backgroundAlpha: 0,
resizeTo: canvasRef.current!.parentElement!,
});
const model = await Live2DModel.from(modelUrl);
app.stage.addChild(model);
}
init();
}, [modelUrl]);
return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
}
4. リップシンク(口パク)のリアルタイム実装
音声解析で得た音量レベルをモデルに反映させます。Reactの useState は使用せず、useRef を使ってTicker内で値を更新するのがパフォーマンス上の鍵です。
モデルのupdateメソッドの上書き
Live2Dのモーションに口パクを打ち消されないよう、internalModel.update の後にパラメータを注入します。
const origUpdate = model.internalModel.update.bind(model.internalModel);
model.internalModel.update = function (dt: number, now: number) {
origUpdate(dt, now); // モーション適用
// audioLevelRefから取得した値を反映(2.5倍程度で調整)
const lipValue = Math.min(audioLevelRef.current * 2.5, 1);
this.coreModel.setParameterValueById("ParamMouthOpenY", lipValue);
};
パラメータ倍率の目安
| 倍率 | 視覚的な効果 |
|---|---|
* 1.5 | 控えめで上品な動き |
* 2.5 | 自然で会話らしい動き(推奨) |
* 4.0 | デフォルメの効いた大きな動き |
5. 感情タグによる表情切り替え
LLM(GeminiやChatGPT等)からの応答に感情タグ(例:[happy])を含め、それをパースしてLive2Dの表情パラメータを操作します。
代表的なパラメータID
| パラメータ名 | 役割 | 範囲 |
|---|---|---|
ParamEyeLSmile | 笑顔の目 | 0 〜 1 |
ParamBrowLY | 眉の上下 | -1 〜 1 |
ParamMouthForm | 口の形(への字〜笑顔) | -1 〜 1 |
実装時の注意点
- Cubismバージョンの使い分け:
pixi-live2d-displayはCubism 2(旧)と4/5(現)でインポート先が異なります。現代のモデルなら/cubism4サブパスを使用してください。 - アンカーポイントとスケール: モデルによってサイズがバラバラです。表示位置がずれる場合は
model.anchor.set(0.5, 0.35)などで調整が必要です。 - パラメータの優先順位: モデルの
updateを上書きしない場合、設定した口パクがモーションによって毎フレームリセットされるため「動かない」原因になります。
まとめ
Next.jsでLive2Dアバターを構築する際は、「サーバーサイドを避ける」ことと「Reactのライフサイクルとは別の時間軸(Ticker)でモデルを動かす」ことを意識すると、非常に滑らかでインタラクティブな体験を構築できます。