本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
今回は眠っているときに他の行動を要求しても寝続けるようにするメモです。
ホロモンの眠り行動
ホロモンの眠り行動を実装した記事は以下になります。
bluebirdofoz.hatenablog.com
実行条件の判定用クラス
様々な要因で呼び出される行動の実行要求を、ホロモンの状態を確認して実行の有無を判断する以下のクラスを実装しました。
現在のホロモンの状態をチェックして眠り行動中であれば要求を拒否します。
・HoloMonAICente.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using Cysharp.Threading.Tasks; using UniRx; using HoloMonApp.ActionSpace; using HoloMonApp.ConditionSpace; using HoloMonApp.SaveDataSpace; using HoloMonApp.FieldOfVisionSpace; using HoloMonApp.DataFormatSpace; namespace HoloMonApp.CommonSpace { /// <summary> /// ホロモンの目的行動を定義する /// </summary> public class HoloMonAICenter : MonoBehaviour { /// <summary> /// 現在の行動目的 /// </summary> [SerializeField, Tooltip("現在の行動目的")] private PurposeInformation p_CurrentPurposeInformation; /// <summary> /// ホロモンの行動コントローラの参照 /// </summary> [SerializeField, Tooltip("ホロモンの行動コントローラの参照")] private HoloMonPorposeController p_HoloMonPorpuseController; /// <summary> /// ホロモンのデータセーブコントローラの参照 /// </summary> [SerializeField, Tooltip("ホロモンのデータセーブコントローラの参照")] private HoloMonDataController p_HoloMonDataController; /// <summary> /// 開始処理 /// </summary> void Start() { // 最初の行動目的は待機状態とする p_CurrentPurposeInformation = new PurposeInformation(new PurposeStandbyData()); } /// <summary> /// 定期処理 /// </summary> void Update() { } /// <summary> /// 現在のホロモンの目的意識を返す /// </summary> /// <returns></returns> public HoloMonPurposeType PurposeType() { return p_CurrentPurposeInformation.HoloMonPurposeType; } /// <summary> /// アクションの完了チェック /// </summary> /// <param name="a_ActionResult"></param> private bool CheckActionComplete(HoloMonPurposeStatus a_ActionResult) { if(a_ActionResult == HoloMonPurposeStatus.Cancel) { // アクションがキャンセルで終了していた場合(割込みがあった場合) // 本スレッドを終了する return false; } switch(a_ActionResult) { case HoloMonPurposeStatus.Achievement: // TODO : 目標達成時の後処理を行う break; case HoloMonPurposeStatus.Missing: // TODO : 目標未達成時の後処理を行う break; default: break; } // アクションがキャンセル以外で完了していた場合は待機スレッドを開始する // TODO : 未達成の目的が残っていた場合はそちらの処理を再開する RequestStandby(); return true; } /// <summary> /// 待機状態の要求 /// </summary> public bool RequestStandby() { Debug.Log("RequestStandby"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // 待機行動を実行する ExecuteStandbyAsync(); return true; } /// <summary> /// プレイヤーに注目する要求 /// </summary> public bool RequestLookPlayer() { Debug.Log("RequestLookPlayer"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // プレイヤーへの注視を実行する ExecuteLookPlayerAsync(); return true; } /// <summary> /// プレイヤー追跡の要求 /// </summary> public bool RequestTrackingPlayer() { Debug.Log("RequestTrackingPlayer"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // プレイヤー追跡を実行する ExecuteTrackingPlayerAsync(); return true; } /// <summary> /// じゃんけん開始の要求 /// </summary> public bool RequestJanken() { Debug.Log("RequestJanken"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // じゃんけん行動を実行する ExecuteJankenAsync(); return true; } /// <summary> /// 眠り開始/停止の要求 /// </summary> public bool RequestSleep(bool onoff) { Debug.Log("RequestSleep"); if (onoff) { // 睡眠中でなければ眠る if (!CheckPurposeIsSleep()) { // 眠り行動を実行する ExecuteSleepAsync(); } else { Debug.Log("Cancel : Current Sleep"); return false; } } else { // 睡眠中なら目を覚ます if (CheckPurposeIsSleep()) { // 待機行動を実行する ExecuteStandbyAsync(); } else { Debug.Log("Cancel : Current Awake"); return false; } } return true; } /// <summary> /// ダンス開始の要求 /// </summary> public bool RequestDance() { Debug.Log("RequestDance"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // ダンス行動を実行する ExecuteDanceAsync(); return true; } /// <summary> /// 食事行動の要求 /// </summary> public bool RequestGiveFood(GameObject a_FoodObject) { Debug.Log("RequestGiveFood"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // 食事行動を実行する ExecuteGiveFoodAsync(a_FoodObject); return true; } /// <summary> /// ボール遊び行動の要求 /// </summary> public bool RequestGiveBall(GameObject a_BallObject) { Debug.Log("RequestGiveBall"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // 待機行動を実行する ExecuteGiveBallAsync(a_BallObject); return true; } /// <summary> /// うんち行動の要求 /// </summary> public bool RequestPutoutShit() { Debug.Log("RequestPutoutShit"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // うんち行動を実行する ExecutePutoutShitAsync(); return true; } /// <summary> /// 待て行動の要求 /// </summary> public bool RequestStayWait() { Debug.Log("RequestStayWait"); // 睡眠中は行動しない if (CheckPurposeIsSleep()) { Debug.Log("Cancel : Current Sleep"); return false; } // 待て行動を実行する ExecuteStayWaitAsync(); return true; } /// <summary> /// 待機状態の実行 /// </summary> public async UniTask<bool> ExecuteStandbyAsync() { // 待機の行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeStandbyData()); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// プレイヤーへの注目行動の実行 /// </summary> public async UniTask<bool> ExecuteLookPlayerAsync() { // プレイヤーに注目する行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeLookFriendData()); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// プレイヤー追跡の実行 /// </summary> public async UniTask<bool> ExecuteTrackingPlayerAsync() { // プレイヤーを捜索して追跡する行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeMoveTrackingData(ObjectUnderstandType.FriendFace)); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// じゃんけん行動の実行 /// </summary> public async UniTask<bool> ExecuteJankenAsync() { // じゃんけんを行う行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeJankenData()); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// 眠り行動の実行 /// </summary> public async UniTask<bool> ExecuteSleepAsync() { // 睡眠をとる行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeSleepData()); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// ダンス行動の実行 /// </summary> public async UniTask<bool> ExecuteDanceAsync() { // ダンスを行う行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeDanceData()); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// 食事行動の実行 /// </summary> public async UniTask<bool> ExecuteGiveFoodAsync(GameObject a_FoodObject) { // 食事を取る行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeMealFoodData(a_FoodObject)); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// ボール遊び行動の実行 /// </summary> public async UniTask<bool> ExecuteGiveBallAsync(GameObject a_BallObject) { // ボールで遊ぶ行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeCatchBallData(a_BallObject)); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// うんち行動の実行 /// </summary> public async UniTask<bool> ExecutePutoutShitAsync() { // うんちをする行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposePutoutShitData()); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// 待て行動の実行 /// </summary> public async UniTask<bool> ExecuteStayWaitAsync() { // 待て(おすわり)を行う行動目的を設定する p_CurrentPurposeInformation = new PurposeInformation(new PurposeStayWaitData()); // 行動目的の処理を開始する HoloMonPurposeStatus result = await p_HoloMonPorpuseController.StartPorpuseAsync(p_CurrentPurposeInformation); // アクションの完了チェックを行う return CheckActionComplete(result); } /// <summary> /// 実行待機中のロジックに割込み通知を行う /// </summary> public bool TransmissionInterrupt(InterruptInformation a_InterruptInfo) { bool isProcessed = p_HoloMonPorpuseController .TransmissionInterrupt(a_InterruptInfo); return isProcessed; } /// <summary> /// データ書き込みの要求 /// </summary> public void RequestDataSave() { // 身体状態をデータに設定する HoloMonBodyStatus bodyStatus = HoloMonConditionBodySingleton. Instance.IReadOnlyReactivePropertyHoloMonBodyStatus.Value; p_HoloMonDataController.SetBodyStatus(bodyStatus); // 生活状態をデータに設定する HoloMonLifeStatus lifeStatus = HoloMonConditionLifeSingleton. Instance.IReadOnlyReactivePropertyHoloMonLifeStatus.Value; p_HoloMonDataController.SetLifeStatus(lifeStatus); // データを書き込む p_HoloMonDataController.ExecuteSave(); } /// <summary> /// データ読み込みの要求 /// </summary> public void RequestDataLoad() { // データを読み込む bool isLoaded = p_HoloMonDataController.ExecuteLoad(); // データを読み込めたか否か if (isLoaded) { // データから身体状態を設定する HoloMonConditionBodySingleton.Instance.SetHoloMonData(p_HoloMonDataController.GetSaveData()); // データから生活状態を設定する HoloMonConditionLifeSingleton.Instance.SetHoloMonData(p_HoloMonDataController.GetSaveData()); } } /// <summary> /// 現在の行動がスタンバイまたは注視待機中か否か /// </summary> /// <returns></returns> private bool CheckPurposeIsStanbyAndLooking() { bool isCheckPurpose = false; switch (p_CurrentPurposeInformation.HoloMonPurposeType) { // スタンバイまたは注視待機中 case HoloMonPurposeType.Standby: case HoloMonPurposeType.LookFriend: isCheckPurpose = true; break; default: break; } return isCheckPurpose; } /// <summary> /// 現在の行動が眠り行動中かチェックする /// </summary> /// <returns></returns> private bool CheckPurposeIsSleep() { bool isCheckPurpose = false; switch (p_CurrentPurposeInformation.HoloMonPurposeType) { // 眠り行動中 case HoloMonPurposeType.Sleep: isCheckPurpose = true; break; default: break; } return isCheckPurpose; } } }
ただプレイヤーのアクションに全く反応を返さないのは寂しいので、新たにモーションを作成して頭を撫でると寝ながら尻尾を振るようにしました。
こちらの処理は行動ロジック内の割込み処理で記述しています。
・HoloMonModeLogicSleep.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using Cysharp.Threading.Tasks; using UniRx; using HoloMonApp.CommonSpace; using HoloMonApp.DataFormatSpace; using HoloMonApp.TactileBodySpace; namespace HoloMonApp.ModeLogicSpace { public class HoloMonModeLogicSleep : MonoBehaviour, HoloMonModeLogicInterface { /// <summary> /// モードロジック共通情報 /// </summary> [SerializeField, Tooltip("モードロジック共通情報")] private HoloMonModeLogicCommon p_ModeLogicCommon = new HoloMonModeLogicCommon(); /// <summary> /// 現在の実行待機中フラグ /// </summary> /// <returns></returns> public bool CurrentRunAwaitFlg() { return p_ModeLogicCommon.RunAwaitFlg; } /// <summary> /// モード実行(async/await制御) /// </summary> public async UniTask<ModeLogicResult> RunModeAsync(ModeLogicSetting a_ModeLogicSetting) { // 設定アクションデータを保持する p_ModeLogicCommon.SaveCommonSetting(a_ModeLogicSetting); // 開始処理を行う EnableSetting(); // モードを開始して完了を待機する ModeLogicResult result = await p_ModeLogicCommon.RunModeAsync(); // 終了状態を返却する return result; } /// <summary> /// モードキャンセル /// </summary> public void CancelMode() { // 停止処理を行う DisableSetting(); // キャンセル処理を行う p_ModeLogicCommon.CancelMode(); } /// <summary> /// モード内部停止 /// </summary> private void StopMode(ModeLogicResult a_StopModeLogicResult) { // 停止処理を行う DisableSetting(); // 停止状態を設定する p_ModeLogicCommon.StopMode(a_StopModeLogicResult); } /// <summary> /// 割込み通知 /// </summary> public bool TransmissionInterrupt(InterruptInformation a_InterruptInfo) { bool isProcessed = false; // 処理対象の割込み処理のみ記述 switch (a_InterruptInfo.HoloMonInterruptType) { case HoloMonInterruptType.TactileBody: { // 触覚オブジェクトの割込み InterruptTactileBodyData interruptTactileBodyData = a_InterruptInfo.InterruptTactileBodyData; // イベント種別をチェックする if (interruptTactileBodyData.TactileBodyEvent == HoloMonTactileBodyEvent.Add) { if (interruptTactileBodyData.TactileBodyLimb == HoloMonTactileBodyLimb.Head) { // 頭部への触覚オブジェクトが新規検出された場合は反応する isProcessed = TouchHeadReaction(interruptTactileBodyData.TactileObjectWrap); } } } break; default: break; } return isProcessed; } /// <summary> /// 固有アクションデータの参照 /// </summary> private ModeLogicSleepData p_Data => p_ModeLogicCommon.ModeLogicSetting.ModeLogicSleepData; /// <summary> /// 一時アクションの実行のトリガー /// </summary> IDisposable p_TempActionTrigger; /// <summary> /// モードの設定を有効化する /// </summary> private bool EnableSetting() { // アニメーションを睡眠モードにする p_ModeLogicCommon.ReferenceAnimation.StartSleepMode(); return true; } /// <summary> /// モードの設定を無効化する /// </summary> private bool DisableSetting() { // アニメーションを待機モードにする p_ModeLogicCommon.ReferenceAnimation.ReturnStandbyMode(); return true; } /// <summary> /// 頭に触れられたことに反応する /// </summary> /// <param name="a_TaouchObject"></param> private bool TouchHeadReaction(TactileObjectWrap a_TactileObjectWrap) { bool isProcessed = false; // 頭部に触れたオブジェクトが右手もしくは左手か if ((a_TactileObjectWrap.CurrentFeatures().ObjectUnderstandType == ObjectUnderstandType.FriendRightHand) || (a_TactileObjectWrap.CurrentFeatures().ObjectUnderstandType == ObjectUnderstandType.FriendLeftHand)) { // 手の状態を取得する int HandStatusHash = a_TactileObjectWrap.CurrentFeatures().ObjectUnderstandDataInterface.StatusHash(); #if UNITY_EDITOR // Editor上では確認のため、ピストル状態でパーと判定する if (HandStatusHash == (int)ObjectStatusHand.Hand_Pistol) HandStatusHash = (int)ObjectStatusHand.Hand_Par; #endif if (HandStatusHash == (int)ObjectStatusHand.Hand_Par) { // パーの手で触れられていた場合は頭を撫でられたアクションを行う /// 頭を撫でられたアクションを実行する p_ModeLogicCommon.ReferenceAnimation.SetSleepPose(SleepPose.SleepFun); // トリガーを設定済みの場合は一旦破棄する p_TempActionTrigger?.Dispose(); // アニメーション完了後キャンセルのトリガーを実行する p_TempActionTrigger = p_ModeLogicCommon.ReferenceAnimation.AnimationTrigger .OnStateUpdateAsObservable() .Where(onStateInfo => !onStateInfo.Animator.IsInTransition(onStateInfo.LayerIndex)) // アニメーションの移行が完了するのを待つ .Where(onStateInfo => onStateInfo.StateInfo.normalizedTime > 0.95) // アニメーションの完了直前を検出する .Take(1) .Subscribe(_ => { // 通常ポーズに戻す p_ModeLogicCommon.ReferenceAnimation.SetSleepPose(SleepPose.Nothing); }) .AddTo(this); isProcessed = true; } } return isProcessed; } } }
動作確認
シーンを再生して動作を確認します。
22時を過ぎると、ホロモンが寝始めます。
この状態でコマンドの実行や声掛けをしてみますが、起きる様子はありません。
頭を撫でてやると、起きはしませんが尻尾を振ってくれます。