本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
今回はホロモンの活動時間を計算するメモです。
ホロモンの活動時間
ホロモンは時間経過に合わせて徐々に空腹度やスタミナが変化します。
一定時間アプリを停止していた場合は前回の停止時間と現在時刻から経過した時間を計算してステータスを変化させる必要があります。
ただしホロモンが睡眠していた時間は空腹度が変化しない時間のため、経過時間から減算する必要があります。
ホロモンの活動時間を計算するスクリプト
以下の通り、ホロモンの睡眠時間の設定から活動時間を計算するスクリプトを作成しました。
・CalculationActivityTime.cs
using System.Collections; using System.Collections.Generic; using System; namespace HoloMonApp.ConditionSpace { public class CalculationActivityTime { /// <summary> /// 睡眠開始時刻 /// </summary> private TimeSpan p_StartSleepTime; /// <summary> /// 睡眠終了時刻 /// </summary> private TimeSpan p_EndSleepTime; public CalculationActivityTime(TimeSpan a_StartSleepTime, TimeSpan a_EndSleepTime) { p_StartSleepTime = a_StartSleepTime; p_EndSleepTime = a_EndSleepTime; } #region "Public Method" /// <summary> /// 指定時刻から指定時刻までの睡眠時間を考慮しない全ての経過時間(TimeSpan)を返す /// </summary> /// <param name="a_StartTime"></param> /// <param name="a_EndTime"></param> /// <returns></returns> public TimeSpan AllElapsedTimeSpan(DateTime a_StartTime, DateTime a_EndTime) { return CalculateAllElapsedTimeSpan(a_StartTime, a_EndTime); } /// <summary> /// 指定時刻から指定時刻までの睡眠時間を考慮した経過時間(TimeSpan)を返す /// </summary> /// <param name="a_StartTime"></param> /// <param name="a_EndTime"></param> /// <returns></returns> public TimeSpan AwakeElapsedTimeSpan(DateTime a_StartTime, DateTime a_EndTime) { return CalculateAwakeElapsedTimeSpan(a_StartTime, a_EndTime); } /// <summary> /// 指定の時刻が睡眠時刻に含まれるか否か /// </summary> /// <param name="a_DataTime"></param> public bool IsSleepTime(DateTime a_DataTime) { return CheckSleepTime(a_DataTime); } #endregion #region "経過時間の計算" /// <summary> /// 指定時刻から指定時刻までの睡眠時間を考慮しない全ての経過時間(TimeSpan)を返す /// </summary> /// <param name="a_StartTime"></param> /// <param name="a_EndTime"></param> /// <returns></returns> private TimeSpan CalculateAllElapsedTimeSpan(DateTime a_StartTime, DateTime a_EndTime) { // 経過時間を計算する TimeSpan subtractionTime = a_EndTime - a_StartTime; // 計算の結果、0 未満になっていた場合は 0 分で返却する if (subtractionTime < TimeSpan.Zero) return TimeSpan.Zero; return subtractionTime; } /// <summary> /// 指定時刻から指定時刻までの睡眠時間を考慮した経過時間(TimeSpan)を返す /// </summary> /// <param name="a_StartTime"></param> /// <param name="a_EndTime"></param> /// <returns></returns> private TimeSpan CalculateAwakeElapsedTimeSpan(DateTime a_StartTime, DateTime a_EndTime) { // 経過時間の計算結果を格納する変数 TimeSpan subtractionTime = TimeSpan.Zero; // 睡眠時間を考慮した指定開始時刻を取得する // 開始時刻が睡眠時刻に含まれる場合は開始時刻から睡眠終了時刻を計算して開始時刻とする DateTime startTime = a_StartTime; if (CheckSleepTime(startTime)) startTime = CalcStartSleepTime(startTime) ?? startTime; // 睡眠時間を考慮した指定終了時刻を取得する // 終了時刻が睡眠時刻に含まれる場合は終了時刻から睡眠開始時刻を計算して終了時刻とする DateTime endTime = a_EndTime; if (CheckSleepTime(endTime)) endTime = CalcEndSleepTime(endTime) ?? endTime; // 最初に次の睡眠時間までの経過時刻を計算する // 事前に睡眠時刻チェックをしているので実際には null は返らない DateTime nextSleepTime = CalcNextSleepTime(startTime) ?? startTime; // 終了時刻が次の睡眠時間を跨ぐか否か if (nextSleepTime > endTime) { // 跨がない場合は終了時刻から開始時刻を引いた時間のみの計算とする subtractionTime += endTime - startTime; } else { // 跨ぐ場合は次の睡眠時間までの時間とそれ以降の日数と活動時間を加算して計算する // 次の睡眠時間までの経過時間を計算する subtractionTime += nextSleepTime - startTime; // それ以降の日数と活動時間を計算する // 次の睡眠時間からの経過時間を取得する TimeSpan subtractionNextDayTime = endTime - nextSleepTime; // 経過日数に合わせて睡眠時間分を減算する // ループ回数は経過日数+1(初日分) int subtractionNextDays = (int)Math.Ceiling(subtractionNextDayTime.TotalDays); for (int loop = 0; loop < subtractionNextDays; loop++) { subtractionNextDayTime -= OneDaySleepTimeSpan(); } // 計算の結果、0 未満になっていた場合は 0 分で計算する if (subtractionNextDayTime < TimeSpan.Zero) return TimeSpan.Zero; // 計算結果を加算する subtractionTime += subtractionNextDayTime; } // 計算の結果、0 未満になっていた場合は 0 分で返却する if (subtractionTime < TimeSpan.Zero) return TimeSpan.Zero; return subtractionTime; } #endregion #region "睡眠時間の計算" /// <summary> /// 1日当たりの睡眠時間(TimeSpan) /// </summary> private TimeSpan OneDaySleepTimeSpan() { TimeSpan sleepTimeSpan = TimeSpan.Zero; // 睡眠時間が0時を跨ぐか否か(睡眠終了時刻が開始時刻の後か) if (p_EndSleepTime > p_StartSleepTime) { // 0時を跨がない場合 // 全体の睡眠時間を引き算で算出する sleepTimeSpan += p_EndSleepTime - p_StartSleepTime; } else { // 0時を跨ぐ場合 // 0時前の睡眠時間と0時後の睡眠時間をそれぞれ算出する sleepTimeSpan += new TimeSpan(1, 0, 0, 0) - p_StartSleepTime; sleepTimeSpan += p_EndSleepTime - new TimeSpan(0, 0, 0); } return sleepTimeSpan; } /// <summary> /// 指定時刻を基に睡眠中であれば睡眠開始からの睡眠時間を返す /// 睡眠中でなければ 0 経過を返す /// </summary> /// <param name="a_CheckTime"></param> /// <returns></returns> private TimeSpan CalcFromSleepTime(DateTime a_CheckTime) { // 指定時刻が睡眠時刻に含まれるか否か if (!CheckSleepTime(a_CheckTime)) { // 含まれないなら 0 経過の時刻を返す return TimeSpan.Zero; } // 睡眠開始時刻を取得する // 事前に睡眠時刻チェックをしているので実際には null は返らない DateTime startSleepTime = CalcStartSleepTime(a_CheckTime) ?? a_CheckTime; // 睡眠開始からの時間を計算する return a_CheckTime - startSleepTime; } /// <summary> /// 指定時刻を基に睡眠中であれば睡眠終了までの睡眠時間を返す /// 睡眠中でなければ 0 経過を返す /// </summary> /// <param name="a_CheckTime"></param> /// <returns></returns> private TimeSpan CalcUntilSleepTime(DateTime a_CheckTime) { // 指定時刻が睡眠時刻に含まれるか否か if (!CheckSleepTime(a_CheckTime)) { // 含まれないなら 0 経過の時刻を返す return TimeSpan.Zero; } // 睡眠終了時刻を取得する // 事前に睡眠時刻チェックをしているので実際には null は返らない DateTime endSleepTime = CalcEndSleepTime(a_CheckTime) ?? a_CheckTime; // 睡眠終了までの時間を計算する return endSleepTime - a_CheckTime; } #endregion #region "睡眠開始/終了時刻計算" /// <summary> /// 指定時刻を基に睡眠開始時刻を返す /// 指定時刻が睡眠中でなければnullを返す /// </summary> /// <param name="a_CheckTime"></param> /// <returns></returns> private DateTime? CalcStartSleepTime(DateTime a_CheckTime) { // 開始時刻が睡眠時刻に含まれるか否か if (!CheckSleepTime(a_CheckTime)) { // 睡眠中でなければnullを返す return null; } // 同じ日付の睡眠開始時刻を取得する DateTime startSleepTime = new DateTime( a_CheckTime.Year, a_CheckTime.Month, a_CheckTime.Day, p_StartSleepTime.Hours, p_StartSleepTime.Minutes, p_StartSleepTime.Seconds ); // 睡眠開始時刻が指定時刻を超えているか否か while (startSleepTime > a_CheckTime) { // 睡眠開始時刻が指定時刻を超えている場合 // 前日の睡眠開始時刻を再計算する startSleepTime = startSleepTime - new TimeSpan(1, 0, 0, 0); } return startSleepTime; } /// <summary> /// 指定時刻を基に睡眠終了時刻を返す /// 指定時刻が睡眠中でなければnullを返す /// </summary> /// <param name="a_CheckTime"></param> /// <returns></returns> private DateTime? CalcEndSleepTime(DateTime a_CheckTime) { // 終了時刻が睡眠時刻に含まれるか否か if (!CheckSleepTime(a_CheckTime)) { // 睡眠中でなければnullを返す return null; } // 同じ日付の睡眠終了時刻を取得する DateTime endSleepTime = new DateTime( a_CheckTime.Year, a_CheckTime.Month, a_CheckTime.Day, p_EndSleepTime.Hours, p_EndSleepTime.Minutes, p_EndSleepTime.Seconds ); // 睡眠終了時刻が指定時刻を超えていないか while (a_CheckTime > endSleepTime) { // 睡眠終了時刻が指定時刻を超えていない場合 // 次の日の睡眠終了時刻を再計算する endSleepTime = endSleepTime + new TimeSpan(1, 0, 0, 0); } return endSleepTime; } /// <summary> /// 指定時刻を基に次の睡眠開始時刻を返す /// 指定時刻が睡眠中ならばnullを返す /// </summary> /// <param name="a_CheckTime"></param> /// <returns></returns> private DateTime? CalcNextSleepTime(DateTime a_CheckTime) { // 開始時刻が睡眠時刻に含まれるか否か if (CheckSleepTime(a_CheckTime)) { // 睡眠中ならnullを返す return null; } // 同じ日付の睡眠開始時刻を取得する DateTime nextSleepTime = new DateTime( a_CheckTime.Year, a_CheckTime.Month, a_CheckTime.Day, p_StartSleepTime.Hours, p_StartSleepTime.Minutes, p_StartSleepTime.Seconds ); // 次の睡眠開始時刻が指定時刻を超えていないか while (a_CheckTime > nextSleepTime) { // 次の睡眠開始時刻が指定時刻を超えていない場合 // 次の日の睡眠開始時刻を再計算する nextSleepTime = nextSleepTime + new TimeSpan(1, 0, 0, 0); } return nextSleepTime; } #endregion #region "共通関数" /// <summary> /// 指定の時刻が睡眠時刻に含まれるか否か /// </summary> /// <param name="a_DataTime"></param> private bool CheckSleepTime(DateTime a_DataTime) { // 日時から時刻情報だけを取得する TimeSpan timeOfDay = a_DataTime.TimeOfDay; // 睡眠時間が0時を跨ぐか否か(睡眠終了時刻が開始時刻の後か) if (p_EndSleepTime > p_StartSleepTime) { // 0時を跨がない場合 // 睡眠時間の範囲内か(開始時刻と終了時刻そのものは睡眠時間に含まない) if ((p_StartSleepTime < timeOfDay) && (timeOfDay < p_EndSleepTime)) { return true; } } else { // 0時を跨ぐ場合 // 睡眠時間の範囲内か(開始時刻と終了時刻そのものは睡眠時間に含まない) if (((new TimeSpan(0, 0, 0) <= timeOfDay) && (timeOfDay < p_EndSleepTime)) || ((p_StartSleepTime < timeOfDay) && (timeOfDay <= new TimeSpan(1, 0, 0, 0)))) { return true; } } return false; } #endregion } }
時間経過を再生で確認するのは難しいため、動作確認には UnityTestFramework の EditMode を利用しました。
UnityTestFramework の詳細な使い方は以下の記事を参照ください。
bluebirdofoz.hatenablog.com
以下の通り、試験スクリプトを実装しました。
・HoloMonChangeOverTimeTest.cs
using System.Collections; using System.Collections.Generic; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; using System; using HoloMonApp.ConditionSpace; namespace Tests { public class HoloMonChangeOverTimeTest { static readonly TimeSpan sr_StartSleepTime = new TimeSpan(22, 0, 0); static readonly TimeSpan sr_EndSleepTime = new TimeSpan(6, 0, 0); CalculationActivityTime p_Instance = new CalculationActivityTime(sr_StartSleepTime, sr_EndSleepTime); /// <summary> /// 経過時間 正常ケース /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AllElapsedTimeSpan01() { DateTime startTime = new DateTime(2022, 1, 1, 2, 0, 0); DateTime endTime = new DateTime(2022, 1, 1, 12, 30, 0); TimeSpan result = p_Instance.AllElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(10, 30, 0).TotalHours, result.TotalHours); } /// <summary> /// 経過時間 マイナスケース /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AllElapsedTimeSpan02() { DateTime startTime = new DateTime(2022, 1, 2, 2, 0, 0); DateTime endTime = new DateTime(2022, 1, 1, 12, 30, 0); TimeSpan result = p_Instance.AllElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(0, 0, 0).TotalHours, result.TotalHours); } /// <summary> /// 経過時間 数日経過ケース /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AllElapsedTimeSpan03() { DateTime startTime = new DateTime(2022, 1, 1, 2, 0, 0); DateTime endTime = new DateTime(2022, 1, 15, 12, 30, 0); TimeSpan result = p_Instance.AllElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(14, 10, 30, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 正常ケース /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan01() { DateTime startTime = new DateTime(2022, 1, 1, 2, 0, 0); DateTime endTime = new DateTime(2022, 1, 1, 12, 30, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(6, 30, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 マイナスケース /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan02() { DateTime startTime = new DateTime(2022, 1, 2, 2, 0, 0); DateTime endTime = new DateTime(2022, 1, 1, 12, 30, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(0, 0, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 数日経過ケース /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan03() { DateTime startTime = new DateTime(2022, 1, 1, 12, 0, 0); DateTime endTime = new DateTime(2022, 1, 3, 12, 30, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(1, 8, 30, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 数日経過ケース(開始が睡眠時間内) /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan04() { DateTime startTime = new DateTime(2022, 1, 1, 2, 0, 0); DateTime endTime = new DateTime(2022, 1, 3, 12, 30, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(1, 14, 30, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 数日経過ケース(終了が睡眠時間内) /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan05() { DateTime startTime = new DateTime(2022, 1, 1, 12, 0, 0); DateTime endTime = new DateTime(2022, 1, 3, 23, 30, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(1, 18, 0, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 数日経過ケース(開始と終了が睡眠時間内) /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan06() { DateTime startTime = new DateTime(2022, 1, 1, 2, 0, 0); DateTime endTime = new DateTime(2022, 1, 3, 23, 30, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(2, 0, 0, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 数ヵ月経過ケース(開始と終了が睡眠時間内) /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan07() { DateTime startTime = new DateTime(2022, 1, 1, 2, 0, 0); DateTime endTime = new DateTime(2022, 3, 2, 2, 0, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(40, 0, 0, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 数日経過ケース(境界値) /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan08() { DateTime startTime = new DateTime(2022, 1, 1, 6, 0, 0); DateTime endTime = new DateTime(2022, 1, 2, 22, 00, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(1, 8, 0, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 睡眠時間内 /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan09() { DateTime startTime = new DateTime(2022, 1, 1, 23, 0, 0); DateTime endTime = new DateTime(2022, 1, 2, 5, 00, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(0, 0, 0, 0).TotalHours, result.TotalHours); } /// <summary> /// 活動時間 睡眠時間内(境界値) /// </summary> [Test] public void HoloMonChangeOverTimeTestSimplePasses_AwakeElapsedTimeSpan10() { DateTime startTime = new DateTime(2022, 1, 1, 22, 0, 0); DateTime endTime = new DateTime(2022, 1, 2, 6, 00, 0); TimeSpan result = p_Instance.AwakeElapsedTimeSpan(startTime, endTime); Assert.AreEqual(new TimeSpan(0, 0, 0, 0).TotalHours, result.TotalHours); } } }
動作確認
TestRunner を起動して試験を実行します。
全ての試験ケースが成功することを確認できました。