Vue.js + Cubism SDK For Web を使って、Live2Dモデルを表示・制御するサンプル的なものを作ってみました。
前置き
それぞれの単語について簡単に説明します。
Cubism SDK For Web
Cubism SDK for Webは、Live2D社が提供するソフトウェア開発キット(SDK)の一つで、Webブラウザ上でLive2Dモデルを表示・操作するためのツールです。https://live2d.com
主な特徴:
- WebGL対応:主要なWebブラウザ(Google Chrome、Firefox、Safari、Microsoft Edgeなど)で動作し、幅広い環境で利用可能です。
- TypeScriptで実装:ソースコードはTypeScriptで書かれており、トランスパイルすることでJavaScriptからも扱うことができます。
- サンプル実装の提供:公式のGitHubリポジトリでサンプル実装が公開されており、開発の参考にすることができます。GitHub
Vue.js
Vue.js(ヴュー・ジェイエス)は、ユーザーインターフェースの構築を目的としたオープンソースのJavaScriptフレームワークです。Vue.js
主な特徴:
- 親しみやすさ:直感的なAPIと充実したドキュメントにより、標準的なHTML、CSS、JavaScriptの知識があれば容易に学習・導入が可能です。
- 高パフォーマンス:仮想DOM(Virtual DOM)の採用により、効率的なレンダリングが可能で、Webページの高速な読み込みと実行を実現します。
- 多用途性:ライブラリとしての軽量さと、フル機能のフレームワークとしての拡張性を兼ね備え、シングルページアプリケーション(SPA)の開発にも適しています。
メリット:
- 学習コストの低さ:他のフレームワークと比較して習得が容易で、初心者にも扱いやすいとされています。
- 豊富なエコシステム:公式のルーティング(Vue Router)や状態管理(Pinia)などのライブラリが充実しており、開発を効率化できます。
- 高いパフォーマンス:仮想DOMの採用により、Webページの高速な読み込みと実行が可能です。
Vue.js で機能をコンポーネント化

1つのコンポーネントにすべての機能を詰め込もうとすると、コンポーネントが肥大化してしまい保守性が悪くなります。
そこで、Vue.jsでコンポーネントを機能ごとに分割し、それぞれのコンポーネントが1つの機能を担当するようにしました。
分割するにあたって、以下のような方針を立てました。
オブジェクトの生存期間
Vue3のライフサイクルフック(onMounted
, onBeforeUnmount
, onUnmounted
)を使って、各リソースの初期化と破棄を行うことでリソースの生存期間の管理を容易にします。
コンポーネントの生存期間=リソースの生存期間とすることで、リソースの管理を行いやすくしようという考えです。
また、子要素は v-if
で初期化が完了しているかを確認してからマウント(=初期化処理)を行うようにします。
具体例としてフレームワークの初期化と終了処理を行うコンポーネントは以下のようになります。
<template>
<slot v-if="initialized"></slot>
</template>
<script setup lang="ts">
import { logger } from '@/logger';
import { CubismFramework } from '@framework/live2dcubismframework';
import { onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue';
const initialized = ref(false);
onMounted(() => {
// Live2Dの初期化処理
CubismFramework.startUp()
CubismFramework.initialize()
logger.debug('CubismFramework has been initialized')
initialized.value = true;
});
onBeforeUnmount(() => {
initialized.value = false;
});
onUnmounted(() => {
// Live2Dの終了処理
CubismFramework.dispose()
CubismFramework.cleanUp()
logger.debug('CubismFramework has been disposed')
});
</script>
オブジェクトの共有
Vue3には provide()
と inject()
という仕組みがあり、親コンポーネントから子コンポーネントにオブジェクトを渡すことができます。
これを使うと例えば、親コンポーネントがリソースを管理し、処理は子コンポーネントが行うというように分業することができます。
以下の例では親コンポーネントから子コンポーネントにモデルアセットを provide()
しています。
<template>
<slot v-if="initialized" />
</template>
<script setup lang="ts">
/**
* モデルアセットを提供するコンポーネント
*/
import { provide } from 'vue'
const props = defineProps<{
modelHomeDir: string;
modelFileName: string;
}>()
const initialized = ref(false)
// モデルアセット
const modelAssets = computed<CubismModelAssets>(() => { ... })
// 子コンポーネントで利用するためにモデルアセットを提供
export type ProvidedCubismModelAssets = Ref<CubismModelAssets>;
provide<ProvidedCubismModelAssets>('CubismModelAssets', modelAssets);
// モデルアセットのロード処理
async function loadAssets(modelHomeDir: string, modelFileName: string) {
// ...
}
// マウント時にモデルアセットをロード
onMounted(async () => {
await loadAssets(props.modelHomeDir, props.modelFileName)
initialized.value = true
})
</script>
子コンポーネント側では inject()
を使ってリソースを取得し、各種処理を行います。
以下のように inject()
を使うことで、状態の更新と描画処理を別コンポーネントに分けることが可能になります。
<script setup lang="ts">
/**
* モデルアセットの状態を更新するコンポーネント
*/
import { inject } from 'vue'
// モデルアセットを親コンポーネントから注入
const modelAssets = inject<ProvidedCubismModelAssets>('CubismModelAssets');
// モデルアセットの状態を更新する処理
function update(deltaTime: number) {
const { model } = modelAssets.value;
// ...
model.update();
}
<script setup lang="ts">
/**
* モデルアセットの描画処理を行うコンポーネント
*/
import { inject } from 'vue'
// モデルアセットを親コンポーネントから注入
const modelAssets = inject<ProvidedCubismModelAssets>('CubismModelAssets');
// モデルの描画処理
function render() {
const { model, renderer } = modelAssets.value;
// ...
renderer.drawModel();
}
分けることで処理をカスタマイズしやすくなり、コンポーネントの再利用性も高まります。
変更のウォッチ
Vue3の watch()
を使うことで変数の変更を監視することができます。
以下の例では props
の変更を監視し、変更と同時に表情の切り替え処理を行っています。
<script setup lang="ts">
import { watch } from 'vue'
const props = defineProps<{
index: number; // 表情インデックス
}>()
function startExpression(index: number) {
// ... 表情の切り替え処理
}
watch(() => props.index, (index) => {
startExpression(index)
}))
</script>
このようにすることで、外部からの操作に応じたモデルの制御が容易になります。
コンポーネント構成

