本日は HoloLens2 の小ネタ枠です。
SampleQRCodesプロジェクト内の処理を確認してQRコード追従の仕組みを理解します。
今回はQRコードの検出と更新イベントを使って視覚化する処理を確認します。
前提条件
前回記事の続きです。
bluebirdofoz.hatenablog.com
スクリプトの確認
今回はシーンにアタッチされているスクリプトとその関連スクリプトを確認して、QRコードの検出と更新イベントを理解します。
・Singleton
・QRCodeManager
・QRCodesVisualizer
なお、SampleQRCodes プロジェクトでは以下のスクリプトは未使用です。
・QRCodesSetup
スクリプトの役割と各コードの処理や参照ドキュメントをコメントとして追記しました。
Singleton.cs
シングルトン作成用のクラスです。
以下の通り、本クラスを継承することでシングルトンを作成できます。
public class MyClassName : Singleton<MyClassName> {}
using UnityEngine; namespace QRTracking { /// <summary> /// Inherit from this base class to create a singleton. /// e.g. public class MyClassName : Singleton<MyClassName> {} /// 本クラスを継承することでシングルトンを作成できる /// 例. public class MyClassName : Singleton<MyClassName> {} /// </summary> public class Singleton<T> : MonoBehaviour where T : MonoBehaviour { // アプリ終了の確認用フラグ private static bool m_ShuttingDown = false; // ロック用オブジェクト private static object m_Lock = new object(); // 生成した単一インスタンス private static T m_Instance; /// <summary> /// Access singleton instance through this propriety. /// 本プロパティを介してシングルトンインスタンスにアクセスする /// </summary> public static T Instance { get { // 終了フラグのチェック if (m_ShuttingDown) { // 終了フラグがONの場合 // インスタンスは返さない Debug.LogWarning("[Singleton] Instance '" + typeof(T) + "' already destroyed. Returning null."); return null; } // 多重処理が走らないようロックする lock (m_Lock) { // 単一インスタンスを生成済みか否か if (m_Instance == null) { // Search for existing instance. // 既存のインスタンスが生成済みか検索する m_Instance = (T)FindObjectOfType(typeof(T)); // Create new instance if one doesn't already exist. // インスタンスがまだ存在しない場合は新しいインスタンスを作成する if (m_Instance == null) { // Need to create a new GameObject to attach the singleton to. // シングルトンをアタッチする新しいGameObjectを作成する var singletonObject = new GameObject(); m_Instance = singletonObject.AddComponent<T>(); singletonObject.name = typeof(T).ToString() + " (Singleton)"; // Make instance persistent. // インスタンスを永続化する // https://docs.unity3d.com/ja/current/ScriptReference/Object.DontDestroyOnLoad.html DontDestroyOnLoad(singletonObject); } } // 単一インスタンスを返す return m_Instance; } } } /// <summary> /// アプリケーションの終了イベント /// </summary> private void OnApplicationQuit() { m_ShuttingDown = true; } /// <summary> /// 破棄イベント /// </summary> private void OnDestroy() { m_ShuttingDown = true; } } }
QRCodeManager.cs
QRコード監視の開始と停止を行います。
QRコードの検出、更新、ロスト時に登録されたイベントハンドラーを実行します。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using Microsoft.MixedReality.QR; namespace QRTracking { /// <summary> /// イベントハンドラーに渡すクラスを生成する /// </summary> public static class QRCodeEventArgs { public static QRCodeEventArgs<TData> Create<TData>(TData data) { return new QRCodeEventArgs<TData>(data); } } /// <summary> /// データを受け取るイベントハンドラーに渡す引数クラス /// </summary> /// <typeparam name="TData"></typeparam> [Serializable] public class QRCodeEventArgs<TData> : EventArgs { public TData Data { get; private set; } public QRCodeEventArgs(TData data) { Data = data; } } /// <summary> /// QRコードマネージャ /// </summary> public class QRCodesManager : Singleton<QRCodesManager> { // QRコードスキャナーを自動的に起動するかどうか [Tooltip("Determines if the QR codes scanner should be automatically started.")] public bool AutoStartQRTracking = true; // トラッキングの実行中フラグ public bool IsTrackerRunning { get; private set; } // QRコードのサポート有無フラグ public bool IsSupported { get; private set; } // QRコードの各種イベント発生時に呼び出されるイベントハンドラー public event EventHandler<bool> QRCodesTrackingStateChanged; public event EventHandler<QRCodeEventArgs<Microsoft.MixedReality.QR.QRCode>> QRCodeAdded; public event EventHandler<QRCodeEventArgs<Microsoft.MixedReality.QR.QRCode>> QRCodeUpdated; public event EventHandler<QRCodeEventArgs<Microsoft.MixedReality.QR.QRCode>> QRCodeRemoved; // 検出中のQRコード情報リスト(ID, QRコード情報) private System.Collections.Generic.SortedDictionary<System.Guid, Microsoft.MixedReality.QR.QRCode> qrCodesList = new SortedDictionary<System.Guid, Microsoft.MixedReality.QR.QRCode>(); // QRコードウォッチャの参照 private QRCodeWatcher qrTracker; // アクセス許可の確認完了フラグ private bool capabilityInitialized = false; // ユーザーのQRコードアクセスに関する同意状態 private QRCodeWatcherAccessStatus accessStatus; // QRコードアクセスに関するユーザーの同意要求タスク private System.Threading.Tasks.Task<QRCodeWatcherAccessStatus> capabilityTask; /// <summary> /// 指定QRコードのIDを取得する /// </summary> /// <param name="qrCodeData"></param> /// <returns></returns> public System.Guid GetIdForQRCode(string qrCodeData) { lock (qrCodesList) { foreach (var ite in qrCodesList) { if (ite.Value.Data == qrCodeData) { return ite.Key; } } } return new System.Guid(); } /// <summary> /// 全てのQRコード情報をリスト形式で返す /// </summary> /// <returns></returns> public System.Collections.Generic.IList<Microsoft.MixedReality.QR.QRCode> GetList() { lock (qrCodesList) { return new List<Microsoft.MixedReality.QR.QRCode>(qrCodesList.Values); } } protected void Awake() { } /// <summary> /// 起動処理 /// </summary> async protected virtual void Start() { // QRコード監視のサポートの有無を取得する IsSupported = QRCodeWatcher.IsSupported(); // QRコード監視のユーザ許可を確認する capabilityTask = QRCodeWatcher.RequestAccessAsync(); accessStatus = await capabilityTask; capabilityInitialized = true; } /// <summary> /// QRコード監視のセットアップ /// </summary> private void SetupQRTracking() { try { // QRコード監視の参照を取得する qrTracker = new QRCodeWatcher(); IsTrackerRunning = false; // QRコード監視の各種イベント(検出,更新,ロスト,列挙完了)に関数を登録する qrTracker.Added += QRCodeWatcher_Added; qrTracker.Updated += QRCodeWatcher_Updated; qrTracker.Removed += QRCodeWatcher_Removed; qrTracker.EnumerationCompleted += QRCodeWatcher_EnumerationCompleted; } catch (Exception ex) { Debug.Log("QRCodesManager : exception starting the tracker " + ex.ToString()); } // 自動起動が有効か否か if (AutoStartQRTracking) { // 自動起動が有効な場合 // QRコード監視の開始処理を行う StartQRTracking(); } } /// <summary> /// QRコード監視の開始処理 /// </summary> public void StartQRTracking() { // QRコード監視の参照が取得できているか // 既に監視が開始済みか否か if (qrTracker != null && !IsTrackerRunning) { Debug.Log("QRCodesManager starting QRCodeWatcher"); try { // QRコード監視の開始を要求する qrTracker.Start(); IsTrackerRunning = true; // QRコード監視の状態変化時イベントハンドラーを実行する QRCodesTrackingStateChanged?.Invoke(this, true); } catch(Exception ex) { Debug.Log("QRCodesManager starting QRCodeWatcher Exception:" + ex.ToString()); } } } /// <summary> /// QRコード監視の停止処理 /// </summary> public void StopQRTracking() { // 監視が開始済みか否か if (IsTrackerRunning) { IsTrackerRunning = false; if (qrTracker != null) { // QRコード監視の停止を要求する qrTracker.Stop(); // QRコードの保持情報をクリアする qrCodesList.Clear(); } // QRコード監視の状態変化時イベントハンドラーを実行する var handlers = QRCodesTrackingStateChanged; if (handlers != null) { handlers(this, false); } } } /// <summary> /// QRコードのロストを検知した時の実行関数 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void QRCodeWatcher_Removed(object sender, QRCodeRemovedEventArgs args) { Debug.Log("QRCodesManager QRCodeWatcher_Removed"); bool found = false; lock (qrCodesList) { // QRコード情報リストからロストしたQRコードの情報を削除する if (qrCodesList.ContainsKey(args.Code.Id)) { qrCodesList.Remove(args.Code.Id); found = true; } } if (found) { // QRコードの削除処理が正常に行われた場合 // QRコードのロスト時イベントハンドラーを実行する var handlers = QRCodeRemoved; if (handlers != null) { handlers(this, QRCodeEventArgs.Create(args.Code)); } } } /// <summary> /// QRコードの更新を検知した時の実行関数 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void QRCodeWatcher_Updated(object sender, QRCodeUpdatedEventArgs args) { Debug.Log("QRCodesManager QRCodeWatcher_Updated"); bool found = false; lock (qrCodesList) { // QRコード情報リストから更新されたQRコードの情報を更新する if (qrCodesList.ContainsKey(args.Code.Id)) { found = true; qrCodesList[args.Code.Id] = args.Code; } } if (found) { // QRコードの更新処理が正常に行われた場合 // QRコードの更新時イベントハンドラーを実行する var handlers = QRCodeUpdated; if (handlers != null) { handlers(this, QRCodeEventArgs.Create(args.Code)); } } } /// <summary> /// QRコードの検出を検知した時の実行関数 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void QRCodeWatcher_Added(object sender, QRCodeAddedEventArgs args) { Debug.Log("QRCodesManager QRCodeWatcher_Added"); lock (qrCodesList) { // QRコード情報リストに検出したQRコードの情報を追加する qrCodesList[args.Code.Id] = args.Code; } // QRコードの検出時イベントハンドラーを実行する var handlers = QRCodeAdded; if (handlers != null) { handlers(this, QRCodeEventArgs.Create(args.Code)); } } /// <summary> /// QRコードの列挙完了を検知した時の実行関数 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void QRCodeWatcher_EnumerationCompleted(object sender, object e) { Debug.Log("QRCodesManager QrTracker_EnumerationCompleted"); } /// <summary> /// 定期処理 /// </summary> private void Update() { // QRコード監視の参照が取得できているか // 初期化処理を一旦完了しているか // QRコード監視がサポートされているか if (qrTracker == null && capabilityInitialized && IsSupported) { if (accessStatus == QRCodeWatcherAccessStatus.Allowed) { // ユーザからQRコードアクセスの許可が行われた場合 // QRコード監視のセットアップを行う SetupQRTracking(); } else { Debug.Log("Capability access status : " + accessStatus); } } } } }
QRCodesVisualizer.cs
QRコードの検出状態を視覚化して見た目に分かるようにします。
QRコード監視には QRCodeManager を利用します。
using System.Collections; using System.Collections.Generic; using UnityEngine; using Microsoft.MixedReality.QR; namespace QRTracking { /// <summary> /// QRコードの検出領域を視覚化する /// </summary> public class QRCodesVisualizer : MonoBehaviour { /// <summary> /// 視覚化用プレハブ /// </summary> public GameObject qrCodePrefab; /// <summary> /// QRコード視覚化オブジェクトリスト(ID,オブジェクト参照) /// </summary> private System.Collections.Generic.SortedDictionary<System.Guid, GameObject> qrCodesObjectsList; /// <summary> /// クリア実行フラグ /// </summary> private bool clearExisting = false; /// <summary> /// アクション情報 /// </summary> struct ActionData { /// <summary> /// アクション種別 /// </summary> public enum Type { /// <summary> /// 検出 /// </summary> Added, /// <summary> /// 更新 /// </summary> Updated, /// <summary> /// ロスト /// </summary> Removed }; /// <summary> /// アクション種別 /// </summary> public Type type; /// <summary> /// QRコード情報 /// </summary> public Microsoft.MixedReality.QR.QRCode qrCode; public ActionData(Type type, Microsoft.MixedReality.QR.QRCode qRCode) : this() { this.type = type; qrCode = qRCode; } } /// <summary> /// 処理待ちアクションキュー /// </summary> private System.Collections.Generic.Queue<ActionData> pendingActions = new Queue<ActionData>(); void Awake() { } /// <summary> /// 起動処理 /// </summary> void Start() { Debug.Log("QRCodesVisualizer start"); // 変数の初期化 qrCodesObjectsList = new SortedDictionary<System.Guid, GameObject>(); // QRコードマネージャのイベントハンドラー(トラッキング状態変化,検出,更新,ロスト)に実行関数を登録する QRCodesManager.Instance.QRCodesTrackingStateChanged += Instance_QRCodesTrackingStateChanged; QRCodesManager.Instance.QRCodeAdded += Instance_QRCodeAdded; QRCodesManager.Instance.QRCodeUpdated += Instance_QRCodeUpdated; QRCodesManager.Instance.QRCodeRemoved += Instance_QRCodeRemoved; if (qrCodePrefab == null) { throw new System.Exception("Prefab not assigned"); } } /// <summary> /// トラッキング状態変化時の実行関数 /// </summary> /// <param name="sender"></param> /// <param name="status"></param> private void Instance_QRCodesTrackingStateChanged(object sender, bool status) { if (!status) { // トラッキングが無効化された場合 // クリア実行フラグをONにする clearExisting = true; } } /// <summary> /// QRコード検出時の実行関数 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Instance_QRCodeAdded(object sender, QRCodeEventArgs<Microsoft.MixedReality.QR.QRCode> e) { Debug.Log("QRCodesVisualizer Instance_QRCodeAdded"); lock (pendingActions) { // キューに検出イベントを登録する pendingActions.Enqueue(new ActionData(ActionData.Type.Added, e.Data)); } } /// <summary> /// QRコード更新時の実行関数 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Instance_QRCodeUpdated(object sender, QRCodeEventArgs<Microsoft.MixedReality.QR.QRCode> e) { Debug.Log("QRCodesVisualizer Instance_QRCodeUpdated"); lock (pendingActions) { // キューに更新イベントを登録する pendingActions.Enqueue(new ActionData(ActionData.Type.Updated, e.Data)); } } /// <summary> /// QRコードロスト時の実行関数 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Instance_QRCodeRemoved(object sender, QRCodeEventArgs<Microsoft.MixedReality.QR.QRCode> e) { Debug.Log("QRCodesVisualizer Instance_QRCodeRemoved"); lock (pendingActions) { // キューにロストイベントを登録する pendingActions.Enqueue(new ActionData(ActionData.Type.Removed, e.Data)); } } /// <summary> /// キューのイベント処理を実行する /// </summary> private void HandleEvents() { lock (pendingActions) { // キューのアクションを全て実行するまで繰り返し while (pendingActions.Count > 0) { // キューからアクションを取り出し var action = pendingActions.Dequeue(); if (action.type == ActionData.Type.Added) { // QRコード検出アクションの場合 // 視覚化用プレハブをスポーンする GameObject qrCodeObject = Instantiate(qrCodePrefab, new Vector3(0, 0, 0), Quaternion.identity); // スポーンオブジェクトの SpatialGraphCoordinateSystem コンポーネントにQRコードのノードIDを設定する qrCodeObject.GetComponent<SpatialGraphCoordinateSystem>().Id = action.qrCode.SpatialGraphNodeId; // スポーンオブジェクトの QRCode コンポーネントにQRコード情報を設定する qrCodeObject.GetComponent<QRCode>().qrCode = action.qrCode; // QRコード視覚化オブジェクトリストにIDとオブジェクト参照を追加する qrCodesObjectsList.Add(action.qrCode.Id, qrCodeObject); } else if (action.type == ActionData.Type.Updated) { // QRコード更新アクションの場合 if (!qrCodesObjectsList.ContainsKey(action.qrCode.Id)) { // QRコード視覚化オブジェクトリストに含まれていない場合のみ、視覚化の処理を行う // 視覚化用プレハブをスポーンする GameObject qrCodeObject = Instantiate(qrCodePrefab, new Vector3(0, 0, 0), Quaternion.identity); // スポーンオブジェクトの SpatialGraphCoordinateSystem コンポーネントにQRコードのノードIDを設定する qrCodeObject.GetComponent<SpatialGraphCoordinateSystem>().Id = action.qrCode.Id; // スポーンオブジェクトの QRCode コンポーネントにQRコード情報を設定する qrCodeObject.GetComponent<QRCode>().qrCode = action.qrCode; // QRコード視覚化オブジェクトリストにIDとオブジェクト参照を追加する qrCodesObjectsList.Add(action.qrCode.Id, qrCodeObject); } } else if (action.type == ActionData.Type.Removed) { // QRコードロストアクションの場合 if (qrCodesObjectsList.ContainsKey(action.qrCode.Id)) { // QRコード視覚化オブジェクトリストに含まれる場合のみ、削除の処理を行う // オブジェクトを破棄する Destroy(qrCodesObjectsList[action.qrCode.Id]); // QRコード視覚化オブジェクトリストから情報を削除する qrCodesObjectsList.Remove(action.qrCode.Id); } } } } if (clearExisting) { // クリア実行フラグが有効な場合 clearExisting = false; // 全ての視覚化オブジェクトを破棄する foreach (var obj in qrCodesObjectsList) { Destroy(obj.Value); } // QRコード視覚化オブジェクトリストをクリアする qrCodesObjectsList.Clear(); } } /// <summary> /// 定期処理 /// </summary> void Update() { // キューのイベント処理を実行する HandleEvents(); } } }
QRCodesSetup.cs
QRコードマネージャと可視化の設定を自動で行います。
SampleQRCodes プロジェクトではシーンに予めマネージャと可視化の設定が行われており、本スクリプトは未使用です。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using Microsoft.MixedReality.QR; namespace QRTracking { public class QRCodesSetup : MonoBehaviour { // QRコードスキャナーを自動的に起動するかどうか [Tooltip("Determines if the QR codes scanner should be automatically started.")] public bool AutoStartQRTracking = true; // 検出されたQRコードを3D空間で視覚化するかどうか [Tooltip("Visualize the detected QRCodes in the 3d space.")] public bool VisualizeQRCodes = true; // QRコードマネージャの参照 QRCodesManager qrCodesManager = null; /// <summary> /// 起動処理 /// </summary> void Awake() { // 参照を取得する qrCodesManager = QRCodesManager.Instance; // QRコードスキャナーを自動的に起動するかどうか if (AutoStartQRTracking) { // トラッキングを開始する qrCodesManager.StartQRTracking(); } // 検出されたQRコードを3D空間で視覚化するかどうか if (VisualizeQRCodes) { // QRコードの視覚化スクリプトを設定する gameObject.AddComponent(typeof(QRTracking.QRCodesVisualizer)); } } void Start() { } void Update() { } } }