simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:VRMオンラインチャット構想

■画一的なエモーションコマンド作れる?→YES
VRM には表情プロキシクラスがあるので、どのような VRM でも統一したエモーション名で表情を表出することができます。

■テキストに合わせて表情と口を動かすことできる?→YES
あいうえおの言葉の表現もあるので、テキストから母音を取り出して口の形を決めることができます。
qiita.com

MeCab は単語の意味から感情も取れる?→NO
挑む例は多くとも
qiita.com
ディフォルトでサポートするとか、そういうものではない

なら、表情はランダムに出すというより
「!」(笑)で、笑顔
「?」で終わると困り顔
「!?」で、驚き顔
に変わるとかで試験できますね

■ゲーム内に半透明で吹き出しとテキストを表示できる?→
TMP で

blogs.unity3d.com


www.hanachiru-blog.com

口の動きと、会話のタイミングについては、これで合わせられることになるだろう。

■ゲーム中のチャットテキスト入力画面の表示→調査完了
コピー&ペーストすることを考えるなら、画面下部に一行入力できるラインがあると良い

ログとして流せると良いので、マインクラフトは真似る
tを押すと半透明の帯が現れる
黒い半透明背景にテキストは白が基本
プレイヤー名が左に表示される(可能ならVRMの顔画像としたい)
教えてもらったテキストをコピーできるようにもしておく
最大コメントログ行数はディフォルトで5行、ログはtを押したときに現れる

ログの表示は困るか、不自由で良いので
近くに来て吹き出しの内容が読めるときだけ意思疎通できるようにすると良い

ところで、キャラクターに吹き出しが出て、テキストが流れつつリップシンクと表情の変化があるとなお良い

全員のログが流れるとうざい
キャラごとに色分けされていると良い(色は自分で設定できるカラーピッカー)
baba-s.hatenablog.com

吹き出しを重ならないように調整できるか

各クライアントにて、カメラに対して正対するように回転を与える
キャラクターごとに重なると読みづらいが、必ずレイヤーの上層で表示させる
吹き出しは横長にして、高さが重なったら、遠いプレイヤーの方が吹き出しをずらす。
カメラの画面にキャラが入っていない場合は、距離が近いとキャラの方に矢印が出る形で画面に入ってくる。

■そんな描画技術あるのか→発明した

UIキャンバスはそのまま使い、動的に吹き出し要素のuGUIが飛び出てくるようにする
距離に応じて小さくなるように設定できる
必ずUIとして前面に表示させることができる

シミュレーションして式の形をイメージします。
画面内にキャラクターがいる時
キャラクターの頭の上に吹き出しが現れる
頭の上に余白がないときは、画面外へ出る寸前で、端が内に収まるように配置される、アニメはしない
キャラクターが画面左へ移動したとき、画面中央から考えられるベクトルの接点で位置が決まる
画面付近にいる場合はこれで表現ができる
Positionからスクリーン座標の計算
docs.unity3d.com


画面内にキャラクターがいないとき
カメラの方向の真横にキャラクターが移動したとき

とにかく上記のインタフェースでどれくらいの計算結果が得られるのか試験します。

■試験結果
Unity2019.1.1 で試験しました。

前面に表示されるときは、矢じりの延長がクロスする点が期待通りの位置に表示され
背面に入った時は、矢筈の延長がクロスする点の位置を返すようになりました。

真横で計算できないことになると原点を返す様子です。

原点は無視
前面で枠外に出る時は、枠内に収めるようにクランプ
背面のときは、鏡面反射座標にしてから、枠外へのオフセットを与えて、枠内に収めるようにクランプ

が良さそうであることが想像できました。

コードに落として動作を見てみます。

using UnityEngine;

public class World2ScreenPoint : MonoBehaviour
{
    public Transform position;
    public RectTransform text;
    Camera cam;

    void Start()
    {
        cam = GetComponent<Camera>();
    }

    void Update()
    {
        var distance = Vector3.Distance(position.position, cam.transform.position);
        text.localScale = Vector3.one * Mathf.Clamp01(3 / distance);

        var dot = Vector3.Dot(cam.transform.forward, position.position - cam.transform.position);
        var flag = Mathf.Sign(dot);

        Vector3 screenPos = cam.WorldToScreenPoint(position.position);

        var marginX = text.rect.width / 2 * text.localScale.x;
        var marginY = text.rect.height / 2 * text.localScale.y;
        var x = Mathf.Clamp(screenPos.x, marginX, Screen.width - marginX);
        var y = Mathf.Clamp(screenPos.y, marginY, Screen.height - marginY);
        var z = screenPos.z;
        text.rotation = Quaternion.identity;

        if (0 > flag)
        {
            x = Screen.width - x;
            y = Screen.height - y;
            text.rotation = Quaternion.Euler(0, 180, 0);
        }
        text.position = new Vector3(x, y, z);
    }
}

期待通り動きました。

■キャラクターを配置してテスト
どのような動きになるのか、本番さながらの様子をみてみましょう。

キャラクターの頭上に必ずテキストが配置されるものとします。
単体での表示なら問題なさそうですね。

3体と同時に会話したときに、何が起こるのか試してみましょう。
誰の発言なのか一目でわかるようにサムネイルがほしい

サムネイルを表示してみましたが、プレイヤー名の方が自然ですね。

MeCabによる動的なテキスト解析

例のリンク先を読みます。
以下のコードにて Mac 環境 Editor でも動作させることが可能であることを確認しました。
Mac アプリだとプラットフォームで許可されてないエラーが出たので、後日ソースを使って調べる

using NMeCab;
using UnityEngine;
using UnityEngine.UI;

public class NMeCabTest : MonoBehaviour
{
    public Text text;

    void Start()
    {
        string result = "";
        string sentence = "Unityで形態素解析";

        MeCabParam param = new MeCabParam();
        param.DicDir = $"{Application.streamingAssetsPath}/NMeCab/dic/ipadic";

        MeCabTagger t = MeCabTagger.Create(param);
        MeCabNode node = t.ParseToNode(sentence);
        while (node != null)
        {
            if (node.CharType > 0)
            {
                Debug.Log(node.Surface + "\t" + node.Feature);
                result += node.Surface + "\t" + node.Feature + "\r\n";
            }
            node = node.Next;
        }
        this.text.text = result;
        Debug.Log("");
    }
}