MRが楽しい

MRやVRについて学習したことを書き残す

HoloLens2でホロモンアプリを作る その34(ホロモンが見るものに優先度をつける)

本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
f:id:bluebirdofoz:20210430235431j:plain

今回はホロモンが見るものに優先度をつけるメモです。

オブジェクトに優先度を設定する

前回まででホロモンの視界にあるオブジェクトを検出するロジックを作成しました。
bluebirdofoz.hatenablog.com

今回はホロモンの視界にあるオブジェクト群に優先度をつけ、ホロモンが注目するオブジェクトを決定するロジックを組み込みます。
見えているオブジェクトが更新されたタイミングで以下の判定ロジックが走るスクリプトを作成しました。
1.対象のオブジェクトは興味がある(発見して間もない)オブジェクトか
2.対象のオブジェクトは他のオブジェクトより興味のある(食べ物>その他など)オブジェクトか
3.優先度が同じオブジェクトの場合は距離が近いほうを優先する
・HoloMonFieldOfVisionReactiveProperty.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UniRx;

using HMProject.HoloMon;

namespace HMProject.HoloMonFieldOfVision
{
    /// <summary>
    /// 視界オブジェクトの定義
    /// </summary>
    [Serializable]
    public class HoloMonFieldOfVisionObject
    {
        /// <summary>
        /// 視界オブジェクトの有無
        /// </summary>
        public bool Vision;

        /// <summary>
        /// オブジェクト名
        /// </summary>
        public string ObjectName;

        /// <summary>
        /// オブジェクトトランスフォーム
        /// </summary>
        public GameObject GameObject;

        /// <summary>
        /// オブジェクトの理解種別
        /// </summary>
        public ObjectUnderstandType UnderstandType;

        /// <summary>
        /// オブジェクト状態の理解種別
        /// </summary>
        public ObjectStatusType StatusType;

        public HoloMonFieldOfVisionObject(bool a_Vision, string a_Objectname,
            GameObject a_GameObject, ObjectUnderstandType a_UnderstandType, ObjectStatusType a_StatusType)
        {
            Vision = a_Vision;
            ObjectName = a_Objectname;
            GameObject = a_GameObject;
            UnderstandType = a_UnderstandType;
            StatusType = a_StatusType;
        }

        public bool CompareFieldOfVision(HoloMonFieldOfVisionObject a_HoloMonFieldOfVision)
        {
            return (
                (this.Vision == a_HoloMonFieldOfVision.Vision) &&
                (this.ObjectName == a_HoloMonFieldOfVision.ObjectName) &&
                (this.UnderstandType == a_HoloMonFieldOfVision.UnderstandType) &&
                (this.StatusType == a_HoloMonFieldOfVision.StatusType)
                );
        }
    }

    /// <summary>
    /// ホロモンのボディコンディションを表すReactiveProperty
    /// </summary>
    [Serializable]
    public class HoloMonFieldOfVisionReactiveProperty : ReactiveProperty<HoloMonFieldOfVisionObject>
    {
        public HoloMonFieldOfVisionReactiveProperty()
        {
        }
        public HoloMonFieldOfVisionReactiveProperty(HoloMonFieldOfVisionObject a_HoloMonFieldOfVision) : base(a_HoloMonFieldOfVision)
        {
        }
    }

    public class HoloMonFieldOfVisionSingleton : MonoBehaviour
    {
        /// <summary>
        /// 単一インスタンス
        /// </summary>
        private static HoloMonFieldOfVisionSingleton ps_instance;

        /// <summary>
        /// 参照用インスタンス
        /// </summary>
        public static HoloMonFieldOfVisionSingleton Instance
        {
            get
            {
                if (ps_instance == null && ps_searchForInstance)
                {
                    ps_searchForInstance = false;
                    var ps_instances = FindObjectsOfType<HoloMonFieldOfVisionSingleton>();
                    if (ps_instances.Length == 1)
                    {
                        ps_instance = ps_instances[0];
                    }
                    else if (ps_instances.Length > 1)
                    {
                        Debug.LogErrorFormat("Expected exactly 1 {0} but found {1}.", ps_instance.GetType().ToString(), ps_instances.Length);
                    }
                }
                return ps_instance;
            }
        }

        private static bool ps_searchForInstance = true;


        /// <summary>
        /// 視界オブジェクトの監視参照
        /// </summary>
        [SerializeField, Tooltip("視界オブジェクトの監視参照")]
        private FieldOfVisionWatcher p_Watcher;


        /// <summary>
        /// 最優先注目オブジェクト名
        /// </summary>
        [SerializeField, Tooltip("最優先注目オブジェクト名")]
        private string p_AttentionObjectName;


        /// <summary>
        /// 視界内のホロモンの見てるもの
        /// </summary>
        [SerializeField, Tooltip("視界内のホロモンの見てるもの")]
        private HoloMonFieldOfVisionReactiveProperty p_HoloMonVision
            = new HoloMonFieldOfVisionReactiveProperty();

