本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
今回は「ピース」と言ったりチョキの手を見せるとホロモンがピースサインをするメモです。
音声認識の追加
最初に「ピース」という音声認識を行えるように設定を変更します。
MRTK のコンフィギュレーションに「ピース」の音声コマンドを追加し、F10 キーでデバッグできるように[KeyCode]を割り当てました。
MRTK で音声認識を設定する手順の詳細は以下のドキュメントを参照ください。
docs.microsoft.com
モーションの追加
次にアニメーションコントローラにピースサインの新規 State を追加します。
モーションにはじゃんけんのチョキポーズを流用しました。
待機中のロジックコード
後はこれまでと同様に、既存の待機コードに音声認識とポーズアクションを追加するだけです。
ホロモンの待機中の実行スクリプトが以下になります。
・HoloMonModeLogicStandby.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.HeadLogicSpace; using HoloMonApp.FieldOfVisionSpace; using HoloMonApp.TactileBodySpace; namespace HoloMonApp.ModeLogicSpace { public class HoloMonModeLogicStandby : 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.ListenWord: { // 音声入力の割込み InterruptListenWordData interruptListenWordData = a_InterruptInfo.InterruptListenWordData; // ヨシの合図をチェックする isProcessed = ListenWord(interruptListenWordData.ListenWord); } break; case HoloMonInterruptType.FieldOfVision: { // 視界オブジェクトの割込み InterruptFieldOfVisionData interruptFieldOfVisionData = a_InterruptInfo.InterruptFieldOfVisionData; // イベント種別をチェックする if (interruptFieldOfVisionData.FieldOfVisionEvent == HoloMonFieldOfVisionEvent.Add) { // 視界オブジェクトが新規検出された場合は 1 秒間よそ見をする isProcessed = Lookaway(interruptFieldOfVisionData.VisionObjectWrap.Object, 1.0f); // よそ見は割込み処理と判定しない isProcessed = false; } } break; 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 ModeLogicStandbyData p_Data => p_ModeLogicCommon.ModeLogicSetting.ModeLogicStandbyData; /// <summary> /// 現在注視中のオブジェクト /// </summary> [SerializeField, Tooltip("現在注視中のオブジェクト")] GameObject p_LookingObject; /// <summary> /// よそ見実行のトリガー /// </summary> IDisposable p_LookawayTrigger; /// <summary> /// 一時アクションの実行のトリガー /// </summary> IDisposable p_TempActionTrigger; void Update() { // モードが実行中かチェックする if (p_ModeLogicCommon.ModeLogicStatus == HoloMonActionModeStatus.Runtime) { // 最も優先度の高い注目オブジェクトを注視する LookObjectByPriority(); } } /// <summary> /// モードの設定を有効化する /// </summary> private bool EnableSetting() { // アニメーションを待機モードにする p_ModeLogicCommon.ReferenceAnimation.StartReactionMode(); // 頭部追従ロジックを設定する ActionHeadLookObject(p_Data.StartLookObject); // 最も優先度の高い注目オブジェクトを注視する LookObjectByPriority(); return true; } /// <summary> /// モードの設定を無効化する /// </summary> private bool DisableSetting() { // アニメーションを待機モードにする p_ModeLogicCommon.ReferenceAnimation.ReturnStandbyMode(); // 頭部追従ロジックを解除する ActionHeadLookObject(null); return true; } /// <summary> /// 最も優先度の高い注目オブジェクトを注視する /// </summary> private void LookObjectByPriority() { // 視界内オブジェクトから現在最も優先度の高い注目オブジェクトを取得する VisionObjectWrap priorityObjectWrap = p_ModeLogicCommon.ReferenceFieldOfVision.CheckCollectionByTypePriority(); if (priorityObjectWrap != null) { // 優先注目オブジェクトが取得できていれば注視リアクションを行う ActionHeadLookObject(priorityObjectWrap.Object); // 理解種別に応じたリアクションを行う RectionUnderStandInformation(priorityObjectWrap.CurrentFeatures()); } else { // 取得できていなければ注視を止める ActionHeadLookObject(null); } } /// <summary> /// 一定時間よそ見をする /// </summary> /// <param name="a_LookObject"></param> private bool Lookaway(GameObject a_LookObject, float a_LookawaySec) { bool isProcessed = false; // 指定のオブジェクトを見る p_ModeLogicCommon.ReferenceLimbControl.Head.ChangeHeadLogic( new HeadLogicSetting(new HeadLogicLookAtTargetData(a_LookObject)) ); // トリガーを設定済みの場合は一旦破棄する p_LookawayTrigger?.Dispose(); // 指定時間後によそ見キャンセルのトリガーを実行する p_LookawayTrigger = Observable .Timer(TimeSpan.FromSeconds(a_LookawaySec)) .SubscribeOnMainThread() .Subscribe(x => { // 再び最も優先度の高い注目オブジェクトを注視する LookObjectByPriority(); }) .AddTo(this); isProcessed = true; return isProcessed; } /// <summary> /// 頭部アクションを行う /// </summary> private void ActionHeadLookObject(GameObject a_LookObject) { if (p_LookingObject != null && a_LookObject != null) { if (p_LookingObject == a_LookObject) { // 対象が同一オブジェクトの場合は処理しない return; } } // 頭部追従ロジックを設定する p_ModeLogicCommon.ReferenceLimbControl.Head.ChangeHeadLogic( (a_LookObject == null) ? new HeadLogicSetting(new HeadLogicNoOverrideData()) : new HeadLogicSetting(new HeadLogicLookAtTargetData(a_LookObject)) ); p_LookingObject = a_LookObject; } /// <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) { // パーの手で触れられていた場合は頭を撫でられたアクションを行う /// 頭を撫でられたアクションを実行する TempReaction(ReactionPose.StrokingHead); isProcessed = true; } } return isProcessed; } /// <summary> /// オブジェクト理解種別に応じたリアクションを制御する /// </summary> /// <returns></returns> private bool RectionUnderStandInformation(ObjectUnderstandInformation a_Info) { bool isReaction = false; // オブジェクト種別ごとに固有の反応を行う switch (a_Info.ObjectUnderstandType) { case ObjectUnderstandType.Unknown: break; case ObjectUnderstandType.Learning: break; case ObjectUnderstandType.Other: break; case ObjectUnderstandType.FriendFace: // 友達が見えていれば状態に応じてリアクションする isReaction = ReactionLookFriend(a_Info.ObjectUnderstandFriendFaceData); break; case ObjectUnderstandType.FriendRightHand: switch (a_Info.ObjectUnderstandFriendRightHandData.HandStatus) { #if UNITY_EDITOR // Editor上では確認のため、ピストル状態でもチョキと判定する case ObjectStatusHand.Hand_Pistol: #endif case ObjectStatusHand.Hand_Choki: // チョキが見えていればピースサインをする isReaction = ReactionPeace(); break; default: break; } break; case ObjectUnderstandType.FriendLeftHand: switch (a_Info.ObjectUnderstandFriendLeftHandData.HandStatus) { #if UNITY_EDITOR // Editor上では確認のため、ピストル状態でもチョキと判定する case ObjectStatusHand.Hand_Pistol: #endif case ObjectStatusHand.Hand_Choki: // チョキが見えていればピースサインをする isReaction = ReactionPeace(); break; default: break; } break; case ObjectUnderstandType.Ball: break; case ObjectUnderstandType.Food: break; case ObjectUnderstandType.Shit: break; case ObjectUnderstandType.Jewel: break; default: break; }; return isReaction; } /// <summary> /// 友人オブジェクト注視時のリアクションを制御する /// </summary> private bool ReactionLookFriend(ObjectUnderstandFriendFaceData a_Data) { bool isReaction = false; // 顔オブジェクトの状態をチェックする switch (a_Data.HeadStatus) { case ObjectStatusHead.Head_Nod: // 相手が頷けばこちらも頷く TempReaction(ReactionPose.HeadNod); isReaction = true; break; case ObjectStatusHead.Head_Shake: // 相手が首を振ればこちらも首を振る TempReaction(ReactionPose.HeadShake); isReaction = true; break; case ObjectStatusHead.Head_TiltLeft: // 相手が左方向に首を傾げればこちらは右方向に首を傾げる TempReaction(ReactionPose.HeadTiltRight); isReaction = true; break; case ObjectStatusHead.Head_TiltRight: // 相手が右方向に首を傾げればこちらは左方向に首を傾げる TempReaction(ReactionPose.HeadTiltLeft); isReaction = true; break; default: // 反応する行動がない場合は特に何もしない break; }; return isReaction; } /// <summary> /// 聞き取った言葉に合わせたアクションを実行する /// </summary> /// <param name="a_ListenWord"></param> private bool ListenWord(HoloMonListenWord a_ListenWord) { bool isProcessed = false; switch (a_ListenWord) { case HoloMonListenWord.Peace: // ピースという言葉を聞いた場合 ReactionPeace(); isProcessed = true; break; default: break; } return isProcessed; } /// <summary> /// ピースサインのリアクションを制御する /// </summary> private bool ReactionPeace() { // 視界内に友達がいるか確認する VisionObjectWrap friendObjectWrap = p_ModeLogicCommon.ReferenceFieldOfVision. CheckCollectionByNearDistance(a_ObjectUnderstandType:ObjectUnderstandType.FriendFace); // 視界内に友達がいない場合は処理しない if (friendObjectWrap == null) return false; // 一定時間友達の方を見る Lookaway(friendObjectWrap.Object, 1.0f); /// ピースサインアクションを実行する TempReaction(ReactionPose.PeaceSign); return true; } /// <summary> /// 一時的なリアクションを実行する /// </summary> /// <returns></returns> private bool TempReaction(ReactionPose a_Pose) { /// ポーズアクションを実行する p_ModeLogicCommon.ReferenceAnimation.SetReactionPose(a_Pose); // トリガーを設定済みの場合は一旦破棄する 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.SetReactionPose(ReactionPose.Nothing); }) .AddTo(this); return true; } } }
動作確認
シーンを再生して動作を確認します。
F10キーを押し、「ピース」と音声認識した場合のイベントを発生させます。
ホロモンが待機状態のとき、こちらを見てピースしてくれました。
後はエディター上でピストルのポーズの手をホロモンに見せます。
こちらもエディター上ではチョキの手と認識され、ホロモンがピースを返してくれます。