MRが楽しい

MRやVRについて学習したことを書き残す

HoloLens2でホロモンアプリを作る その65(ホロモンの現在ステータスをUIで確認する)

本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
f:id:bluebirdofoz:20211017225345j:plain

ホロモンの現在ステータスをUIで確認する

以下の記事で作成したホロモンの内部ステータスをUIから確認できるようにします。
bluebirdofoz.hatenablog.com

内部ステータスの変化は UI 側で UniRx の ReadOnlyReactiveProperty を購読して検出します。
ReadOnlyReactiveProperty の詳細は以下の記事を参照ください。
bluebirdofoz.hatenablog.com

実装スクリプト

HoloMonConditionLifeSingleton クラスから対象パラメータの ReadOnlyReactiveProperty を購読して状態が変化するとテキストを変更するスクリプトを作成しました。
・ShowStatusHungry.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

using TMPro;

using UniRx;
using HoloMonApp.ConditionSpace;

namespace HoloMonApp.MenuViewSpace
{
    public class ShowStatusHungry : MonoBehaviour
    {
        [SerializeField, Tooltip("テキスト出力先")]
        private TextMeshPro p_TextField;

        /// <summary>
        /// 開始処理
        /// </summary>
        void Start()
        {
            HoloMonConditionLifeSingleton.Instance.IReadOnlyReactivePropertyHoloMonLifeStatus
                .ObserveOnMainThread()
                .Subscribe(status => {
                    int percent = status.HungryPercent;
                    p_TextField.text = percent.ToString() + " %";
                })
                .AddTo(this);
        }
    }
}

f:id:bluebirdofoz:20211017225413j:plain

UIの表示パネルをシーンに追加し、それぞれ空腹度、機嫌、元気(疲労度)のスクリプトを設定しました。
f:id:bluebirdofoz:20211017225421j:plain

動作確認

シーンを再生して動作を確認します。
f:id:bluebirdofoz:20211017225430j:plain

ハンドメニューからパラメータの項目を開くと、ホロモンの現在のステータスが表示されます。
ステータスに変化があると ReadOnlyReactiveProperty のイベントが発生し、即座に反映されます。
f:id:bluebirdofoz:20211017225536j:plain

HoloLens2でホロモンアプリを作る その64(ホロモンが何かを見つけた時に瞳孔が開く)

本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
f:id:bluebirdofoz:20211016234913j:plain

ホロモンが何かを見つけた時に瞳孔が開くようにしたメモです。

ホロモンが何かを見つけた時に瞳孔が開く

以下の記事で作成したホロモンの瞳孔を操作する仕組みを利用します。
bluebirdofoz.hatenablog.com
bluebirdofoz.hatenablog.com

今回は以下のロジックに瞳孔を開くコードを追加することで、何かを見つけた時にホロモンの瞳孔が開くようにしました。
bluebirdofoz.hatenablog.com

実装スクリプト

以下の頭部の視線変更を行うスクリプトに前回スクリプトの参照を追加しました。
何か発見すると同時に瞳孔を開く関数を呼び出しています。
・HoloMonHeadLogicLookAtTarget.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using System;

using HoloMonApp.CommonSpace;
using HoloMonApp.ActionSpace;

namespace HoloMonApp.HeadLogicSpace
{
    public class HoloMonHeadLogicLookAtTarget : MonoBehaviour, HoloMonHeadLogicInterface
    {
        /// <summary>
        /// 視線ロジック共通情報
        /// </summary>
        [SerializeField, Tooltip("視線ロジック共通情報")]
        private HoloMonHeadLogicCommon p_HeadLogicCommon = new HoloMonHeadLogicCommon();

        /// <summary>
        /// 現在のモードを取得する
        /// </summary>
        /// <returns></returns>
        public HoloMonActionHeadStatus GetHoloMonHeadStatus() => p_HeadLogicCommon.HeadLogicStatus;

        /// <summary>
        /// EveryValueChanged オブザーバ参照変数を取得する
        /// </summary>
        /// <returns></returns>
        public IObservable<HoloMonActionHeadStatus> GetHoloMonHeadStatusEveryValueChanged()
            => p_HeadLogicCommon.IObservableHeadLogicStatusEveryValueChanged;

        /// <summary>
        /// モード設定を有効化する
        /// </summary>
        public bool EnableHead()
        {
            // 開始処理を行う
            EnableSetting();

            // 開始状態を設定する
            p_HeadLogicCommon.SetHeadStatus(HoloMonActionHeadStatus.Runtime);

            return true;
        }