        /// <summary>
        /// 視界内のホロモンの見てるもののReadOnlyReactivePropertyの保持変数
        /// </summary>
        private IReadOnlyReactiveProperty<HoloMonFieldOfVisionObject> p_IReadOnlyReactivePropertyHoloMonVision;

        /// <summary>
        /// 視界内のホロモンの見てるもののReadOnlyReactivePropertyの参照変数
        /// </summary>
        public IReadOnlyReactiveProperty<HoloMonFieldOfVisionObject> IReadOnlyReactivePropertyHoloMonVision
            => p_IReadOnlyReactivePropertyHoloMonVision
            ?? (p_IReadOnlyReactivePropertyHoloMonVision = p_HoloMonVision.ToSequentialReadOnlyReactiveProperty());


        /// <summary>
        /// ホロモンの見てるものの設定
        /// </summary>
        private void ReceptionVision(HoloMonFieldOfVisionObject a_HoloMonVison)
        {
            // 非変更時は通知が発生しない
            HoloMonFieldOfVisionObject fieldOfVision = p_HoloMonVision.Value;

            // 状態の変化が発生するか否か
            if (!fieldOfVision.CompareFieldOfVision(a_HoloMonVison))
            {
                // 状態を更新する
                p_HoloMonVision.SetValueAndForceNotify(a_HoloMonVison);
            }
        }


        /// <summary>
        /// オブジェクト理解種別ごとの優先度リスト
        /// </summary>
        [SerializeField, Tooltip("オブジェクト理解種別ごとの優先度リスト")]
        private List<ObjectPriorityUnderstandType> p_ObjectPriorityUnderstandTypeList;

        /// <summary>
        /// 興味を失うまでのフレーム間隔
        /// </summary>
        //[SerializeField, Tooltip("興味を失うまでのフレーム間隔")]
        private int p_LostInterestFrameInterval = 1000;



        /// <summary>
        /// チェック用の固定長優先度リスト
        /// </summary>
        private int[] p_StaticPriorityList;

        /// <summary>
        /// 視界オブジェクト更新のトリガー
        /// </summary>
        IDisposable p_VisionUpdateTrigger;


        /// <summary>
        /// 開始処理
        /// </summary>
        void Start()
        {
            // チェック用の固定長優先度リストを作成しておく
            p_StaticPriorityList = MakeStaticPriorityUnderstandTypeList();

            p_VisionUpdateTrigger = p_Watcher.IObservableVisionObjectCollectionEveryValueChanged
                .ObserveOnMainThread()
                .Subscribe(framecount =>
                {
                    // 現在の視界にあるオブジェクトから注視するオブジェクトを選別する
                    List<VisionObject> visionObjectList = p_Watcher.GetVisionObjectInformations();
                    VisionObjectPickUp(visionObjectList);
                })
                .AddTo(this);
        }

        /// <summary>
        /// 定期処理
        /// </summary>
        void Update()
        {
        }

        /// <summary>
        /// オブジェクトの理解情報を設定する
        /// </summary>
        public void SetObjectUnderstand(string a_Objectname, ObjectUnderstandType a_UnderstandType, ObjectStatusType a_StatusType)
        {
            p_Watcher.SetObjectUnderstand(a_Objectname, a_UnderstandType, a_StatusType);
        }


        /// <summary>
        /// 最優先オブジェクト名を指定する
        /// </summary>
        /// <param name="a_ObjectName"></param>
        public void SetAttentionObject(string a_ObjectName)
        {
            p_AttentionObjectName = a_ObjectName;
        }

        /// <summary>
        /// 最優先オブジェクトをクリアする
        /// </summary>
        /// <param name="a_ObjectName"></param>
        public void ClearAttentionObject()
        {
            p_AttentionObjectName = "";
        }

        /// <summary>
        /// 友人オブジェクトを捜索する
        /// </summary>
        /// <returns></returns>
        public string FindFriend()
        {
            VisionObject findVisionObject = FindTypeObject(ObjectUnderstandType.FriendFace);

            string objectname = findVisionObject?.ObjectName ?? "";

            return objectname;
        }


        /// <summary>
        /// 視界から指定オブジェクト種別のオブジェクトを探す
        /// </summary>
        private VisionObject FindTypeObject(ObjectUnderstandType a_UnderstandType)
        {
            VisionObject findVisionObject = null;

            // 現在の視界にあるオブジェクトから指定種別のオブジェクトを選別する
            List<VisionObject> visionObjectList = p_Watcher.GetVisionObjectInformations();

            foreach (VisionObject visionObject in visionObjectList)
            {
                if (visionObject.UnderstandType == a_UnderstandType)
                {
                    // オブジェクト種別が一致すれば対象とする
                    if (findVisionObject == null)
                    {
                        findVisionObject = visionObject;
                    }
                    else
                    {
                        // すでに発見済みであれば距離が近い方を優先する
                        if (visionObject.Distance < findVisionObject.Distance)
                        {
                            findVisionObject = visionObject;
                        }
                    }
                }
            }

            return findVisionObject;
        }


