MRが楽しい

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

HoloLens2でホロモンアプリを作る その21(ReactivePropertyに構造体を指定する)

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

今回は ReactiveProperty に構造体を指定するメモです。

ReactiveProperty に構造体を指定する

ホロモンの空腹度、ご機嫌度、眠さなどの情報を前回作成した以下のクラスで合わせて管理させるようにしました。
bluebirdofoz.hatenablog.com

これらのステータスを保持する一つの構造体を作成し、ReactiveProperty としました。
以下のように、ステータスの変更を行うスクリプトと参照するスクリプトを作成します。
・HoloMonLifeStatus.cs

using UnityEngine;
using System;
using UniRx;

namespace HMProject.HoloMon
{
    /// <summary>
    /// ホロモンのライフコンディションの構造定義
    /// </summary>
    public struct HoloMonLifeStatus
    {
        /// <summary>
        /// ホロモンの空腹度
        /// </summary>
        public int HungryPercent;

        /// <summary>
        /// ホロモンの機嫌度
        /// </summary>
        public int HappyPercent;

        /// <summary>
        /// ホロモンの眠り度
        /// </summary>
        public HoloMonSleepinessLevel SleepinessLevel;
    }

    /// <summary>
    /// ホロモンの眠り度を表す定義
    /// </summary>
    public enum HoloMonSleepinessLevel
    {
        Nothing = 0,
        Little = 1,
        Sleepy = 2
    }

    /// <summary>
    /// ホロモンのライフコンディションを表すReactiveProperty
    /// </summary>
    [Serializable]
    public class HoloMonLifeStatusReactiveProperty : ReactiveProperty<HoloMonLifeStatus>
    {
        public HoloMonLifeStatusReactiveProperty()
        {
        }
        public HoloMonLifeStatusReactiveProperty(HoloMonLifeStatus a_HoloMonLifeStatus) : base(a_HoloMonLifeStatus)
        {
        }
    }

    public class HoloMonConditionLifeSingleton : MonoBehaviour
    {
        /// <summary>
        /// 単一インスタンス
        /// </summary>
        private static HoloMonConditionLifeSingleton ps_instance;

        /// <summary>
        /// 参照用インスタンス
        /// </summary>
        public static HoloMonConditionLifeSingleton Instance
        {
            get
            {
                if (ps_instance == null && ps_searchForInstance)
                {
                    ps_searchForInstance = false;
                    var ps_instances = FindObjectsOfType<HoloMonConditionLifeSingleton>();
                    if (ps_instances.Length == 1)
                    {
                        ps_instance = ps_instances[0];
                    }
                    else if (ps_instances.Length > 1)
                    {
                        Debug.LogErrorFormat("Expected exactly 1 {0} but found {1}.", ps_instance.GetType().ToString(), ps_instances.Length);
                    }
                }
                return ps_instance;
            }
        }

        private static bool ps_searchForInstance = true;


        /// <summary>
        /// ホロモンのライフコンディション
        /// </summary>
        [SerializeField, Tooltip("ホロモンのライフコンディション")]
        private HoloMonLifeStatusReactiveProperty p_HoloMonLifeStatus
            = new HoloMonLifeStatusReactiveProperty();

        /// <summary>
        /// ホロモンのライフコンディションのReadOnlyReactivePropertyの保持変数
        /// </summary>
        private IReadOnlyReactiveProperty<HoloMonLifeStatus> p_IReadOnlyReactivePropertyHoloMonLifeStatus;

        /// <summary>
        /// ホロモンのライフコンディションのReadOnlyReactivePropertyの参照変数
        /// </summary>
        public IReadOnlyReactiveProperty<HoloMonLifeStatus> IReadOnlyReactivePropertyHoloMonLifeStatus
            => p_IReadOnlyReactivePropertyHoloMonLifeStatus
            ?? (p_IReadOnlyReactivePropertyHoloMonLifeStatus = p_HoloMonLifeStatus.ToReadOnlyReactiveProperty());

        

        /// <summary>
        /// 時刻判定(分刻み)のトリガー
        /// </summary>
        IDisposable p_MinuteTimeTrigger;


        /// <summary>
        /// 眠さの変化
        /// </summary>
        /// <param name="a_Sleepiness"></param>
        private void ReceptionSleepiness(HoloMonSleepinessLevel a_Sleepiness)
        {
            // 変数の登録を行う
            // 非変更時は通知が発生しない
            HoloMonLifeStatus status = p_HoloMonLifeStatus.Value;
            status.SleepinessLevel = a_Sleepiness;
            p_HoloMonLifeStatus.Value = status;
        }


        /// <summary>
        /// 開始処理
        /// </summary>
        void Start()
        {
            // ライフコンディションを初期化
            InitializeLifeStatus();

            // 1分毎にトリガーを実行する
            p_MinuteTimeTrigger = Observable
                .Timer(TimeSpan.FromSeconds(60.0f - DateTime.Now.Second), TimeSpan.FromMinutes(1.0f))
                .SubscribeOnMainThread()
                .Subscribe(x => {
                    ChangeOverTimeCondition(DateTime.Now);
                })
                .AddTo(this);
        }

