本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
今回はホロモンのサイズに応じて視界の強制発見距離を設定するメモです。
前回記事のスクリプトを基に実装を行います。
bluebirdofoz.hatenablog.com
強制発見距離を判定する
スクリプト内に指定の距離より近い場所にあるオブジェクトは視野角や障害物の判定を行わず、発見とする処理を追加します。
ホロモンアプリではホロモンが成長し、巨大化していくので、成長に合わせて強制発見距離も大きくする必要があります。
今回はワールド座標のスケール値を取得し、これに合わせて強制発見距離を変化させることでホロモンから一定の位置にあるオブジェクトを強制発見させました。
・FieldOfVisionWatcher.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; // ToList使用のため using System.Linq; // ReactiveProperty使用のため using UniRx; using UniRx.Triggers; // CoreSystemへのアクセスのため using Microsoft.MixedReality.Toolkit; // 空間認識情報の取得のため using Microsoft.MixedReality.Toolkit.SpatialAwareness; using HMProject.HoloMon; using HMProject.HoloMonUnderstand; namespace HMProject.HoloMonFieldOfVision { /// <summary> /// 視界内オブジェクト管理の定義 /// </summary> [Serializable] public class VisionObjectCollection { /// <summary> /// 更新フレーム /// </summary> public int UpdateFrameCount; /// <summary> /// 視界内オブジェクトリスト /// </summary> public Dictionary<string, VisionObject> VisionObjectDictionary; public VisionObjectCollection() { UpdateFrameCount = 0; VisionObjectDictionary = new Dictionary<string, VisionObject>(); } } /// <summary> /// 視界内オブジェクト情報 /// </summary> [Serializable] public class VisionObject { /// <summary> /// オブジェクト名 /// </summary> public string ObjectName; /// <summary> /// オブジェクトの理解種別 /// </summary> public ObjectUnderstandType UnderstandType; /// <summary> /// オブジェクト状態の理解種別 /// </summary> public ObjectStatusType StatusType; /// <summary> /// コライダー情報 /// </summary> public Collider Collider; /// <summary> /// ゲームオブジェクト情報 /// </summary> public GameObject GameObject; /// <summary> /// 距離 /// </summary> public float Distance; /// <summary> /// 視野角 /// </summary> public float Angle; /// <summary> /// 開始検出フレーム /// </summary> public int FirstFrameCount; /// <summary> /// 最終検出フレーム /// </summary> public int LastFrameCount; public VisionObject(string a_Objectname, ObjectUnderstandType a_UnderstandType, ObjectStatusType a_StatusType, Collider a_Collider, float a_Distance, float a_Angle, int a_Framecount) { ObjectName = a_Objectname; UnderstandType = a_UnderstandType; StatusType = a_StatusType; Collider = a_Collider; GameObject = a_Collider.gameObject; Distance = a_Distance; Angle = a_Angle; FirstFrameCount = a_Framecount; LastFrameCount = a_Framecount; } } /// <summary> /// 視界内オブジェクトリストを表すReactiveDictionary /// </summary> [Serializable] public class FieldOfVisionWatcherReactiveProperty: ReactiveProperty<VisionObjectCollection> { public FieldOfVisionWatcherReactiveProperty() { } public FieldOfVisionWatcherReactiveProperty(VisionObjectCollection a_VisionObjectCollection) : base(a_VisionObjectCollection) { } } [RequireComponent(typeof(SphereCollider))] public class FieldOfVisionWatcher : MonoBehaviour { /// <summary> /// 視界内オブジェクトリストのInspector参照用変数 /// </summary> [SerializeField, Tooltip("視界内オブジェクトリストのInspector参照用変数")] private List<VisionObject> EditorCheckVisionObjectInformations; /// <summary> /// 視界内オブジェクトリスト /// </summary> [SerializeField, Tooltip("視界内オブジェクトリスト")] private FieldOfVisionWatcherReactiveProperty p_VisionObjectCollection = new FieldOfVisionWatcherReactiveProperty(); /// <summary> /// ホロモンのホロモンのボディコンディションのReadOnlyReactivePropertyの保持変数 /// </summary> private IReadOnlyReactiveProperty<VisionObjectCollection> p_IReadOnlyReactivePropertyVisionObjectCollection; /// <summary> /// ホロモンのホロモンのボディコンディションのReadOnlyReactivePropertyの参照変数 /// </summary> public IReadOnlyReactiveProperty<VisionObjectCollection> IReadOnlyReactivePropertyVisionObjectCollection => p_IReadOnlyReactivePropertyVisionObjectCollection ?? (p_IReadOnlyReactivePropertyVisionObjectCollection = p_VisionObjectCollection.ToSequentialReadOnlyReactiveProperty()); // EveryValueChanged を使って更新フレームの変化があった時のみ通知する /// <summary> /// 視界内オブジェクトリストの EveryValueChanged オブザーバ保持変数 /// </summary> private IObservable<int> p_IObservableVisionObjectCollectionEveryValueChanged; /// <summary> /// 視界内オブジェクトリストの EveryValueChanged オブザーバ参照変数 /// </summary> public IObservable<int> IObservableVisionObjectCollectionEveryValueChanged => p_IObservableVisionObjectCollectionEveryValueChanged ?? (p_IObservableVisionObjectCollectionEveryValueChanged = p_VisionObjectCollection.ObserveEveryValueChanged(x => x.Value.UpdateFrameCount)); [SerializeField, Tooltip("視界原点トランスフォーム")] private Transform p_VisionRoot; [SerializeField, Tooltip("空間認識レイヤー")] int p_SpatialAwarenessLayer; [SerializeField, Tooltip("視野角")] private float p_ViewingAngle = 100; [SerializeField, Tooltip("強制発見距離(ホロモン1m時のメートル単位)")] private float p_ForcedDiscoveryDistance = 1.0f; [SerializeField, Tooltip("検出フレーム間隔")] private int p_RecognitionFrameInterval = 100; [SerializeField, Tooltip("ロスト判定フレーム間隔")] private int p_LostSightFrameInterval = 150; /// <summary> /// 起動処理 /// </summary> void Start() { // 空間認識のオブザーバを取得する IMixedRealitySpatialAwarenessMeshObserver SpatialAwarenessMeshObserver = CoreServices.GetSpatialAwarenessSystemDataProvider<IMixedRealitySpatialAwarenessMeshObserver>(); // オブザーバからレイヤー番号を取得する p_SpatialAwarenessLayer = SpatialAwarenessMeshObserver.MeshPhysicsLayer; // コライダーの OnTriggerStay イベントに対する処理を定義する this.OnTriggerStayAsObservable() // OnTriggerStayイベント .BatchFrame() // フレーム毎に値をまとめる .ThrottleFirstFrame(p_RecognitionFrameInterval) // 100フレーム毎に結果を取得する .ObserveOnMainThread() // メインスレッドで行う .Subscribe(colliderlist => { // 現在のフレームカウントを取得する int currentFrameCount = Time.frameCount; foreach (Collider collider in colliderlist) { // 検出オブジェクトを登録する RegistVisionObject(currentFrameCount, collider); } // リストを更新する CheckVisionObjectList(currentFrameCount); // 参照用リストを更新する UpdateGetList(); }) .AddTo(this); } /// <summary> /// 定期処理 /// </summary> void Update() { } /// <summary> /// リストを参照する /// </summary> /// <returns></returns> public List<VisionObject> GetVisionObjectInformations() { return p_VisionObjectCollection.Value.VisionObjectDictionary.Values.ToList(); } /// <summary> /// オブジェクトの理解情報を設定する /// </summary> public void SetObjectUnderstand(string a_Objectname, ObjectUnderstandType a_UnderstandType, ObjectStatusType a_StatusType) { // キーの存在チェック bool isContain = p_VisionObjectCollection.Value.VisionObjectDictionary.ContainsKey(a_Objectname); if (isContain) { // 理解種別を更新する VisionObject visionObjectInformation = p_VisionObjectCollection.Value.VisionObjectDictionary[a_Objectname]; visionObjectInformation.UnderstandType = a_UnderstandType; visionObjectInformation.StatusType = a_StatusType; // Dictionary を更新する p_VisionObjectCollection.Value.VisionObjectDictionary[a_Objectname] = visionObjectInformation; } } /// <summary> /// コライダー情報を登録・更新する /// </summary> /// <param name="a_CurrentFrameCount"></param> /// <param name="a_Collider"></param> private void RegistVisionObject(int a_CurrentFrameCount, Collider a_Collider) { // オブジェクトのレイヤー番号を取得する int layernumber = a_Collider?.gameObject?.layer ?? -1; if ((layernumber == p_SpatialAwarenessLayer) || (layernumber == -1)) { // 空間認識レイヤーの場合は無視する return; } // 視界原点とオブジェクト間のベクトルを算出し、距離と方向ベクトルを取得する Vector3 betweenVector = a_Collider.transform.position - p_VisionRoot.position; float betweenDistance = betweenVector.magnitude; Vector3 betweenDirection = betweenVector.normalized; // 視界原点の方向ベクトルを取得する Vector3 headDirection = p_VisionRoot.rotation * Vector3.forward; // 2つの方向ベクトルの角度差(0°~360°)を取得する float diffAngle = Vector3.Angle(headDirection, betweenDirection); // 強制発見距離を現在のスケールを基に計算する float forcedDiscoveryDistance = p_ForcedDiscoveryDistance * CurrentHeightScale(); // 強制発見距離か否か if (betweenDistance < forcedDiscoveryDistance) { // 発見チェックは行わない } else { // 視野角内か否か if (diffAngle > p_ViewingAngle) { // 視野角外の場合は無視する return; } // レイキャストの結果 RaycastHit[] raycastHits = new RaycastHit[3]; // レイキャストでその方向の衝突オブジェクトを検知する int hitCount = Physics.RaycastNonAlloc(p_VisionRoot.position, betweenDirection, raycastHits, betweenDistance); if (hitCount > 1) { // ヒット数が 1 以上なら障害物(対象以外)があるとみなして無視する return; } } // オブジェクトの参照を取得する GameObject discoveryObject = a_Collider.gameObject; // オブジェクト名を取得する string objectname = discoveryObject.name; // キーの存在チェック bool isContain = p_VisionObjectCollection.Value.VisionObjectDictionary.ContainsKey(objectname); if(!isContain) { // 未登録のオブジェクトの場合 // 検出直後は未確認状態で設定する ObjectUnderstandType understandType = ObjectUnderstandType.NewSpawn; ObjectStatusType statusType = ObjectStatusType.Nothing; // 視界オブジェクト情報を作成する VisionObject visionObjectInfo = new VisionObject( objectname, understandType, statusType, a_Collider, betweenDistance, diffAngle, a_CurrentFrameCount); // Dictionary に追加する p_VisionObjectCollection.Value.VisionObjectDictionary.Add(objectname, visionObjectInfo); } else { // 登録済みのオブジェクトの場合 // 情報と検出フレームを更新する VisionObject visionObjectInformation = p_VisionObjectCollection.Value.VisionObjectDictionary[objectname]; visionObjectInformation.Collider = a_Collider; visionObjectInformation.GameObject = a_Collider.gameObject; visionObjectInformation.Distance = betweenDistance; visionObjectInformation.LastFrameCount = a_CurrentFrameCount; // Dictionary を更新する p_VisionObjectCollection.Value.VisionObjectDictionary[objectname] = visionObjectInformation; } } /// <summary> /// 視界内オブジェクトリストを更新する /// </summary> /// <param name="a_CurrentFrameCount"></param> private void CheckVisionObjectList(int a_CurrentFrameCount) { List<string> keys = p_VisionObjectCollection.Value.VisionObjectDictionary.Keys.ToList(); // 全リストをチェックする foreach(string key in keys) { // 値を取得する VisionObject info = p_VisionObjectCollection.Value.VisionObjectDictionary[key]; // 最終更新フレームが削除フレーム間隔より前なら削除する if ((a_CurrentFrameCount - info.LastFrameCount) > p_LostSightFrameInterval) { p_VisionObjectCollection.Value.VisionObjectDictionary.Remove(key); } } // 更新フレームの値を現在フレームで設定する p_VisionObjectCollection.Value.UpdateFrameCount = a_CurrentFrameCount; } /// <summary> /// 現在のホロモンの身長を取得する /// </summary> /// <returns></returns> private float CurrentHeightScale() { // ワールド基準のY軸スケール値を身長として扱う return this.transform.lossyScale.y; } /// <summary> /// 参照用変数の更新 /// </summary> private void UpdateGetList() { EditorCheckVisionObjectInformations = p_VisionObjectCollection.Value.VisionObjectDictionary.Values.ToList(); } } }

