😇

Vite+VueでLive2Dモデルを表示し、アイトラッキングを止めつつクリックイベントのみを残す

2024/08/24に公開1

初めに

先駆者兄貴の情報により、公式CubismSDKの何をどう調べたらいいのか分からない問題を回避しつつ、非常に簡便な記述でLive2Dモデルを制御できるようになった
先駆者兄貴はNuxt環境だけど、公式SDKがViteを使えとか書いてた気がするし書いてなかったような気もするのでVite+Vue環境でチャレンジすることにした。大体一緒でしょ多分知らんけど

描画コンポーネント作成

先駆者情報に則り、環境をクローン

pixijs@6.5.10
pixi-live2d-display

紆余曲折あって完成したコンポーネントがこれ

<template>
	<canvas id="2d"></canvas>
</template>

<script setup>
import { onMounted } from 'vue';
import * as PIXI from 'pixi.js';
import { Live2DModel } from 'pixi-live2d-display';
import { MotionPreloadStrategy } from 'pixi-live2d-display';
import { HitAreaFrames } from 'pixi-live2d-display/extra';

window.PIXI = PIXI;

onMounted(async () => {
	const app = new PIXI.Application({
		width: window.innerWidth,
		height: window.innerHeight,
		backgroundColor: 0xDFF1F6,
		view: document.getElementById('2d'),
		backgroundAlpha: false,
	});
	document.body.appendChild(app.view);

	const model = await Live2DModel.from('/Resources/Hiyori/Hiyori.model3.json', {
		motionPreload: MotionPreloadStrategy.None,
		autoInteract: false
	});


	// タップイベントをcanvasに追加
	app.view.addEventListener('pointerdown', event => {
		// InteractionDataを使って座標を取得
		const rect = app.view.getBoundingClientRect();
		const x = event.clientX - rect.left;
		const y = event.clientY - rect.top;

		console.log("Pointer down event triggered", x, y);
		model.tap(x, y);
	});


	app.stage.addChild(model);
	model.x = 300;
	model.y = 100;
	model.scale.set(0.1, 0.1);
	model.anchor.set(0.6, 0.1);

	const hitAreaFrames = new HitAreaFrames();
	model.addChild(hitAreaFrames);

	model.on('hit', (hitAreaNames) => {
		if (hitAreaNames.includes('Body')) {
			console.log('test');
			model.motion('TapBody');
		}
	});
});
</script>

App.vueは<Live2D />差し込んだだけ

解説

デフォルト動作

先駆者情報に則ってモデルインポートと描画を行うと、実にすごくあっさり動いてしまう
でも、モデルのアイトラッキングがマウスカーソル追従がデフォルトで、これは2つ以上のモデル描画したら全部がそこを注視しそうでキモいなって思ったので止めたかった
画面タップ位置に応じて何らかのイベントを起動するのは欲しかったので残したかった

よって色んなドキュメントとデモデータを読み漁った結果、次の事が分かった

インタラクションの制御

pixi live2d display公式の完全ガイドのマニュアルインタラクションの項によると次の事が書かれている

Manually
If you don't want the default behaviour, you can disable the autoInteract option, then manually call the interaction methods.
(意訳:デフォルト挙動が嫌だったら次の構文で制御してね)

const model = await Live2DModel.from('shizuku.model.json', { autoInteract: false });

canvasElement.addEventListener('pointermove', event => model.focus(event.clientX, event.clientY));

canvasElement.addEventListener('pointerdown', event => model.tap(event.clientX, event.clientY));

1行目はモデルのロードをする時に全自動インタラクション設定を止めるという事。これは難なく動く

問題は次の2行
Vite環境下でPixiアプリを全画面モードで描画しても、なんか微妙に左側に余白ができてしまう

で、event.clientX/Yは、この余白も反映した値をリターンする
つまり、ブラウザ描画領域に対する座標であってPixiアプリ描画領域に対する座標ではないという事

なんとなく嫌な予感がしていたのだけど、Live2Dのデータを保有しているmodelオブジェクトは、Pixiアプリケーションの描画領域左上を原点として制御される。そこから自身の当たり判定座標を保有しているという事は、このコードでは当たり判定が作用しないという事

というわけで、アプリケーションの描画領域左上を原点とするように変更したのが以下

	app.view.addEventListener('pointerdown', event => {
		// InteractionDataを使って座標を取得
		const rect = app.view.getBoundingClientRect();
		const x = event.clientX - rect.left;
		const y = event.clientY - rect.top;

		console.log("Pointer down event triggered", x, y);
		model.tap(x, y);

これでアイトラッキングを停止させ、タップイベントのみ反映させることができた

めでたしめでたし

最後に

オフィシャルの情報はバニラなHTMLとJSを想定したNodeモジュールっぽいので、いろんな場所でこういう変更が必要そうだなと思った
公式のデモデータが非常に短いスクリプトなので、解析するには捗る。少なくとも公式CubismFrameworkを解析するより楽
Pixi Live2d Display公式ドキュメントの整備も行き届いてて良き

Discussion

暁よあけ暁よあけ

なんかマウスイベントくらいPixiアプリケーション自体が搭載してそうな気がしたので、そっちに座標問い合わせたらもっと簡単なんじゃね? っていう気持ちが若干あります