        /// <summary>
        /// モード設定を無効化する
        /// </summary>
        public bool DisableHead(HoloMonActionHeadStatus a_DisableStatus)
        {
            // 停止処理を行う
            DisableSetting();

            // 停止状態を設定する
            p_HeadLogicCommon.SetHeadStatus(a_DisableStatus);

            return true;
        }

        /// <summary>
        /// アクション設定を反映する
        /// </summary>
        /// <returns></returns>
        public bool ApplySetting(HeadLogicSetting a_HeadLogicSetting)
        {
            // 設定の反映を行う
            ApplyData(a_HeadLogicSetting);

            return true;
        }


        /// <summary>
        /// 設定アクションデータ
        /// </summary>
        [SerializeField, Tooltip("設定アクションデータ")]
        private HeadLogicLookAtTargetData p_Data;

        /// <summary>
        /// ホロモン頭部視点の制御コンポーネント
        /// </summary>
        [SerializeField, Tooltip("ホロモン頭部視点の制御コンポーネント")]
        private HeadLookControllerSmooth p_HeadLookController;

        /// <summary>
        /// ホロモン瞳孔部の制御コンポーネント
        /// </summary>
        [SerializeField, Tooltip("ホロモン瞳孔部の制御コンポーネント")]
        private PupilAnimationController p_PupilAnimationController;

        /// <summary>
        /// HeadLookControllerの各ボーンの最大角デフォルト設定
        /// </summary>
        [SerializeField, Tooltip("HeadLookControllerの各ボーンの最大角デフォルト設定")]
        private float p_DefaultMaxBendingAngles;

        /// <summary>
        /// HeadLookControllerのアニメーションオーバーライドのデフォルト値
        /// </summary>
        [SerializeField, Tooltip("HeadLookControllerのアニメーションオーバーライドのデフォルト値")]
        private bool p_DefaultOverrideAnimation;



        /// <summary>
        /// 定期処理(Late)
        /// </summary>
        void LateUpdate()
        {
            if (p_HeadLogicCommon.HeadLogicStatus == HoloMonActionHeadStatus.Runtime)
            {
                // プレイヤーの頭部位置を毎フレーム更新する
                p_HeadLookController.target = p_Data.TargetObject.transform.position;
            }
        }


        /// <summary>
        /// 視線モードの設定を有効化する
        /// </summary>
        private bool EnableSetting()
        {
            // 視線の追従を有効化する
            ChangeMaxBendingAngle(true);

            // 瞳孔の拡大アクションを実行する
            ChangePupilExpansion();

            return true;
        }

        /// <summary>
        /// 視線モードの設定を無効化する
        /// </summary>
        private bool DisableSetting()
        {
            // 視線の追従を無効化する
            ChangeMaxBendingAngle(false);

            return true;
        }

        /// <summary>
        /// 設定アクションデータを反映する
        /// </summary>
        private bool ApplyData(HeadLogicSetting a_HeadLogicSetting)
        {
            // 設定アクションデータをキャストして取得する
            p_Data = a_HeadLogicSetting.HeadLogicLookAtTargetData;

            return true;
        }

        /// <summary>
        /// 有効無効に合わせてHeadLookControllerの最大角を変更する
        /// </summary>
        /// <param name="onoff"></param>
        private void ChangeMaxBendingAngle(bool onoff)
        {
            // 有効無効に合わせてHeadLookControllerの最大角を変更する
            int segmentnum = p_HeadLookController.segments.Length;
            if (segmentnum > 0)
            {
                for (int loop = 0; loop < segmentnum; loop++)
                {
                    float setValue = 0;
                    if (onoff)
                    {
                        setValue = p_DefaultMaxBendingAngles;
                    }
                    p_HeadLookController.segments[loop].maxBendingAngle = setValue;
                }
            }
            // 有効無効に合わせてOverrideAnimationの設定を変更する
            p_HeadLookController.overrideAnimation = onoff ? p_DefaultOverrideAnimation : false;
        }

        /// <summary>
        /// 瞳孔の拡大アクションを実行する
        /// </summary>
        private void ChangePupilExpansion()
        {
            // 瞳孔の拡大アクションを実行する
            p_PupilAnimationController.ExpansionAction();
        }
    }
}

f:id:bluebirdofoz:20211016234953j:plain

動作確認

シーンを再生して動作を確認します。
f:id:bluebirdofoz:20211016235003j:plain

