こんにちは Iwaken Lab.のきょうすけです!
普段はワイクウーデザインにてXRエンジニアやってます
本記事は、Iwaken Lab.アドベントカレンダー2023の5日目の記事です
昨日のサックーさんの記事をご覧になってない方はそちらもぜひ
概要
「HoloLens 2のローカルストレージにCSVファイルを作成して書き込んで保存する」という、一見大したことなさそうな処理が一癖二癖あったのでその話をしようと思います
具体的には以下の話
- Dispose()/Close()
- UWPのライフサイクル
環境
目標
この記事では以下を達成することを目標とします
- 視線をカーソルとして可視化する
- 視線の座標をCSVに書き込む
- CSVをPCに保存する
そこで今回は以下のシチュエーションで上記目標を実装します
- シーン上にボタンが配置されている
- ボタンは視線が合った状態でピンチするとクリックできる
- クリックしたときの視線の座標とタイムスタンプをCSVに書き込む
- 保存されたCSVをPCに保存する
MRアプリケーション開発のセットアップ手順は公式トレーニングでも参考にしてください
ただし、2023年11月段階ではMRTK3だけはインポートしない方がいいです
実装例
アイトラッキングの有効化
今回は深く考えずデモシーンのプロファイルを使います
デモシーンの導入
Mixed Reality Feature Toolを使用し、Mixed Reality Toolkit Examplesをインポートします
その後、Package ManagerからアイトラッキングのデモシーンDemo - EyeTracking
をインポートします
プロファイルの設定
HierarchyのMixedRealityToolkit
を選択し、EyeTrackingDemoConfigrationProfile
を選択します
デモシーンでは視線オブジェクトが透明なものになっているので別のPrefabを設定します
input
タブ内のPointers > Gaze Settings > Gaze Cursor Prefab
をDefaultGazeCursor
に変更します
ボタンの配置
MRTK付属のボタンをお好みで選んで適当にシーンに配置してください
今回はPressableButtonHoloLens2
を使います
スクリプト
CSVに取得した視線情報を書き込む処理を記述しています
using UnityEngine;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.MixedReality.Toolkit;
public class WriteCsv : MonoBehaviour
{
private StreamWriter _streamWriter;
// ストリームを開く
private void Start()
{
var address = Application.persistentDataPath + "/test.csv";
Debug.Log(address);
// ファイルが存在していなければ作成する
if (!File.Exists(address))
{
_streamWriter = File.CreateText(address);
_streamWriter.Flush();
_streamWriter.Dispose();
}
_streamWriter = new StreamWriter(new FileStream(address, FileMode.Truncate, FileAccess.Write), Encoding.UTF8);
string[] s1 = { "タイムスタンプ", "パネルX座標", "パネルY座標" };
var s2 = string.Join(",", s1);
_streamWriter.WriteLine(s2);
}
// ボタンを押したときにタイムスタンプと座標を書き込む
public void Write()
{
var timestamp = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
var touchX = CoreServices.InputSystem.EyeGazeProvider.HitPosition.x;
var touchY = CoreServices.InputSystem.EyeGazeProvider.HitPosition.y;
var s2 = string.Join(",", timestamp, touchX.ToString(CultureInfo.CurrentCulture), touchY.ToString(CultureInfo.CurrentCulture));
_streamWriter.WriteLine(s2);
}
// 終了処理
// ストリームを閉じる
#if WINDOWS_UWP
private void OnApplicationFocus(bool focus)
{
if (focus) return;
_streamWriter.Flush();
_streamWriter.Dispose();
Windows.ApplicationModel.Core.CoreApplication.Exit();
}
#else
private void OnApplicationQuit()
{
_streamWriter.Flush();
_streamWriter.Dispose();
}
#endif
}
保存先
保存先はApplication.persistentDataPath + "/test.csv"
となっていますが、具体的にはデバイスポータルのSystem\File explorer\User Folder\LocalAppDat\${カンパニー名}\${アプリ名}\LocalState
以下に保存されます
ストリームを閉じる処理
AndroidなどでCSVの書き込み処理をしたことある人は以下のようなコードを書いたことがあると思います
private void Write()
{
_streamWriter.WriteLine(str);
_streamWriter.Close();
}
この例ではストリームを閉じるときClose()
を使っています
ですが、このClose()
は.NETチームにとって忌み子なようで、UWPでは適応範囲外になっています(画像元:リファレンス)
そのため、HoloLensではDispose()
を使います
DisposeやCloseについての話は以下の記事をご覧ください
ストリームを閉じるタイミング
今回はHoloLensのメニューからホームボタンを押したときにやります
ホームボタンを押すと白いウィンドウが表示され、アプリが終了したように見えるのでOnApplicationQuit()
が呼び出されているような気もしますが、実は呼ばれません
と、言うより実は終了していません
まずHoloLensで動作するUnityアプリはUWPアプリに乗っかって動作します
そして、ホームボタンを押すとUWPのライフサイクルで言うところのSuspendedになります
この状態から閉じるボタンを押してアプリを終了させてもOnApplicationQuit()
は呼ばれません
そこでHoloLensでの終了処理はプレイヤーフォーカスの動向を検知するOnApplicationFocus(bool)
を使用します
HoloLensで動作するUnityアプリは、ホームボタンを押すとフォーカスが失われ、OnApplicationFocus(False)
が呼び出されます
これを利用してストリームの閉じる処理を行います
// 終了処理
// ストリームを閉じる
#if WINDOWS_UWP
private void OnApplicationFocus(bool focus)
{
if (focus) return;
_streamWriter.Flush();
_streamWriter.Dispose();
Windows.ApplicationModel.Core.CoreApplication.Exit();
}
#else
private void OnApplicationQuit()
{
_streamWriter.Flush();
_streamWriter.Dispose();
}
#endif
今回はストリームを閉じるついでにWindows.ApplicationModel.Core.CoreApplication.Exit()
を呼び出し、UWPアプリごと終了させるようにしています
まとめ
UWPは癖が強く、ちょっとUnity触ったことある程度なら余裕で躓きそうですね