babylon.js x ブラウザで VRM モデルを歩かせてみた

https://playground.babylonjs.com/#NHXN10#18

babylon.js は書いたコードをブラウザ上ですぐに再生出来るプレイグラウンドが用意されています。 TypeScript で型付きで書くことも可能です。この環境で VRM キャラクターを歩かせてみます。

元のソースコードはプレイグラウンドの方に記載されていますが、それぞれ何をしているか紹介します。

```typescript
const vrmLoaderScript = document.createElement('script');
vrmLoaderScript.src = 'https://cdn.jsdelivr.net/npm/babylon-vrm-loader@v1.4.13/dist/index.js';
document.head.appendChild(vrmLoaderScript);
```

これは [babylon-vrm-loader](https://github.com/virtual-cast/babylon-vrm-loader) というライブラリを追加で読み込む処理です。これで babylon.js 上で VRM を読み込むことが可能になります。

**※このライブラリ、私が書きましたが、揺れもの(SecondaryAnimation)の処理がバグっています。 [help wanted](https://github.com/virtual-cast/babylon-vrm-loader/issues/27) です。**

```typescript

class Playground {
    /**
     * Entry point
     */
    public static CreateScene(engine: BABYLON.Engine, canvas: HTMLCanvasElement): BABYLON.Scene {
        const scene = new BABYLON.Scene(engine);
        const camera = createCamera(scene);
        camera.attachControl(canvas, true);

        vrmLoaderScript.onload = () => {
            BABYLON.SceneLoader.Append(
                'https://raw.githubusercontent.com/vrm-c/UniVRM/master/Tests/Models/Alicia_vrm-0.51/',
                'AliciaSolid_vrm-0.51.vrm',
                scene,
                () => {
                    // create scene after loading VRM
                    Playground.constructScene(scene, camera);
            });
        };

        return scene;
    }
}
```

プレイグラウンドでは、 GitHub に上がっている [アリシアソリッドちゃん](https://github.com/vrm-c/UniVRM/blob/master/Tests/Models/Alicia_vrm-0.51/AliciaSolid_vrm-0.51.vrm) を読み込んでいます。

```typescript
        scene.onBeforeRenderObservable.add(() => {
            // Update every frame
            vrmManager.update(scene.getEngine().getDeltaTime());
            camera.setTarget(new BABYLON.Vector3(vrmMesh.position.x, vrmMesh.position.y + 1, vrmMesh.position.z));
        });
```

シーンの `onBeforeRenderObservable` に毎フレーム処理したいものを記述していきます。 `vrmManager.update` は揺れものの更新、それとカメラを追従するようにターゲット設定しています。

```typescript
vrmMesh.movePOV(0, 0, delta / 1300);
```

これで前方に進むことが出来ます。

```typescript
vrmMesh.rotate(BABYLON.Vector3.Up(), -delta / 1000);
```

これで左回転しています。

```typescript
    const inputMap = {
        65: false, // A
        83: false, // S
        68: false, // D
        87: false, // W
    };
    scene.actionManager = new BABYLON.ActionManager(scene);
    scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnKeyDownTrigger, function (evt) {
        inputMap[evt.sourceEvent.keyCode] = evt.sourceEvent.type == "keydown";
    }));
    scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnKeyUpTrigger, function (evt) {
        inputMap[evt.sourceEvent.keyCode] = evt.sourceEvent.type == "keydown";
    }));

    scene.onBeforeRenderObservable.add(() => {
        const speed = scene.getEngine().getDeltaTime();
        let moveCount = 0;
        if (inputMap[65]) {
            onLeft(speed);
            moveCount++;
        }
        if (inputMap[83]) {
            onBackward(speed);
            moveCount++;
        }
        if (inputMap[68]) {
            onRight(speed);
            moveCount++;
        }
        if (inputMap[87]) {
            onForward(speed);
            moveCount++;
        }
        if (moveCount == 0) {
            onStop();
        }
    });
```

`scene.onKeyboardObservable` というものがありますが、これは「押しっぱなしでずっと呼ばれる」のではなく、文字が入力された時(例えば k を押し続けると、 k と入力されてから少し間をおいて kkk... と入力されます)に発火するので、押しっぱなしの処理をするには不向きでした。代わりにこの `ActionManager` を用いています。ブラウザネイティブの機能を使っても実現出来ると思います。

```typescript
    const leftUpperLegAnim = {
        name: 'leftUpperLegAnim',
        keys: [
            [0, 0, 0, 0],
            [15, 0, Math.PI / 10, 0],
            [45, 0, -Math.PI / 15, 0],
            [60, 0, 0, 0],
        ],
    };
```

vrmManager は humanoidBone を持っているので、どんなモデルでも共通のボーンに対して処理を行うことが出来ます。

アニメーションをするために、キーフレームを登録して、さらに `AnimationGroup` を使って複数のアニメーションをひとまとめにしています。

---

歩かせるだけですが結構な量のコードになりますね。ここからもっと応用を重ねればなんとかゲームを作れそうです。

このソースコードは Apache License 2.0 ですので、これをコピーしてゲームを作っても構いません。作ったら教えて頂けると嬉しいです。