該当処理のコードは以下になります。
[SerializeField, Tooltip("強制発見距離(ホロモン1m時のメートル単位)")] private float p_ForcedDiscoveryDistance = 1.0f; // ----- 中略 ----- // 強制発見距離を現在のスケールを基に計算する float forcedDiscoveryDistance = p_ForcedDiscoveryDistance * CurrentHeightScale(); // 強制発見距離か否か if (betweenDistance < forcedDiscoveryDistance) { // 発見チェックは行わない } // ----- 中略 ----- /// <summary> /// 現在のホロモンの身長を取得する /// </summary> /// <returns></returns> private float CurrentHeightScale() { // ワールド基準のY軸スケール値を身長として扱う return this.transform.lossyScale.y; }
作成したスクリプトをオブジェクト検出用のコライダーが設定されたオブジェクトに追加します。
また、このオブジェクトはホロモンの頭部オブジェクトに追従するようスクリプトを設定しています。
動作確認
シーンを再生して動作を確認します。
ホロモンの正面にいるときはコライダーの距離で視野角に収まり、障害物がければ見えていると判定されます。
そのままホロモンの後ろ側に回り込んでみます。
ホロモンの視野角から外れたため、見えないと判定され、ホロモンがこちらを見失いました。
後ろから近づき、強制発見距離より近くに寄ります。
ホロモンがこちらを発見し、振り向きました。