以下のように新しいオブジェクトを発見したときにホロモンの瞳孔が少し大きくなります。
細かいですが、ホロモンが対象に興味を持ったことが何となく感じ取れます。
f:id:bluebirdofoz:20211016235013j:plain

HoloLens2でホロモンアプリを作る その63(瞳孔の収縮をスクリプトから操作する)

本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
f:id:bluebirdofoz:20211015223935j:plain

瞳孔の収縮をスクリプトから操作するメモです。

瞳孔の収縮を操作する

ホロモンの動向を収縮させるため、以下の記事でホロモンの瞳にボーンを仕込みました。
bluebirdofoz.hatenablog.com

今回はこのボーンのスケールをスクリプトから操作することで、任意のタイミングで瞳孔の収縮を発生させます。
LateUpdate からボーンのトランスフォームを操作することで通常のアニメーションを上書きすることができます。

実装スクリプト

瞳孔の収縮を操作するスクリプトを作成しました。
ContractionAction 関数を実行すると左右の瞳孔ボーンに対して LateUpdate のタイミングで収縮のアニメーションを上書き実行します。
・PupilAnimationController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

using Cysharp.Threading.Tasks;
using UniRx;
using UniRx.Triggers;

public class PupilAnimationController : MonoBehaviour
{
    [SerializeField, Tooltip("右瞳孔の収縮ボーン")]
    private Transform p_RightEyeShrinkage;

    [SerializeField, Tooltip("左瞳孔の収縮ボーン")]
    private Transform p_LeftEyeShrinkage;

    [SerializeField, Tooltip("収縮の最小スケール")]
    private Vector3 p_SpanMinScale;

    [SerializeField, Tooltip("瞳孔アニメーション実行中フラグ")]
    private bool p_EyeAnimationFlg = false;

    // 経過時刻
    private float timeElapsed;

    /// <summary>
    /// 一時アクションの実行のトリガー
    /// </summary>
    IDisposable p_TempActionTrigger;

    /// <summary>
    /// 瞳孔の収縮アクションを実行する
    /// </summary>
    /// <returns></returns>
    [ContextMenu("ExecuteContractionAction")]
    public bool ContractionAction()
    {
        if (p_EyeAnimationFlg) return false;
        p_EyeAnimationFlg = true;

        // 各ステップのアニメーション時間
        float startTimeSpan = 1.0f;
        float keepTimeSpan = 1.0f;
        float endTimeSpan = 3.0f;

        timeElapsed = 0.0f;

        // トリガーを設定済みの場合は一旦破棄する
        p_TempActionTrigger?.Dispose();

        // アニメーション完了後キャンセルのトリガーを実行する
        p_TempActionTrigger = this.LateUpdateAsObservable()
            .Subscribe(_ =>
            {
                timeElapsed += Time.deltaTime;

                float lerpFactor = 0.0f;
                if (timeElapsed < startTimeSpan)
                {
                    // 徐々に瞳を収縮させる
                    lerpFactor = (timeElapsed % startTimeSpan);

                    p_RightEyeShrinkage.localScale = Vector3.Lerp(Vector3.one, p_SpanMinScale, lerpFactor);
                    p_LeftEyeShrinkage.localScale = Vector3.Lerp(Vector3.one, p_SpanMinScale, lerpFactor);
                }
                else if(timeElapsed < startTimeSpan + keepTimeSpan)
                {
                    // 状態をキープする
                    lerpFactor = 1.0f;

                    p_RightEyeShrinkage.localScale = Vector3.Lerp(Vector3.one, p_SpanMinScale, lerpFactor);
                    p_LeftEyeShrinkage.localScale = Vector3.Lerp(Vector3.one, p_SpanMinScale, lerpFactor);
                }
                else if(timeElapsed < startTimeSpan + keepTimeSpan + endTimeSpan)
                {
                    float checkTimeElapsed = timeElapsed - (startTimeSpan + keepTimeSpan);

                    // 徐々に瞳の大きさを元に戻す
                    lerpFactor = 1.0f - (checkTimeElapsed % endTimeSpan);

                    p_RightEyeShrinkage.localScale = Vector3.Lerp(Vector3.one, p_SpanMinScale, lerpFactor);
                    p_LeftEyeShrinkage.localScale = Vector3.Lerp(Vector3.one, p_SpanMinScale, lerpFactor);
                }
                else
                {
                    // アニメーションの終了
                    p_EyeAnimationFlg = false;
                    p_TempActionTrigger.Dispose();
                }
            })
            .AddTo(this);

        return true;
    }
}