私なりにCubism SDK For Webの処理を分解してコンポーネント化していくと、以下のような構成になりました。
<!-- GallaryView.vue の一部 -->
<template>
<VCubismFramework>
<!-- WebGL描画用のCanvasをマウントし、WebGLコンテキストを提供 -->
<VCubismCanvasWebGLProvider class="w-full" style="aspect-ratio: 9/16;" width="720" height="1280">
<!-- プロジェクション行列を提供 -->
<VCubismProjectionMatrixProvider>
<!-- View行列を提供 -->
<VCubismViewMatrixProvider>
<!-- 描画ループを提供 -->
<VCubismRenderLoopProvider :fps="30">
<!-- モデルアセットを読み込み提供 -->
<VCubismModelAssetsProvider @loaded="assetsLoaded" :model-home-dir="modelHomeDir"
:model-file-name="modelFileName">
<!-- モデルの更新処理 -->
<VCubismUpdateModel>
<!-- モーションの更新処理 -->
<VCubismUpdateModelMotion>
<!-- まばたきの更新処理 -->
<VCubismUpdateModelEyeBlink />
</VCubismUpdateModelMotion>
<!-- 呼吸の更新処理 -->
<VCubismUpdateModelBreath />
<!-- 物理演算の更新処理 -->
<VCubismUpdateModelPhysics />
<!-- 表情の更新処理 -->
<VCubismUpdateModelExpression />
</VCubismUpdateModel>
<!-- モデル座標設定用の行列を提供 -->
<VCubismModelMatrixProvider :scale-x="Number(scale)" :scale-y="Number(scale)"
:translate-x="Number(translateX)" :translate-y="Number(translateY)">
<!-- モデルのレンダー処理 -->
<VCubismModelAssetsRenderer />
<!-- ヒットエリアのレンダー処理 -->
<VCubismHitAreaRenderer v-if="showHitBox" />
<!-- ヒットエリアの管理 -->
<VCubismHitManager @hit="onHit" />
</VCubismModelMatrixProvider>
<!-- モーションを管理するコンポーネント -->
<VCubismMotionManager :group="motionGroupName" :index="motionIndex" :loop="true" />
<!-- 表情を管理するコンポーネント -->
<VCubismExpressionManager :index="expressionIndex" />
</VCubismModelAssetsProvider>
</VCubismRenderLoopProvider>
</VCubismViewMatrixProvider>
</VCubismProjectionMatrixProvider>
</VCubismCanvasWebGLProvider>
</VCubismFramework>
</template>
コンポーネント分割とVue.jsのリアクティビリティーを活かすことで、Live2Dモデルの制御を柔軟に行うことができるかと思います。
デモ

Vue.js + Cubism SDK For Webを使って3つのデモを作成しました。
サンプルモデルのギャラリー
Cubism SDK For Webに付属しているサンプルモデルを表示するデモです。
複数のモデルの配置
2つのモデル(Mao, Hiyori)を配置し、それぞれのモデルに対して独立した操作を行うサンプルです。
声と口の動きはVOICEVOX APIを使ってリアルタイムで生成しています。
ノベルゲーム風
ノベルゲーム風の会話シーンを作ってみました。
先程と同じく、声と口の動きはVOICEVOX APIを使ってリアルタイムで生成しています。
会話文と背景はChatGPTに書かせました。
Maoのモデルは ParamA/I/U/E/O
に対応しているので、いい感じに口を動かせますね。
最後に
Vue.jsは主にWebでのユーザーインターフェース構築に使われるフレームワークですが、用途はHTML要素の描画だけに限定されません。
今回の例のようにVue.jsの優れたリアクティビティーやライフサイクルフックを用いると、インスタンスの初期化や破棄、リソースの管理を柔軟に行うことができます。
個人的にVue.jsは非常に柔軟で使いやすいフレームワークだと思っているので、もっと使われて成長していってほしいですね。
コメント