本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
今回は NavMesh を使わずにホロモンの追跡処理を行うメモです。
前提条件
以下の記事ではホロモンのプレイヤー追跡に NavMesh を利用しました。
しかし現実空間では HoloLens の認識する空間メッシュが逐次変化するため、リアルタイムの追跡で失敗することが多いです。
bluebirdofoz.hatenablog.com
そこで NavMesh を使わない追跡処理もホロモンアプリに実装してみました。
実装コード
以下の追跡スクリプトを作成しました。
・HoloMonModeLogicTrackingTarget.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using Cysharp.Threading.Tasks; using UniRx; using HMProject.HoloMon; using HMProject.HoloMonCondition; using HMProject.HoloMonMoveAgent; using HMProject.HoloMonItemHold; namespace HMProject.HoloMonLogic { public class HoloMonModeLogicTrackingTarget : 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(); // モードを開始して完了を待機する HoloMonActionModeStatus status = await p_ModeLogicCommon.RunModeAsync(); // 終了状態を返却する return new ModeLogicResult(status, new ModeLogicTrackingTargetReturn()); } /// <summary> /// モードキャンセル /// </summary> public void CancelMode() { // 停止処理を行う DisableSetting(); // キャンセル処理を行う p_ModeLogicCommon.CancelMode(); } /// <summary> /// モード内部停止 /// </summary> private void StopMode(HoloMonActionModeStatus a_StopModeStatus) { // 停止処理を行う DisableSetting(); // 停止状態を設定する p_ModeLogicCommon.StopMode(a_StopModeStatus); } /// <summary> /// 固有アクションデータの参照 /// </summary> private ModeLogicTrackingTargetData p_Data => p_ModeLogicCommon.ModeLogicSetting.ModeLogicTrackingTargetData; /// <summary> /// アイテムの保持位置 /// </summary> [SerializeField, Tooltip("アイテムの保持位置")] HoloMonItemHolder p_HoloMonItemHolder; /// <summary> /// 追跡ポイントのマップ位置 /// </summary> [SerializeField, Tooltip("追跡ポイントのマップ位置")] Transform p_TargetPoint; /// <summary> /// スタック確認定期処理 /// </summary> private bool p_StackCheckFlg = false; private float p_StackCheckTime = 1.0f; private float p_StackTimeElapsed; /// <summary> /// スタック判定距離 /// </summary> float p_StackDistance = 0.1f; /// <summary> /// スタック判定用ポジション /// </summary> Vector3 p_StackCheckPostion; /// <summary> /// 前回フレームポジション /// </summary> Vector3 p_PrefPosition; /// <summary> /// 開始処理 /// </summary> void Start() { } /// <summary> /// 定期処理 /// </summary> void Update() { // モードが実行中かチェックする if (p_ModeLogicCommon.ModeLogicStatus == HoloMonActionModeStatus.Runtime) { // 自身の座標 Vector3 selfPostion = p_ModeLogicCommon.ReferenceComponent.GetRigidbody().transform.position; // ターゲットの座標 (Y軸方向の追跡はしないので軸の座標は自身と同じ) Vector3 targetPosition = new Vector3( p_TargetPoint.position.x, selfPostion.y, p_TargetPoint.position.z); // ターゲットまでの距離が停止距離か判定する if (CheckStoppingDistance(targetPosition, selfPostion)) { // ターゲット位置に到達していれば完了とする Debug.Log("Tracking Achievement"); StopMode(HoloMonActionModeStatus.Achievement); return; } // 現在位置と過去位置からスタックを判定する if (CheckImmobility(selfPostion)) { // 到達場所が停止距離以遠の状態でスタックしていれば追跡失敗と判定する Debug.Log("Tracking Missing"); StopMode(HoloMonActionModeStatus.Missing); return; } // アニメーションパラメータに速度設定を反映する // (実速度では変動が大きいため、設定値をそのまま利用する) ApplyAnimationSpeed(p_Data.TrackingSetting.MoveSpeed); // 自身の向き Vector3 selfRotateVector = p_ModeLogicCommon.ReferenceComponent.GetRigidbody().transform.forward; // ターゲットへの方向ベクトル Vector3 toTargetVector = (targetPosition - selfPostion).normalized; // フレーム間隔に合わせて回転速度と移動速度を計算する float deltaRotateSpeed = p_Data.TrackingSetting.RotateSpeed * Time.deltaTime; float deltaMoveSpeed = p_Data.TrackingSetting.MoveSpeed * Time.deltaTime; // 回転を反映する Vector3 newRotateVector = Vector3.RotateTowards(selfRotateVector, toTargetVector, deltaRotateSpeed, 0f); p_ModeLogicCommon.ReferenceComponent.GetRigidbody().transform.rotation = Quaternion.LookRotation(newRotateVector); // 移動ベクトルを位置に反映する Vector3 nextPosition = Vector3.MoveTowards(selfPostion, targetPosition, deltaMoveSpeed); p_ModeLogicCommon.ReferenceComponent.GetRigidbody().transform.position = nextPosition; // 前回フレーム位置を記録する p_PrefPosition = selfPostion; } } /// <summary> /// 追跡モードの設定を有効化する /// </summary> private bool EnableSetting() { if (p_Data.HoldItemSetting.IsHold) { // アイテム保持設定があればアイテム保持のための設定を行う EnableItemHold(p_Data.HoldItemSetting.HoldItemObject); } // アニメーションを追跡モードにする p_ModeLogicCommon.ReferenceAnimation.StartTrackingMode(); // 追跡対象を指定する p_TargetPoint = p_Data.TargetObject.transform; // スタックチェックを初期化する p_StackCheckFlg = false; p_StackTimeElapsed = 0.0f; p_StackCheckPostion = Vector3.zero; return true; } /// <summary> /// 追跡モードの設定を無効化する /// </summary> private bool DisableSetting() { if (p_Data.HoldItemSetting.IsHold) { // アイテム保持設定があればアイテム解放のための設定を行う DisableItemHold(); } // アニメーションを待機モードにする p_ModeLogicCommon.ReferenceAnimation.ReturnStandbyMode(); // 追跡対象をクリアする p_TargetPoint = null; // 回転慣性を切る p_ModeLogicCommon.ReferenceComponent.GetRigidbody().angularVelocity = Vector3.zero; // スタックチェックを初期化する p_StackCheckFlg = false; p_StackTimeElapsed = 0.0f; p_StackCheckPostion = Vector3.zero; return true; } /// <summary> /// アイテムの保持状態を有効化する /// </summary> /// <param name="a_HoldItemObject"></param> /// <returns></returns> private bool EnableItemHold(GameObject a_HoldItemObject) { // アニメーションのアイテム保持モードを有効化する p_ModeLogicCommon.ReferenceAnimation.ChangeHoldItemOption(true); // アイテムを保持位置に固定する p_HoloMonItemHolder.SetHoldItem(a_HoldItemObject); return true; } /// <summary> /// アイテムの保持状態を無効化する /// </summary> private bool DisableItemHold() { // アニメーションのアイテム保持モードを無効化する p_ModeLogicCommon.ReferenceAnimation.ChangeHoldItemOption(false); // アイテムを保持位置から解放する p_HoloMonItemHolder.ReleaseHoldItem(); return true; } /// <summary> /// 到達距離のチェック /// </summary> private bool CheckStoppingDistance(Vector3 a_TargetPosition, Vector3 a_SelfPostion) { bool isReached = false; // ターゲットまでの距離 float toTargetDistance = Vector3.Distance(a_TargetPosition, a_SelfPostion); // 現在のホロモンの大きさに合わせて停止距離を調整する float checkDistance = p_Data.TrackingSetting.StoppingDistance * CurrentHeightScale(); if (toTargetDistance < checkDistance) { isReached = true; } return isReached; } /// <summary> /// 移動を継続できているか(スタックしていないか)のチェック /// </summary> private bool CheckImmobility(Vector3 a_CurrentPostion) { bool isImmovility = false; // 一定時間ごとに移動距離を判定する p_StackTimeElapsed += Time.deltaTime; if (p_StackTimeElapsed > p_StackCheckTime) { // チェック座標を保存済みか否か if (p_StackCheckFlg) { // スタック範囲内でしか動けていないのであれば移動していないと判定する float moveDistance = Vector3.Distance(a_CurrentPostion, p_StackCheckPostion); if (moveDistance < p_StackDistance) { isImmovility = true; } } // 前回位置を記録する p_StackCheckFlg = true; p_StackCheckPostion = a_CurrentPostion; p_StackTimeElapsed = 0.0f; } return isImmovility; } /// <summary> /// 現在の速度をアニメーションパラメータに反映する /// </summary> private void ApplyAnimationSpeed(float a_AgentSpeed) { // 速度をアニメーション調整用にスケール値で補正する // 同じ速度でもスケールが小さければ低い敷居値で走りになる float scaleRatio = CurrentHeightScale(); float scaleAgentSpeed = (a_AgentSpeed / scaleRatio); // 速度をアニメーションに通知する p_ModeLogicCommon.ReferenceAnimation.SetModelSpeed(scaleAgentSpeed); } /// <summary> /// 現在のホロモンの身長を取得する /// </summary> /// <returns></returns> private float CurrentHeightScale() { // ワールド基準のY軸スケール値を身長として扱う return this.transform.lossyScale.y; } } }
適当なオブジェクトに追加し、プレイヤー追跡の要求から呼び出せるようにします。
動作確認
シーンを再生し、動作を確認します。
ホロモンに「おいで」と声をかけてみます。
ホロモンがプレイヤーの方に向かって追跡移動を開始します。
一定距離まで近づくと、追跡を完了したと判定して止まります。
NavMesh と異なり、プレイヤーに向かって真っすぐに進んでくるのみで障害物を避けたりするロジックは実装していません。
一定時間移動できないとスタックしたと判断し、追跡失敗の判定で止まります。