f:id:bluebirdofoz:20211015224014j:plain

スクリプトをオブジェクトに設定し、左右の動向ボーンへの参照を設定します。
f:id:bluebirdofoz:20211015224023j:plain

動作確認

シーンを再生して動作を確認します。
f:id:bluebirdofoz:20211015224032j:plain

コンテキストメニューから関数を実行してみます。
f:id:bluebirdofoz:20211015224040j:plain

以下の通り、スクリプトから瞳孔の収縮アニメーションを上書き再生することができました。
f:id:bluebirdofoz:20211015224048j:plain

MRTKを使って任意のオブジェクトに触れた時に発生するイベントを登録する

本日は MRTK の小ネタ枠です。
MRTKを使って任意のオブジェクトに触れた時に発生するイベントを登録する手順を記事にします。
f:id:bluebirdofoz:20211014214329j:plain

前提条件

前回記事の続きです。
Interactable コンポーネントと新たに コンポーネントを利用します。
bluebirdofoz.hatenablog.com

NearInteractionTouchable

Pointer がオブジェクトに触れるたびに PointerDown イベントと PointerUp イベントを取得するタッチ可能なサーフェスを構成します。
docs.microsoft.com

サンプルプロジェクトの作成

MRTK をインポートしたサンプルプロジェクトを作成します。

MRTK のインポートと基本設定

MRTK のインポートと HoloLens 向けプロジェクトの基本設定を行い、サンプルプロジェクトを作成します。
手順の詳細は以下の記事を参照してください。
bluebirdofoz.hatenablog.com

サンプルシーンの作成

シーンには任意のオブジェクトとして Cube オブジェクトを作成しました。
f:id:bluebirdofoz:20211014214405j:plain

[Add Componet]から[Interactable]コンポーネントを選択してオブジェクトに追加します。
f:id:bluebirdofoz:20211014214415j:plain

[Receivers]パネルを開き、[Add Event]をクリックして受け取りイベントを追加します。
f:id:bluebirdofoz:20211014214426j:plain

Receiver を追加したら[EventReceiverType]に[InteractableOnTouchReceiver]を設定します。
f:id:bluebirdofoz:20211014214437j:plain

[OnTouch]イベントの設定欄が表示されます。
Cube オブジェクトの LookAt 関数を指定し、Touch イベント発生時にカメラ方向を向くように設定しました。
f:id:bluebirdofoz:20211014214449j:plain

最後にオブジェクトに触れた時に Touch イベントが発生するように[Add Componet]から[NearInteractionTouchable]コンポーネントを選択して追加します。
f:id:bluebirdofoz:20211014214501j:plain

[NearInteractionTouchable]コンポーネントのアタリ判定を Collider に一致させるため[Fix Bounds]と[Fix Center]ボタンをクリックします。
f:id:bluebirdofoz:20211014214513j:plain

これでオブジェクトに触れた時にイベントが発生するようになりました。
f:id:bluebirdofoz:20211014214523j:plain

動作確認

シーンを再生して動作を確認します。
f:id:bluebirdofoz:20211014214533j:plain

適当な方向から Cube オブジェクトに触れると [OnTouch]イベントが実行され、Cube オブジェクトがこちらを向きます。
f:id:bluebirdofoz:20211014214541j:plain

MRTKを使って任意のオブジェクトにクリックイベントを登録する

本日は MRTK の小ネタ枠です。
MRTKを使って任意のオブジェクトにクリックイベントを登録する手順を記事にします。
f:id:bluebirdofoz:20211013231502j:plain

Interactable

任意のオブジェクトにクリックイベントを登録するには Interactable コンポーネントを利用します。
Interactable は任意のオブジェクトを対話可能にし、入力に応答させるコンテナです。
タッチ、ハンドレイ、音声などを含む全ての種類の入力をキャッチできます。
またオブジェクトの状態に対応したビジュアルテーマの設定も可能です。
docs.microsoft.com

サンプルプロジェクトの作成

MRTK をインポートしたサンプルプロジェクトを作成します。

MRTK のインポートと基本設定

MRTK のインポートと HoloLens 向けプロジェクトの基本設定を行い、サンプルプロジェクトを作成します。
手順の詳細は以下の記事を参照してください。
bluebirdofoz.hatenablog.com

サンプルシーンの作成

