本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
今回はホロモンが時間経過でお腹を空かせるメモです。
活動時間の時間経過を計算する
単純に定期的な時間経過による処理の実行を行う場合は UniRx の Interval または Timer のオペレータを利用すると便利です。
・UniRxを使って一定間隔で処理を行う
https://bluebirdofoz.hatenablog.com/entry/2021/02/26/024511
ただし、今回に関しては以下の条件を満たしたいため、この手法は利用しませんでした。
・アプリを終了した後、再起動しても経過時間を考慮してお腹を空かせる
・睡眠中は空腹度が変化せず、起きている時間のみ、空腹が進行する
2つの時間から時間経過を求める場合、DateTime 型同士を減算して TimeSpan 型の戻り値を取得することができます。
// 始点時刻と終点時刻 DateTime p_StartTime = DateTime.Now; DateTime p_EndTime = DateTime.Now.AddMinutes(30.0f); // 経過時刻を計算する TimeSpan SubtractionTime = a_EndTime - a_StartTime;
最終的に以下のようなスクリプトを作成しました。
・HoloMonConditionLifeSingleton.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UniRx; namespace HMProject.HoloMonCondition { /// <summary> /// ホロモンのライフコンディションの構造定義 /// </summary> [Serializable] public struct HoloMonLifeStatus { /// <summary> /// 空腹度変更日時 /// </summary> public DateTime HungryChangeTime; /// <summary> /// ホロモンの空腹度 /// </summary> public int HungryPercent; /// <summary> /// 機嫌度変更時刻 /// </summary> public DateTime MoodChangeTime; /// <summary> /// ホロモンの機嫌度 /// </summary> public int MoodPercent; /// <summary> /// 眠り度変更時刻 /// </summary> public DateTime SleepinessChangeTime; /// <summary> /// ホロモンの眠り度 /// </summary> public HoloMonSleepinessLevel SleepinessLevel; } /// <summary> /// ホロモンの空腹度設定 /// </summary> [Serializable] public class HoloMonHungrySetting { /// <summary> /// 減少値 /// </summary> public int DecreasePoint; /// <summary> /// 減少間隔(分) /// </summary> public int DecreaseMarginMinute; public HoloMonHungrySetting() { DecreasePoint = 1; DecreaseMarginMinute = 10; } } /// <summary> /// ホロモンの機嫌度設定 /// </summary> [Serializable] public class HoloMonMoodSetting { /// <summary> /// 減少値 /// </summary> public int DecreasePoint; /// <summary> /// 減少間隔(分) /// </summary> public int DecreaseMarginMinute; public HoloMonMoodSetting() { DecreasePoint = 2; DecreaseMarginMinute = 1; } } /// <summary> /// ホロモンの睡眠設定 /// </summary> [Serializable] public class HoloMonSleepSetting { /// <summary> /// 睡眠時間帯(開始) /// </summary> public TimeSpan StartTime; /// <summary> /// 睡眠時間帯(終了) /// </summary> public TimeSpan EndTime; public HoloMonSleepSetting() { StartTime = new TimeSpan(22, 0, 0); EndTime = new TimeSpan(6, 0, 0); } } /// <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> [SerializeField, Tooltip("ホロモンの空腹設定")] private HoloMonHungrySetting p_HoloMonHungrySetting = new HoloMonHungrySetting(); /// <summary> /// ホロモンの機嫌設定 /// </summary> [SerializeField, Tooltip("ホロモンの機嫌設定")] private HoloMonMoodSetting p_HoloMonMoodSetting = new HoloMonMoodSetting(); /// <summary> /// ホロモンの睡眠設定 /// </summary> [SerializeField, Tooltip("ホロモンの睡眠設定")] private HoloMonSleepSetting p_HoloMonSleepSetting = new HoloMonSleepSetting(); /// <summary> /// 時刻判定(分刻み)のトリガー /// </summary> IDisposable p_MinuteTimeTrigger; /// <summary> /// 空腹度の変化 /// </summary> /// <param name="a_HungryPercent"></param> private void ReceptionHungryPercent(int a_HungryPercent) { // 変数の登録を行う // 非変更時は通知が発生しない HoloMonLifeStatus status = p_HoloMonLifeStatus.Value; // 状態の変化が発生するか否か if (status.HungryPercent != a_HungryPercent) { // 状態を設定する status.HungryPercent = a_HungryPercent; // 変更時刻を記録する status.HungryChangeTime = DateTime.Now; // 状態を更新する p_HoloMonLifeStatus.Value = status; } } /// <summary> /// 機嫌度の変化 /// </summary> /// <param name="a_MoodPercent"></param> private void ReceptionMoodPercent(int a_MoodPercent) { // 変数の登録を行う // 非変更時は通知が発生しない HoloMonLifeStatus status = p_HoloMonLifeStatus.Value; // 状態の変化が発生するか否か if (status.MoodPercent != a_MoodPercent) { // 状態を設定する status.MoodPercent = a_MoodPercent; // 変更時刻を記録する status.MoodChangeTime = DateTime.Now; // 状態を更新する p_HoloMonLifeStatus.Value = status; } } /// <summary> /// 眠さの変化 /// </summary> /// <param name="a_Sleepiness"></param> private void ReceptionSleepiness(HoloMonSleepinessLevel a_Sleepiness) { // 変数の登録を行う // 非変更時は通知が発生しない HoloMonLifeStatus status = p_HoloMonLifeStatus.Value; // 状態の変化が発生するか否か if (status.SleepinessLevel != a_Sleepiness) { // 状態を設定する status.SleepinessLevel = a_Sleepiness; // 変更時刻を記録する status.SleepinessChangeTime = DateTime.Now; // 状態を更新する 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 : 再起動時の読み込み処理 ReceptionHungryPercent(50); ReceptionMoodPercent(50); ReceptionSleepiness(HoloMonSleepinessLevel.Nothing); } /// <summary> /// 時間経過によるコンディション変化を管理する /// </summary> /// <param name="a_DateTime"></param> private void ChangeOverTimeCondition(DateTime a_DateTime) { Debug.Log("ChangeOverTimeCondition : " + a_DateTime.ToString()); // 空腹度をチェックする CheckHungry(a_DateTime); // 機嫌度をチェックする CheckMood(a_DateTime); // 睡眠時刻をチェックする CheckSleepTime(a_DateTime); } /// <summary> /// 指定時刻から指定時刻までの活動経過時間(分)を返す /// </summary> /// <param name="a_StartTime"></param> /// <param name="a_EndTime"></param> /// <returns></returns> public int ActivityElapsedMinutes(DateTime a_StartTime, DateTime a_EndTime) { double doubleElapsedTotalMinutes = 0.0f; // 開始時刻が睡眠時刻に含まれるか否か if (IsSleepTime(a_StartTime)) { // 含まれる場合は開始時刻を起床時刻に設定する a_StartTime = new DateTime( a_StartTime.Year, a_StartTime.Month, a_StartTime.Day, p_HoloMonSleepSetting.EndTime.Hours, p_HoloMonSleepSetting.EndTime.Minutes, p_HoloMonSleepSetting.EndTime.Seconds ); } // 終了時刻が睡眠時刻に含まれるか否か if (IsSleepTime(a_StartTime)) { // 含まれる場合は終了時刻を就寝時刻に設定する a_EndTime = new DateTime( a_EndTime.Year, a_EndTime.Month, a_EndTime.Day, p_HoloMonSleepSetting.StartTime.Hours, p_HoloMonSleepSetting.StartTime.Minutes, p_HoloMonSleepSetting.StartTime.Seconds ); } // 経過時間を計算する TimeSpan SubtractionTime = a_EndTime - a_StartTime; // 経過時間を分単位で取得する doubleElapsedTotalMinutes = SubtractionTime.TotalMinutes; if (doubleElapsedTotalMinutes > 0.0f) { // 日数が経っている場合は就寝時間を計算する int sleepMinutes = SubtractionTime.Days * TotalSleepMinutes(); // 就寝時間分を減算する doubleElapsedTotalMinutes -= sleepMinutes; } else { doubleElapsedTotalMinutes = 0.0f; } // 四捨五入して整数で結果を取得する int elapsedTotalMinutes = (int)Math.Round(doubleElapsedTotalMinutes); return elapsedTotalMinutes; } /// <summary> /// 空腹度の変化をチェックする /// </summary> /// <param name="a_DataTime"></param> private void CheckHungry(DateTime a_DataTime) { // 前回の空腹度の変化時刻を取得する DateTime changeTime = p_HoloMonLifeStatus.Value.HungryChangeTime; // 前回の空腹度変化からの経過時間(分)を取得する int elapsedTotalMinutes = ActivityElapsedMinutes(changeTime, a_DataTime); // 減少間隔から減少係数を計算する int decreseCoefficient = elapsedTotalMinutes / p_HoloMonHungrySetting.DecreaseMarginMinute; // 減少値を計算する int totalDecreasePoint = decreseCoefficient * p_HoloMonHungrySetting.DecreasePoint; // 空腹度を減算する int hungryPercent = p_HoloMonLifeStatus.Value.HungryPercent - totalDecreasePoint; // 計算結果の空腹度を設定する ReceptionHungryPercent(hungryPercent); } /// <summary> /// 機嫌度の変化をチェックする /// </summary> /// <param name="a_DataTime"></param> private void CheckMood(DateTime a_DataTime) { // 前回の機嫌度の変化時刻を取得する DateTime changeTime = p_HoloMonLifeStatus.Value.MoodChangeTime; // 前回の機嫌度変化からの経過時間(分)を取得する int elapsedTotalMinutes = ActivityElapsedMinutes(changeTime, a_DataTime); // 減少間隔から減少係数を計算する int decreseCoefficient = elapsedTotalMinutes / p_HoloMonMoodSetting.DecreaseMarginMinute; // 減少値を計算する int totalDecreasePoint = decreseCoefficient * p_HoloMonMoodSetting.DecreasePoint; // 機嫌度を減算する int moodPercent = p_HoloMonLifeStatus.Value.MoodPercent - totalDecreasePoint; // 計算結果の機嫌度を設定する ReceptionMoodPercent(moodPercent); } /// <summary> /// 睡眠時刻をチェックする /// </summary> /// <param name="a_DataTime"></param> private void CheckSleepTime(DateTime a_DataTime) { if(IsSleepTime(a_DataTime)) { // 範囲内なら眠さ度の変更を行う ReceptionSleepiness(HoloMonSleepinessLevel.Sleepy); } else { // 範囲外なら眠さ度の変更を行う ReceptionSleepiness(HoloMonSleepinessLevel.Nothing); } } /// <summary> /// 睡眠時刻か否か /// </summary> /// <param name="a_DataTime"></param> private bool IsSleepTime(DateTime a_DataTime) { // 睡眠時刻か否か bool isSleepTime = false; // 日時から時刻情報だけを取得する TimeSpan timeOfDay = a_DataTime.TimeOfDay; // 0時を跨ぐか否か if (p_HoloMonSleepSetting.StartTime > p_HoloMonSleepSetting.EndTime) { // 睡眠時間の範囲内か if ((p_HoloMonSleepSetting.StartTime <= timeOfDay) && (timeOfDay <= p_HoloMonSleepSetting.EndTime)) { isSleepTime = true; } else { isSleepTime = false; } } else { // 睡眠時間の範囲内か if (((new TimeSpan(0, 0, 0) <= timeOfDay) && (timeOfDay <= p_HoloMonSleepSetting.EndTime)) || ((p_HoloMonSleepSetting.StartTime <= timeOfDay) && (timeOfDay <= new TimeSpan(23, 59, 59)))) { isSleepTime = true; } else { isSleepTime = false; } } return isSleepTime; } /// <summary> /// 1日当たりの睡眠時間(分) /// </summary> private int TotalSleepMinutes() { double doubleTotalSleepMinutes = 0.0f; // 睡眠時間が0時を跨ぐか否か if (p_HoloMonSleepSetting.StartTime > p_HoloMonSleepSetting.EndTime) { // 午前の睡眠時間を加算する TimeSpan AmTotalSleepTime = p_HoloMonSleepSetting.EndTime - new TimeSpan(0, 0, 0); doubleTotalSleepMinutes += AmTotalSleepTime.TotalMinutes; // 午後の睡眠時間を加算する TimeSpan PmTotalSleepTime = new TimeSpan(23, 59, 59) - p_HoloMonSleepSetting.StartTime; doubleTotalSleepMinutes += PmTotalSleepTime.TotalMinutes; } else { // 睡眠時間を加算する TimeSpan TotalSleepTime = p_HoloMonSleepSetting.EndTime - p_HoloMonSleepSetting.StartTime; doubleTotalSleepMinutes += TotalSleepTime.TotalMinutes; } // 四捨五入して整数を取得する int totalSleepMinutes = (int)Math.Round(doubleTotalSleepMinutes); return totalSleepMinutes; } } }
シーンを再生して動作を確認します。
時間経過に合わせて設定値通りにステータスが変化しました。