        /// <summary>
        /// 定期処理
        /// </summary>
        void Update()
        {
        }

        /// <summary>
        /// ライフコンディションを初期化する
        /// </summary>
        private void InitializeLifeStatus()
        {
            // ライフコンディションを初期化する
            //  Todo : 再起動時の読み込み処理
            HoloMonLifeStatus status = p_HoloMonLifeStatus.Value;

            status.HungryPercent = 0;
            status.HappyPercent = 0;
            status.SleepinessLevel = HoloMonSleepinessLevel.Nothing;

            p_HoloMonLifeStatus.Value = status;
        }

        /// <summary>
        /// 時間経過によるコンディション変化を管理する
        /// </summary>
        /// <param name="a_DateTime"></param>
        private void ChangeOverTimeCondition(DateTime a_DateTime)
        {
            Debug.Log("ChangeOverTimeCondition : " + a_DateTime.ToString());

            // 睡眠時刻をチェックする
            CheckSleepTime(a_DateTime);
        }


        /// <summary>
        /// 睡眠時刻をチェックする
        /// </summary>
        /// <param name="a_DataTime"></param>
        private void CheckSleepTime(DateTime a_DataTime)
        {
            // 現在時刻から時刻情報だけを取得する
            TimeSpan timeOfDay = DateTime.Now.TimeOfDay;

            // 21:00 ~ 06:00 の範囲をチェックする
            TimeSpan startTime_am = new TimeSpan(0, 0, 0);
            TimeSpan endTime_am = new TimeSpan(5, 59, 59);
            TimeSpan startTime_pm = new TimeSpan(21, 0, 0);
            TimeSpan endTime_pm = new TimeSpan(23, 59, 59);

            // 時間の範囲内か
            if (((startTime_am <= timeOfDay) && (timeOfDay <= endTime_am)) ||
                ((startTime_pm <= timeOfDay) && (timeOfDay <= endTime_pm)))
            {
                // 範囲内なら眠さ度の変更を行う
                ReceptionSleepiness(HoloMonSleepinessLevel.Sleepy);
            }
            else
            {
                // 範囲外なら眠さ度の変更を行う
                ReceptionSleepiness(HoloMonSleepinessLevel.Nothing);
            }
        }
    }
}

f:id:bluebirdofoz:20210327234346j:plain

・HoloMonAISingleton.cs

using UnityEngine;
using UniRx;

using HMProject.HoloMonLogic;

namespace HMProject.HoloMon
{
    public class HoloMonAISingleton : MonoBehaviour
    {
        // --- 中略 ---
        
        /// <summary>
        /// 開始処理
        /// </summary>
        void Start()
        {
            // 音声聞き取り時の処理を設定する
            HoloMonListenSingleton.Instance.IReadOnlyReactivePropertyHoloMonListenWord
                .ObserveOnMainThread()
                .Subscribe(word => {
                    ListenWord(word);
                })
                .AddTo(this);

            // 眠り状態の変化時の処理を設定する
            HoloMonConditionLifeSingleton.Instance.IReadOnlyReactivePropertyHoloMonLifeStatus
                .ObserveOnMainThread()
                .Subscribe(status =>
                {
                    SleepinessLevel(status.SleepinessLevel);
                })
                .AddTo(this);
        }
        
        // --- 中略 ---
        
        /// <summary>
        /// 眠り度の変化に合わせたアクションを実行する
        /// </summary>
        /// <param name="a_SleepinessLevel"></param>
        private void SleepinessLevel(HoloMonSleepinessLevel a_SleepinessLevel)
        {
            Debug.Log("Change SleepinessLevel");
            switch(a_SleepinessLevel)
            {
                case HoloMonSleepinessLevel.Nothing:
                    // 眠気がない場合かつ眠り状態であれば待機状態にして起こす
                    if(HoloMonModeStatusSingleton.Instance.IReadOnlyReactivePropertyHoloMonMode.Value == HoloMonMode.Sleep)
                    {
                        RequestStandby();
                    }
                    break;
                case HoloMonSleepinessLevel.Little:
                    // 少し眠い状態では何も行わない
                    break;
                case HoloMonSleepinessLevel.Sleepy:
                    // 眠い状態なら睡眠の開始要求を行う
                    RequestStartSleep();
                    break;
            }
        }
}

f:id:bluebirdofoz:20210327234357j:plain

シーンを再生して、ホロモンの睡眠時間にステータスの変更が発生することを確認します。
ReactiveProperty に構造体を指定した場合でも、変更が行われたタイミングでのみ通知が行われます。
f:id:bluebirdofoz:20210327233901j:plain