        /// <summary>
        /// チェック用の固定長優先度リストを作成する
        /// </summary>
        private int[] MakeStaticPriorityUnderstandTypeList()
        {
            int[] priorityList = new int[Enum.GetNames(typeof(ObjectUnderstandType)).Length];

            // リストを初期化する
            for (int index = 0; index < priorityList.Length; index++)
            {
                priorityList[index] = 0;
            }

            // オブジェクト理解種別ごとの優先度リストに対応する優先度があれば設定する
            foreach(ObjectPriorityUnderstandType priority in p_ObjectPriorityUnderstandTypeList)
            {
                priorityList[(int)priority.ObjectUnderstand] = priority.Priority;
            }

            return priorityList;
        }

        /// <summary>
        /// 現在の視界オブジェクトから1つの注視するオブジェクトをチェックする
        /// </summary>
        private void VisionObjectPickUp(List<VisionObject> a_VisionObjectList)
        {
            // 現在の注視オブジェクト
            VisionObject targetVisionObject = null;

            foreach (VisionObject visionObject in a_VisionObjectList)
            {
                // 興味がないオブジェクトはチェックしない
                if(!VisionInterest(visionObject))
                {
                    continue;
                }

                // 優先度の高いものを注視オブジェクトとして取得する
                targetVisionObject = VisionPriority(visionObject, targetVisionObject);
            }

            // 取得した注視オブジェクトを設定する
            ReceptionVision(TranslateVisionObject(targetVisionObject));
        }

        /// <summary>
        /// 対象のオブジェクトが興味のあるオブジェクトか判定する
        /// </summary>
        /// <returns></returns>
        private bool VisionInterest(VisionObject a_VisionObject)
        {
            bool isInterest = false;

            // 発見してから時間が経過したものには興味を失う
            int currentFrameCount = Time.frameCount;

            // 発見からの経過フレーム数を取得する
            int progressFrameCount = currentFrameCount - a_VisionObject.FirstFrameCount;

            // 興味を失ったフレームの判定を行う
            if (progressFrameCount > p_LostInterestFrameInterval)
            {
                isInterest = false;
            }
            else
            {
                isInterest = true;
            }

            return isInterest;
        }

        /// <summary>
        /// 注視の優先ロジック
        /// </summary>
        private VisionObject VisionPriority(VisionObject a_Alpha, VisionObject a_Beta)
        {
            VisionObject result = a_Alpha ?? a_Beta;
            if (a_Alpha == null || a_Beta == null)
            {
                return result;
            }

            // 最優先注目オブジェクト名に一致すればそちらを優先する
            if (a_Alpha.ObjectName == p_AttentionObjectName)
            {
                return a_Alpha;
            }
            if (a_Beta.ObjectName == p_AttentionObjectName)
            {
                return a_Beta;
            }

            // 認識種別の優先度が異なるなら高い方を優先する
            if (p_StaticPriorityList[(int)a_Alpha.UnderstandType] != p_StaticPriorityList[(int)a_Beta.UnderstandType])
            {
                // 優先度が高い方を優先する
                if (p_StaticPriorityList[(int)a_Alpha.UnderstandType] > p_StaticPriorityList[(int)a_Beta.UnderstandType])
                {
                    return a_Alpha;
                }
                else
                {
                    return a_Beta;
                }
            }

            // 近い方を優先する
            if (a_Alpha.Distance != a_Beta.Distance)
            {
                if (a_Alpha.Distance > a_Beta.Distance)
                {
                    return a_Beta;
                }
                else
                {
                    return a_Alpha;
                }
            }
            return result;
        }


        /// <summary>
        /// 視界オブジェクト情報から注視情報を作成する
        /// </summary>
        /// <param name="a_VisionObject"></param>
        /// <returns></returns>
        private HoloMonFieldOfVisionObject TranslateVisionObject(VisionObject a_VisionObject)
        {
            if (a_VisionObject == null)
            {
                // 空変数の場合は視界オブジェクト無しを作成する
                return new HoloMonFieldOfVisionObject(false, "", null, ObjectUnderstandType.Nothing, ObjectStatusType.Nothing);
            }

            return new HoloMonFieldOfVisionObject(true, a_VisionObject.ObjectName,
                a_VisionObject.GameObject, a_VisionObject.UnderstandType, a_VisionObject.StatusType);
        }
    }
}

f:id:bluebirdofoz:20210430235521j:plain

作成したオブジェクトにスクリプトを追加します。
Inspector ビューで各オブジェクト種別とその優先度を設定します。
f:id:bluebirdofoz:20210430235534j:plain

シーンを再生して動作を確認します。
ホロモンの目の前に人と注目物が同時に現れた時、優先度の高い注目オブジェクトを見るようになりました。
f:id:bluebirdofoz:20210430235548j:plain