シーンには任意のオブジェクトとして Cube オブジェクトを作成しました。
f:id:bluebirdofoz:20211013231528j:plain

[Add Componet]から[Interactable]コンポーネントを選択してオブジェクトに追加します。
f:id:bluebirdofoz:20211013231538j:plain

[OnClick]イベントに LookAt 関数を指定し、Select アクション発生時にカメラ方向を向くように設定しました。
f:id:bluebirdofoz:20211013231546j:plain

動作確認

シーンを再生して動作を確認します。
f:id:bluebirdofoz:20211013231557j:plain

適当な方向から Cube オブジェクトをタップすると Cube オブジェクトの[OnClick]イベントが実行され、Cube オブジェクトがこちらを向きます。
f:id:bluebirdofoz:20211013231606j:plain

[Interactable]コンポーネントの設定を変更することで MRTK を通して様々な入力をキャッチすることができます。
例えば[SpechCommand]を設定すると音声コマンドで[OnClick]イベントを実行することができます。
f:id:bluebirdofoz:20211013231614j:plain

HoloLensアプリで起動時に表示されるVirtual Reality Splash Imageの画像を差し替える

本日は HoloLens2 の小ネタ枠です。
HoloLensアプリで起動時に表示される Virtual Reality Splash Image の画像を差し替える手順を記事にします。

Virtual Reality Splash Image

VRまたはMRアプリを起動したとき、最初に表示される画像のことです。
デフォルトでは Unity ロゴの画像が表示されます。
f:id:bluebirdofoz:20211012210411j:plain

Virtual Reality Splash Image は Splash Screen とは異なるものです。
Personal 版の Unity でも完全に差し替えることができます。
docs.unity3d.com

SplashImageの変更手順

差し替えたい画像ファイルを Unity プロジェクトに取り込みます。
画像の[TextureType]を[Texture2D]のものに変更しておきます。
f:id:bluebirdofoz:20211012210428j:plain

メニューから[Edit -> ProjectSettings]を選択し、ProjectSettings ウィンドウを開きます。
f:id:bluebirdofoz:20211012210437j:plain

[Player]タブの[SplashImage]パネルを開きます。
[Virtual Reality Splash Image]の[Select]をクリックし、画像選択のダイアログを開きます。
f:id:bluebirdofoz:20211012210446j:plain

先ほど Unity に取り込んだ画像を選択します。
これで Virtual Reality Splash Image が Unity ロゴから指定した画像に差し替えられます。
f:id:bluebirdofoz:20211012210455j:plain

動作確認

プロジェクトをビルドして HoloLens2 にデプロイし、起動確認してみます。
以下のように起動時に登録した画像が表示されるようになります。
f:id:bluebirdofoz:20211012210503j:plain

Blender2.8でモンスター型のキャラクターモデルを作成する その6(瞼の動作ボーンの作成)

本日はモンスター型モデルの作成枠です。
ホロモンの瞼の動きをスケルタルアニメーションで管理してみます。
f:id:bluebirdofoz:20211011232123j:plain

前提条件

前回記事の続きになります。
bluebirdofoz.hatenablog.com

スケルタルアニメーションで瞼の動きを調節する

瞳孔と同じように、瞼に重ね合わせるような形でボーンを作成し、ウェイトを設定します。
f:id:bluebirdofoz:20211011232147j:plain

瞼には連動する2つのボーンのウェイトを設定し、一方は瞼全体を、一方は瞼の閉じた部分に重みづけしています。
f:id:bluebirdofoz:20211011232158j:plain
f:id:bluebirdofoz:20211011232211j:plain

トランスフォーム変換の設定

こちらもコントロールボーンで簡単に操作できるようIK(インバースキネマティクス)を設定します。
瞼は単純に上に動かすだけでは額を突き抜けてしまうので、スケールを変形する設定も行っています。
f:id:bluebirdofoz:20211011232222j:plain
f:id:bluebirdofoz:20211011232231j:plain

もう一方のボーンにも同じコントロールボーンを参照し、参照する移動軸を変更して1つのボーンで操作できるように設定しました。
f:id:bluebirdofoz:20211011232240j:plain

動作確認

[ポーズモード]でコントロール用のボーンを上下前後させるだけで、瞼を変形したり開いたりさせることができるようになりました。
f:id:bluebirdofoz:20211011232251j:plain
f:id:bluebirdofoz:20211011232304j:plain
f:id:bluebirdofoz:20211011232316j:plain