VRMのアバターの表情をアニメーションで管理

#やりたかったこと
研究目的のために複数かつ大量のアバターに適用させるアニメーションを作る必要がありました。首から下はHumanoidBornで普通に操作できましたが、FBX(というか既存の人型アバター)だと表情の設定がバラバラすぎて困惑。
なのでせっかくなので最近できたVRMフォーマットで表情制御をしようと決意(Blenderとか使えれば簡単なんだろうけど挫折)

#前提
Animation,Animatorは最低限使える

#環境構築と設定変更

ドワンゴの[公式ページ](https://dwango.github.io/vrm/)を参考にしてUnityにVRMを読み込む環境構築
使うモデルは[AliciaSolid](http://3d.nicovideo.jp/works/td32797)と[Unityちゃん](http://3d.nicovideo.jp/works/td33435)
ドラッグ＆ドロップだけで普通に使えたので便利

首から下は既に作ってあった「座って腕を組んでる」アニメーションを適用。

Sceneに配置して、とりあえず再生ボタンを押すとなぜか顔が消えて軽くホラー
![スクリーンショット (95).png](https://qiita-image-store.s3.amazonaws.com/0/254776/00f44359-b043-db6f-70db-f1e704ef1d50.png)


顔のパーツが顔に追従しない親子構造になってたが、構造壊すとどうなるかわからなかったので、とりあえずMeshRendererの位置を下げて対応する。
![スクリーンショット (96).png](https://qiita-image-store.s3.amazonaws.com/0/254776/c75df375-f839-0fc0-0425-b5c21882c81a.png)

#VRMBlendShapeProxyを触る

[UniVRMのVRMBlendShapeProxyで表情アニメーション処理を共通化する](https://qiita.com/decchi/items/fbcce64c77d486a8c6e8)
↑を読みつつ、VRMBlendShapeProxyを触ればいいのかと理解。
しかし、Editor上ではStartが始まらないとVRMBlendShapeProxyの値が変更できない=Animationで使えない
![スクリーンショット (97).png](https://qiita-image-store.s3.amazonaws.com/0/254776/3d955493-d606-c37e-5de4-025240e21e65.png)


なので、この値をUpdate後にどうこうするスクリプトを書いて、その変数を変更するAnimationを設定することにした。

```c#:VRMShapesControl.cs
using System;
using UnityEngine;
using VRM;

public class VRMShapesControl : MonoBehaviour
{

    private VRMBlendShapeProxy _proxy;

    [Range(0, 1)]
    public float A;

    [Range(0, 1)]
    public float I;

    [Range(0, 1)]
    public float U;

    [Range(0, 1)]
    public float E;

    [Range(0, 1)]
    public float O;

    [Range(0, 1)]
    public float Blink;

    [Range(0, 1)]
    public float Joy;

    [Range(0, 1)]
    public float Angry;

    [Range(0, 1)]
    public float Sorrow;

    [Range(0, 1)]
    public float Fun;

    [Range(0, 1)]
    public float LookUp;

    [Range(0, 1)]
    public float LookDown;

    [Range(0, 1)]
    public float LookLeft;

    [Range(0, 1)]
    public float LookRight;

    [Range(0, 1)]
    public float Blink_L;

    [Range(0, 1)]
    public float Blink_R;


    // Update is called once per frame
    void Update()
    {
        if (_proxy == null)
        {
            _proxy = GetComponent<VRMBlendShapeProxy>();
        }
        else
        {
            foreach (BlendShapePreset t in Enum.GetValues(typeof(BlendShapePreset)))
            {
                _proxy.SetValue(t, GetPropertyValue(t));
            }
        }
    }

    private float GetPropertyValue(BlendShapePreset t)
    {
        switch (t)
        {
            default:
                return 0;
            case BlendShapePreset.A:
                return A;
            case BlendShapePreset.Angry:
                return Angry;
            case BlendShapePreset.Blink:
                return Blink;
            case BlendShapePreset.Blink_L:
                return Blink_L;
            case BlendShapePreset.Blink_R:
                return Blink_R;
            case BlendShapePreset.E:
                return E;
            case BlendShapePreset.Fun:
                return Fun;
            case BlendShapePreset.I:
                return I;
            case BlendShapePreset.Joy:
                return Joy;
            case BlendShapePreset.LookDown:
                return LookDown;
            case BlendShapePreset.LookLeft:
                return LookLeft;
            case BlendShapePreset.LookRight:
                return LookRight;
            case BlendShapePreset.LookUp:
                return LookUp;
            case BlendShapePreset.O:
                return O;
            case BlendShapePreset.Sorrow:
                return Sorrow;
            case BlendShapePreset.U:
                return U;
        }
    }
}
```

本来はGetPropertyValueの中身を「変数名(string)から値を取得」とかしたかったけど、Enumからの変換とかが上手くいってないようなのでとりあえず原始的に実装。
このスクリプトをVRMBlendShapeProxyと同じGameObjectに追加。
BlendShapePresetは確か自分で変更できたはずなので、その度にここを変更しなきゃいけないのは難点。


とりあえず瞬きをさせたかったので、Blink(瞼を閉じる)の値を触る(0で開ける、1で閉じる)。10秒に1回0.5秒掛けて瞼を閉じる設定。
![スクリーンショット (98).png](https://qiita-image-store.s3.amazonaws.com/0/254776/ba3f66bb-cf51-3427-4ad8-2f9d022fcd54.png)


そして再生
![ezgif-3-e5516dd3b0.gif](https://qiita-image-store.s3.amazonaws.com/0/254776/0db0dc89-490d-04ff-9003-a947dca46946.gif)
成功

同じことがUnityちゃんでもできたので目的達成
![ezgif-3-e02aa66e51.gif](https://qiita-image-store.s3.amazonaws.com/0/254776/fb34455d-88c6-e543-51f0-e34900988be1.gif)


#難点
項目が増えるごとにGetPropertyValueを変えないといけない。
ゲームの再生中じゃないとどう動くかがわからないので値の調整がやりにくいので、モーションを作っている間は常に再生中にしておく必要がある。

#VRM
VRゲーム系の利用ということで、どうしてもアバターの種類がアニメよりに偏っているのが残念。RobotKyleみたいな学会発表中にニヤつかなくて済む感じのも欲しい…まあ自分で作れればいいんだろうけど…