視線追従(Look At)
VRM キャラクターの視線を制御します。キャラクターはマウスカーソルを追従したり、特定のエンティティを追跡したり、視線追従動作を完全に無効にしたりできます。
インポート
import { Vrm } from "@hmcs/sdk";
カーソル追従
vrm.lookAtCursor() はキャラクターの目を画面上のマウスカーソルに追従させます。
const character = await Vrm.findByName("MyAvatar");
await character.lookAtCursor();
エンティティを見る
vrm.lookAtTarget(entity) はキャラクターの目を ID で指定した特定のエンティティに向けます。キャラクター同士を見つめ合わせたり、シーン内の特定のオブジェクトを見させたりするのに便利です。
const character = await Vrm.findByName("MyAvatar");
const other = await Vrm.findByName("OtherCharacter");
// MyAvatar を OtherCharacter の頭に向ける
const headEntity = await other.findBoneEntity("head");
await character.lookAtTarget(headEntity);
VRM のボーンだけでなく、任意の Bevy ECS エンティティのエンティティ ID を使用することもできます:
// 別の VRM のルートエンティティを見る
await character.lookAtTarget(other.entity);
視線追従の無効化
vrm.unlook() は視線追従動作を完全にオフにします。キャラクターの目はデフォルトのアニメーション駆動状態に戻ります。
await character.unlook();
一般的なパターン:状態駆動の視線追従
典型的なパターンは、キャラクターがアイドル状態のときにカーソル追従を有効にし、ドラッグなどのインタラクション中は無効にすることです:
import { Vrm, repeat } from "@hmcs/sdk";
const character = await Vrm.spawn("my-mod:character");
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
// カーソル追従を開始
await character.lookAtCursor();
character.events().on("state-change", async (e) => {
if (e.state === "idle") {
// 短い遅延後にカーソル追従を再開
//(アニメーショントランジション中のジッターを防止するための遅延)
await sleep(500);
await character.lookAtCursor();
} else if (e.state === "drag") {
// ドラッグ中は追従を停止
await character.unlook();
} else if (e.state === "sitting") {
// 座っている間もカーソル追従を継続
await sleep(500);
await character.lookAtCursor();
}
});
例:キャラクター同士の視線
const alice = await Vrm.findByName("Alice");
const bob = await Vrm.findByName("Bob");
// 頭のボーンエンティティを取得
const aliceHead = await alice.findBoneEntity("head");
const bobHead = await bob.findBoneEntity("head");
// お互いを見つめ合わせる
await alice.lookAtTarget(bobHead);
await bob.lookAtTarget(aliceHead);
視線追従の状態
現在の視線追従の状態は Vrm.findAllDetailed() から返される VrmSnapshot に含まれます:
const snapshots = await Vrm.findAllDetailed();
for (const s of snapshots) {
if (s.lookAt === null) {
console.log(`${s.name}: look-at disabled`);
} else if (s.lookAt.type === "cursor") {
console.log(`${s.name}: following cursor`);
} else if (s.lookAt.type === "target") {
console.log(`${s.name}: looking at entity ${s.lookAt.entity}`);
}
}
型定義
type LookAtState =
| { type: "cursor" }
| { type: "target"; entity: number };