Hiromuブログ

最近はこちら(https://zenn.dev/hiromu)が本体

GitHub 1年チャレンジ

ポエムというかただのメモです(せっかくなのでリンクはアフェリエイトになっているので、踏みたくない方はご注意ください)。

2020年4月から、毎日(最低)25分、業務とはあまり関係ないプログラミングをして、GitHubにプッシュするという個人的なチャレンジをしていました。ちょうど1年たって穴をあけることなく目標達成したのでメモとして残しておきます。

f:id:HiromuKato:20210405002459p:plain

なお、ほとんどが private リポジトリで写経などを行った物を上げているだけのものだったりという感じでとくに公開しているものではないです(今後は公開できる何かに注力するのも良いかもしれないとは考えています)。

ちなみに、なぜ 25 分かというと、以前読んだポモドーロテクニック入門という本にかかれていた25分という時間が元になっており、25 分であれば無理なく集中できるという個人的な感触によるものです。PDCAを回して改善とかそういった大層なことはしていません。

あとは、大体の時系列のメモです。

2020/4

C#プログラミングのイディオム/定石&パターン

Unityだけでなくピュアな C# をもうちょっとしっかり基礎を固めておこうと思ってはじめたものです。内容としてタイトルの通りイディオムやパターンととして使えそうなコードサンプルが豊富にありとても良い本でした。

2020/5

Unity2017最適化ガイド

普段からUnityを利用するのであれば読んでおいて間違いのない本です。

2020/6

作れる!学べる!Unreal Engine 4 ゲーム開発入門 第2版

エンジンのバージョンも割と新しく、入門として良い本だと思います。

C++でつくるUNREAL ENGINEアプリ開発

UE C++ について書かれた本は数少ないので貴重な一冊です。

2020/7

C#によるWindowsシステムプログラミング

読んだのは第一版ですが、他の本ではあまり書かれていないようなマネージドとアンマネージドの章があり参考になりました。並列処理についても詳しく書かれています。GUIについてはWindows Forms で書かれているので少し古いかなという気はします。

UnrealEngine4アクションゲーム ブループリント入門

すべて実装してみましたが、やりたいことを目次から探して逆引きリファレンス本として活用すると良さそうな本です。

GitHub Actions 実践入門

GitHub Actions はまだあまり使いこなせていませんが、動かしてみてどのようなものかを理解だけしました。

Unity ゲームエフェクトマスターガイド

Unity におけるエフェクト解説本も数が限られていると思うので、興味がある方にとっては参考になる本だと思います。パラメータのどのあたりを触ればいいのかといった勘所がなんとなく身についたような気はします。

OpenCV4 基本プログラミング

いつか手を出してみようと思ったままずっとスタックされたままの OpenCV に手を出してみました。この本はC++で説明されていますが、Pythonの本などもあるので興味に合わせて選択するのが良いと思います。画像処理や動画処理は純粋にやってて楽しいです。

2020/8

WEB+DB PRESS vol.117 Typescript

このころになぜか急に Typescript に興味が沸いたため買ってみたものです。1冊の厚い本と違ってコンパクトにエッセンスが把握できてよかったです。

2020/9

JavaScript徹底攻略 非同期処理

この著者の方の JavaScript の本は良かったのですべて買いました。代表的なものとしてこの2冊だけ載せておきます。JavaScript の前提知識がない場合は Typescript を始めるならまず初めにこちらを呼んでおくのが良いと思います。

booth.pm

booth.pm

Node.js デザインパターン

JavaScript, TypeScript に興味を持ち始めたのでその勢いのまま Node.js にも手を出してみました。知識がまだ十分に追いついておらずもう一度読み直そうと思っていますが、これも買ってよかったと思える1冊です。

JavaScript グラフィックスプログラミング入門

丁寧にかかれているので JavaScript の勉強としても良い本だと思います。Typescript に書き換えながら実装していました(たまにany使ったりしながら...)。

2020/10

いまどきのJSプログラマーのための Node.jsとReactアプリケーション開発テクニック

少し古めの本なので、積極的にお勧めはしずらいですが、そのあたりをカバーできる程度の基礎知識が身についているのであれば、参考になる内容も多いと思います。

りあクトⅠ.React言語・環境編

りあクトⅡ.React基礎編

りあクトⅢ.React応用編

りあクト TypeScriptで極める現場のReact開発

React に関してはこれらの本が個人的には一番お勧めです。

oukayuka.booth.pm

2020/11

オンラインゲームの仕組み

説明するまでもないほど定番かもしれませんがお勧めです。

UniRx/UniTask完全理解

こちらも 最近ではよく使われるライブラリだと思うので、末永く Unity と向き合っていく場合は読んでおくべき本だと思います。

2020/12

Head Firstデザインパターン

GoF本も有名ですが、デザインパターンについて学びたい場合はこちらも内容としてはわかりやすくお勧めの本です

Rustプログラミング入門

最近Rustの人気が高まり始めたように感じるのでとりあえず手を出してみたという感じです(が、個人的には積極的に手を出す理由も今のところないので Rust はもうちょっと様子見しようかなと思っています)。

実践Vim

プラグインの使い方とかではなく、純粋にVim力を上げるためのガチの本です。

2021/01

Create game with Unreal C++

これも Unreal C++ について書かれた貴重な本です。順番に進めることで、C++で簡単なシューティングゲームのようなものが作れるようになります。

booth.pm

Azure Functions入門

サーバレスの技術は使いこなせたら便利だろうと思ったので、手を出してみました。Azure Functionsに関する本としてここまでのボリュームでまとめられた情報は他にないと思います。進化の早い領域なので情報が陳腐化していないかは気になるところだと思いますが、今年に入ってから読み始めましたが、特に情報が古くて使えないといったことはありませんでした。

React Native

M1 Mac で React Native をやってみたかったので買ってみたものです。環境構築の基本的な流れは本の通りだと思いますが、M1 Mac が出てから日も浅かったこともあり環境構築も一苦労で、Hello World ができたあたりで力尽きました。またそのうち触りたいと思っています。

2021/02

ゼロから作るDeep Learning 3

全60ステップあり、1日1ステップぐらいな感じで進めました。だいたい1ステップごとにアウトプットが確認できるような作りになっているので、楽しみながら取り組めます。また Python の使い方(__call__など特別なメソッドの説明やnumpyに関する補足、テスト、モジュール化の方法など)についても適宜説明があるので、Pythonの知識も身に付き一石二鳥です。

あとは目を通しただけの本などをメモとして、、、

  • レガシーコード改善ガイド

  • HoudiniとUnreal Engine 4で学ぶリアルタイムVFX

  • Power Apps ローコードで作成するビジネスアプリ入門

  • Effective C#6.0/7.0

  • More Effective C#6.0/7.0

  • InDesign クリエイター養成講座

  • プログラミングTypescript

  • オブジェクト指向UIデザイン

  • ドメイン駆動設計入門

  • React.js & NEXT.js 超入門

  • Clean Architecture

  • SwiftUI徹底入門

  • OneNote全事典

  • Visual Studio Code 実践ガイド

  • Blazor入門

  • エッセンシャルWPF

Epic MegaJam におけるライブストリーム

12/4に開催された Epic MegaJam におけるライブストリームで マイクロソフト社員が Mixed Reality に関する説明をする内容のものがありました。とても参考になるものであったので紹介いたします。

動画は以下でアーカイブが公開されています。

2020 Epic MegaJam Kickoff | Inside Unreal

内容としては以下の5つあります(括弧内は開始時間)。

  • Azure Kinect や HoloLens 2 の紹介 (00:35:17-)
  • Unreal Engine + Mixed Reality, UX Tools概要 (00:41:42-)
  • HoloLens 2 アプリの作成方法 + UX Tools (01:21:54-)
  • UX Tools を利用したハンドメニューの作成方法 (01:48:30-)
  • MR アプリにおけるベストプラクティス (02:12:22-)

Azure Kinect / HoloLens 2 の紹介

製品紹介の動画が流されていました。興味のある方はすでに見たことがあるかもしれませんが、こちらは動画を見ていただくのが一番分かりやすいと思います。

f:id:HiromuKato:20201205145702p:plain

Unreal Engine + Mixed Reality, UX Tools概要

以下のような内容について説明されていました。

  • Unreal Engine への関わりや ストリーミングによる PC-quality visuals(Mission AR) に関する説明
  • Unreal Engine 4.26 における Mixed Reality のハイライト
  • UX Tools 概要
  • オープンソースの HoloLens 2 ゲーム紹介(HoloPipes, Kippy's Escape)

f:id:HiromuKato:20201205145731p:plain f:id:HiromuKato:20201205145746p:plain f:id:HiromuKato:20201205145755p:plain f:id:HiromuKato:20201205145817p:plain

HoloLens 2 アプリの作成方法 + UX Tools

プロジェクトの初期設定から始まり、UX Tools のインポート方法やキューブをハンド操作できるものをストリーミングで行うところまで説明されています。

f:id:HiromuKato:20201205145837p:plain f:id:HiromuKato:20201205145851p:plain

この内容とほぼ同様のことができる状態のものをテンプレートとして用意しているので、興味があればお試しください。

www.hiromukato.com

またこちらの動画でもほぼ同じ内容を「日本語で」説明しています。


MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER

UXTools を利用したハンドメニューの作成方法

UX Tools を利用してハンドメニューを実装する方法が1から説明されておりとても参考になります。 また実機へのデプロイ方法も最後に説明されています。

f:id:HiromuKato:20201205150215p:plain f:id:HiromuKato:20201205150229g:plain

MR アプリにおけるベストプラクティス

パフォーマンスを考慮して以下の内容が説明されています。 とても参考になる内容のため、別途ブログにでも書きたいと思います。

  • Project Settings
  • Debugging/Profiling
  • Tips

f:id:HiromuKato:20201205150312p:plain f:id:HiromuKato:20201205150327p:plain


以上、現時点において UX Tools の使い方やパフォーマンスに関する内容はとても貴重なものであると思いますので興味のある方はご覧になられると良いと思います。

UE4 HoloLens Template

UE4 で HoloLes 用のプロジェクトを作成する場合、最初にプラグインを追加したりといくつか設定が必要になります。毎回これを行うのは大変なのでテンプレート化し、プロジェクト作成時に選択できるようにする方法をまとめてみました。

f:id:HiromuKato:20201122182026p:plain

テンプレートとなるプロジェクト作成

まず最初に、テンプレートにするためのプロジェクトを作成します。

プロジェクト名については、すでに TP_HoloLensBP というテンプレートが存在していたので、自分が作成した物であることが分かる名称にするのが良いと思いますが、ここでは TP_HoloLens という名称にしたものとします。

具体的なプロジェクトの内容として以下の設定を行っておくのが良いと思います。

  • Plugins 設定で HoloLens / Microsoft Windows Mixed Reality を有効化
  • ARSessionConfig の作成、設定
  • MRPawn の作成、設定
  • MRGameMode の作成、設定
  • UX Tools プラグインの追加、有効化
  • MRPawn にハンドインタラクション設定を追加

これらの具体的な設定方法については以下をご参照ください。


MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER

テンプレート化

  • C:\Program Files\Epic Games\UE_[Version]\Templates 配下に先ほど作成したプロジェクトを格納します

  • 以下のフォルダは削除します

    • .vs
    • Binaries
    • DerivedDataCache
    • Intermediate
    • Saved
  • Templates\TP_HoloLens\Config\DefaultGame.ini を開き ProjectName 変数を追加します

[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=4D0B0F6940B43F7E0260AE87738672FB
ProjectName=HoloLens Template
  • Templates\TP_FirstPerson\Config\TemplateDefs.ini を Templates\TP_HoloLens\Config\TemplateDefs.ini. にコピーします

  • Templates\TP_HoloLens\Config\TemplateDefs.ini を開き、LocalizedDisplayNames 変数と LocalizedDescriptions 変数を更新します

LocalizedDisplayNames=(Language="en",Text="HoloLens")
LocalizedDescriptions=(Language="en",Text="HoloLens template")

LocalizedDisplayNames=(Language="ko",Text="HoloLens")
LocalizedDescriptions=(Language="ko",Text="HoloLens Template")

LocalizedDisplayNames=(Language="ja",Text="HoloLens")
LocalizedDescriptions=(Language="ja",Text="HoloLens用のテンプレートです")

LocalizedDisplayNames=(Language="zh-Hans",Text="HoloLens")
LocalizedDescriptions=(Language="zh-Hans",Text="HoloLens Template")
  • ClassTypes, AssetTypes の行はテンプレートの説明に表示するためだけのものであり、プロジェクトの作成には影響しないため削除します

  • また以下の行を削除します

SharedContentPacks=(MountName="FirstPerson",DetailLevels=("Standard"))
SharedContentPacks=(MountName="Geometry",DetailLevels=("Standard","High"))
  • Templates\TP_HoloLens\Media に以下ファイルを格納するとサムネイルとして表示されます

    • TP_HoloLens.png
    • TP_HoloLens_preview.png

以上でプロジェクト作成時にテンプレートとして表示されます。

プロジェクト作成後からすぐに UX Tools のコンポーネントも利用可能な状態になっています。

f:id:HiromuKato:20201122182336p:plain

テンプレートは以下で公開しています。 1drv.ms

こういった便利なテンプレートはおそらくそのうち公式から公開されるとは思いますが、それまでのつなぎとして利用されると良いと思います。

参考サイト

プロジェクトをテンプレートに変換する

UNREAL FEST EXTREME 2020 WINTER

UNREAL FEST EXTREME 2020 WINTER で登壇しました。 事前に録画したものを配信するものでしたが、質疑応答にはライブ配信で対応をしました。

unrealengine.jp

動画は以下で公開されています。


MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER

UE4 を利用した HoloLens 2 アプリ開発に興味がある方は是非ご参照ください。

f:id:HiromuKato:20201119211756p:plain

MRTK v2.5.0 Examples 一覧(Experimental編)

本記事は、以下記事の続きで、MRTK v2.5.0 に含まれている Examples の Experimental シーンの説明パネルのキャプチャと概要を記載したものです。

www.hiromukato.com

MRTK Examples

Experimental_CameraOffset

  • CameraOffsetTest

    CameraOffsetInputSimulationProfile を試すシーンのようです。

    説明がないので詳しいことはわかりませんが Oculus Quest 2 など別のデバイスで必要となるカメラオフセット調整用でしょうか。

Experimental_ColorPicker

  • ColorPickerExample

    実行時にさまざまなマテリアルのカラー値を変更するカラーピッカーと対話するためのいくつかの異なる方法を示したサンプル。

    f:id:HiromuKato:20201004161308p:plain

    f:id:HiromuKato:20201004164000g:plain

Experimental_Dialog

  • DialogExample

    ダイアログコントロールの使用法を示したサンプル。

    f:id:HiromuKato:20201004161640p:plain

Experimental_Dock

  • DockExample

    ドックのサンプル。このコントロールを使用すると、オブジェクトを所定の位置に出し入れして、パレット、シェルフ、およびナビゲーションバーを作成できる。

    f:id:HiromuKato:20201004161655p:plain

    f:id:HiromuKato:20201004161706p:plain

Experimental_Dwell

  • DwellExample

    Dwell のサンプル。項目に視線を合わせ、見つめ続けることでトリガーするコントロール。

    f:id:HiromuKato:20201004161718p:plain

Experimental_Elastic

  • ElasticSystemExamples

    弾性システムのサンプル。MRTK 内で動的、応答性、および手続き型のフィードバックとアニメーションを可能にする。

    f:id:HiromuKato:20201004161731p:plain

    f:id:HiromuKato:20201004171448g:plain

Experimental_ExamplesHub

  • MRTKExamplesHub

    MRTKExamplesHubMainMenu の親シーン。

  • MRTKExamplesHubMainMenu

    MRTK の様々なサンプルを確認することができるシーン。

    複数のシーンを切り替えるための Scene System の参考にもなる。 詳細については MRTK Examples Hub日本語)参照。

    f:id:HiromuKato:20201004161754p:plain

Experimental_HandCoach

  • HandCoachExample

    体験で使用できるさまざまなハンドコーチの例を示したサンプル。

    f:id:HiromuKato:20201004161809p:plain

    f:id:HiromuKato:20201004164350g:plain

Experimental_HandMenuLayout

  • HandMenuLayoutExamples

    ハンドメニューレイアウトの例をいくつか示したサンプル。

    f:id:HiromuKato:20201004161823p:plain

    f:id:HiromuKato:20201004161835p:plain

Experimental_Joystick

  • JoystickExample

    ジョイスティックのサンプル。

    ジョイスティックを使用して大きなオブジェクトを移動、拡大縮小、回転する方法を示している。また、ジョイスティックの動作を微調整するスライダーが下にある。

    f:id:HiromuKato:20201004161849p:plain

    f:id:HiromuKato:20201004164828g:plain

Experimental_MixedRealityKeyboard

  • MixedRealityKeyboardExample

    OS標準のキーボードを表示するサンプル。

    Unity エディター上ではキーボードは表示されないが、物理キーボードで入力は可能。

Experimental_NonNativeKeyboard

  • NonNativeKeyboardExample

    OS標準ではない独自のキーボードを表示するサンプル。

    Unity エディター上でも表示される。

    f:id:HiromuKato:20201004161903p:plain

Experimental_PulseShader

  • PulseShaderExamples

    ハンドメッシュと空間メッシュの視覚化にパルスシェーダーを使用する方法を示したサンプル。

    f:id:HiromuKato:20201004161916p:plain

    f:id:HiromuKato:20201004164446g:plain

Experimental_RiggeHandVisualizer

  • RiggedHandVisualizer

    SkinnedMeshを制御して手を視覚化するサンプル。(詳細

Experimental_Solvers

  • DirectionalIndicatorExample

    方向インジケーターの使用法を示したサンプル。

    f:id:HiromuKato:20201004161931p:plain

    f:id:HiromuKato:20201004163651g:plain

  • FollowSolverExample

    Follow ソルバー(追跡対象のターゲットの前に要素を配置するソルバー)のサンプル。

    f:id:HiromuKato:20201004161944p:plain

MRTK v2.5.0 Examples 一覧(Demos編)

MRTK v2.5.0 に含まれている Examples の Demos シーンについて確認したので、 それぞれの説明パネルのキャプチャと概要を記載しました。ボリュームが多かったので Experimental のシーンについては別記事でまとめています。

www.hiromukato.com

MRTK v2.5.0 の更新内容については、リリースノート をご参照ください。

確認環境

MRTK Examples

MRTK v2.5.0 では UPM(Unity Package Manager) を利用した場合 Examples を個別にインストールする必要があります。

UPM によるインストールについては、以下を参照ください。

Mixed Reality Toolkit and Unity Package Manager

f:id:HiromuKato:20201004154226p:plain


以降、各シーンの概要です。

Demos_Audio

  • AudioLoFiEffectExamples

    ローファイオーディオのサンプル。球をタップすることで3種類の効果を確認できる。

    f:id:HiromuKato:20201004154327p:plain

  • AudioOcclusionExamples

    音の遮蔽を確認することができるサンプル。壁の前と後ろで音の聞こえ方が変わる。

    f:id:HiromuKato:20201004154349p:plain

Demos_Boundary

  • BoundaryVisualizationExample

    境界を視覚化するサンプル(VR向け機能)。

    f:id:HiromuKato:20201004154408p:plain

Demos_Diagnostics

  • DiagnosticsDemo

    診断システムを使用して実行時のアプリケーションパフォーマンスを確認するサンプル。

    f:id:HiromuKato:20201004154431p:plain

Demos_EyeTracking

  • EyeTrackingDemo-00-RootScene

    アイトラッキングの他のサンプル(02~05)の親シーン。

    f:id:HiromuKato:20201004154515p:plain

  • EyeTrackingDemo-01-BasicSetup

    アイトラッキングを行うための最小設定のシーン。

    f:id:HiromuKato:20201004154539p:plain

  • EyeTrackingDemo-02-TargetSelection

    アイゲイズと選択(エアタップまたは音声コマンド)で宝石を破壊するサンプル。

    f:id:HiromuKato:20201004154559p:plain

  • EyeTrackingDemo-03-Navigation

    アイゲイズとスクロール、パン、ズームイン/アウトのサンプル。

    f:id:HiromuKato:20201004154617p:plain

  • EyeTrackingDemo-04-TargetPositioning

    目とハンドまたはボイスによるオブジェクトの選択、移動を行うサンプル。

    f:id:HiromuKato:20201004154629p:plain

  • EyeTrackingDemo-05-Visualizer

    アイトラッキングデータをビジュアライズするサンプル。

    f:id:HiromuKato:20201004154643p:plain

Demos_Gltf

  • Glb-Loading-Demo

    ネット経由で Glb ファイルを読み込むサンプル。

    f:id:HiromuKato:20201004154704p:plain

  • Gltf-Loading-Demo

    StreamingAssets にある Gltf ファイルを読み込むサンプル。

    f:id:HiromuKato:20201004154728p:plain

    予め StreamingAssets 配下に gltfファイルを準備しておくか、または以下の [Copy models to StreamingAssets] ボタンを押してモデルを取得しておく必要がある。

    f:id:HiromuKato:20201004154811p:plain

Demos_HandTracking

  • HandInteractionExamples

    プレス、タッチ、グラブ、スクロール、移動、回転、スケールなど、さまざまなタイプのハンドトラッキングインタラクションを示したサンプル。

    f:id:HiromuKato:20201004154835p:plain

    f:id:HiromuKato:20201004154857p:plain

  • HandInteractionGestureEventsExample

    IMixedRealityGestureHandler を使用したさまざまなタイプのジェスチャーイベント(Select, Hold, Manipulation, Navigation)を示したサンプル。

    f:id:HiromuKato:20201004154924p:plain

  • HandInteractionRecordArticulatedHandPose

    手のポーズ(位置・回転)を記録するサンプル。Record Right Hand、もしくは Record Left Hand のボイスコマンドで記録を開始し、Stop ボイスコマンドで記録を停止する。Console に記録内容が json で表示される。

  • HandInteractionTouchablesExample

    さまざまなタッチ可能なコンポーネントと、それらがさまざまなコライダーとどのようにインタラクションするかを示したサンプル。

    f:id:HiromuKato:20201004154944p:plain

    f:id:HiromuKato:20201004154958p:plain

  • HandMenuExamples

    ハンドメニューを表示するサンプル(追跡されたオブジェクトを手で拘束されたコンテンツの安全な領域に拘束するソルバーである HandConstraint の使用方法を示している)。ハンドメニューを表示するには、手を上げて手のひらを見る必要がある。

    f:id:HiromuKato:20201004155014p:plain

    f:id:HiromuKato:20201004155030p:plain

  • LeapMotionHandTrackingExample

    Leap Motion によるハンドトラッキングのサンプル。本サンプルを試すには、Leap Motion Controller が必要。

    f:id:HiromuKato:20201004155049p:plain

  • NearMenuExamples

    様々なメニューのサンプル。ピン止めしたり、掴んで移動したりできる。

    f:id:HiromuKato:20201004155105p:plain

    f:id:HiromuKato:20201004155117p:plain

Demos_Input

  • DictationExample

    音声を録音し、音声のテキスト化を行うことができるサンプル。日本語を表示する場合は日本語のフォントを入れる必要がある。

    f:id:HiromuKato:20201004155137p:plain

    f:id:HiromuKato:20201004155152p:plain

  • DisablePointersExample

    PointerUtils の利用方法を示したサンプル。

    コードから Hand Ray、Gaze、Grab、Poke などのポインターをオンオフすることができ、 HoloLens 1 と 2 などのスタイルのインタラクションを切り替えることができる。

    f:id:HiromuKato:20201004155210p:plain

    f:id:HiromuKato:20201004155225p:plain

  • InputActionsExample

    異なるソースからの入力をユーザー定義の入力アクションにマッピングし、単一のリスナーを介してそれを処理する方法を示したサンプル。

    回転可能なオブジェクトにフォーカスを当てた状態で、以下のアクションのいずれかを行うと Cube が回転する。

    • Xbox コントローラの B ボタンを押す
    • Rotate と言う
    • Hold ジェスチャーを実行する

    f:id:HiromuKato:20201004155348p:plain

  • InputDataExample

    手、頭、目、モーションコントローラーの位置・ポジションなどの入力データにアクセスする方法を示したサンプル。

    f:id:HiromuKato:20201004155404p:plain

  • PointerResultExample

    ポインタのヒット位置を利用して、ポインタクリック時にオブジェクトを生成する方法を示したサンプル。

    f:id:HiromuKato:20201004155422p:plain

  • PrimaryPointerExample

    プライマリポインタ変更イベントを使用してプライマリポインタを追跡する方法を示したサンプル。

    感覚を掴むために、異なるコントローラを使用してチーズを選択して操作したり、コントローラを切断したり、トラッキングロスを強制したりして遊ぶと良いらしい。

    f:id:HiromuKato:20201004155437p:plain

  • SpeechInputExamples

    音声コマンドの構成および使用する方法を示したサンプル。

    Open, Close, Change Color, (Select) の音声コマンドが登録されており、音声コマンド発声時にフォーカスが必要な Local Speech Handler と フォーカスの必要がない Global Speech Handler の例が示されている。

    f:id:HiromuKato:20201004155454p:plain

Demos_ScrollingObjectCollection

  • ScrollingObjectCollection

    ScrolingObjectCollection コンポーネントを使用したさまざまなタイプのスクロールインタラクションを示したサンプル。

    f:id:HiromuKato:20201004155511p:plain

    f:id:HiromuKato:20201004155523p:plain

Demos_Solvers

  • SolverExamples

    ソルバーのサンプル。

    ソルバーは、あらかじめ定義されたアルゴリズムに従ってオブジェクトの位置と向きを計算する手段を容易にするコンポーネント。

    f:id:HiromuKato:20201004155557p:plain

  • SurfaceMagnetismSpatialAwarenessExample

    Spatial Awarenessを使ってSurface Magnetism Solver の使用を示したサンプル。

    現実空間の壁の表面にオブジェクトがスナップして、ポインタに追従する。

    f:id:HiromuKato:20201004155614p:plain

  • TapToPlaceExample

    タップしてオブジェクトを配置するサンプル。

    タップして配置すると、オブジェクトがサーフェスに配置される。 サーフェスが検出されない場合、オブジェクトは、ソルバーハンドラーにあるTrackedTargetType(ヘッド、コントローラーレイ、ハンドジョイント)を基準にしたデフォルトの距離に配置される。

    f:id:HiromuKato:20201004155632p:plain

    f:id:HiromuKato:20201004155644p:plain

Demos_SpatialAwareness

  • SpatialAwarenessMeshDemo

    空間認識のサンプル。現実環境をワイヤーフレームメッシュで表示する。

    球のクリックで環境認識のオブザーバーの動作を停止・再開できる。

    f:id:HiromuKato:20201004155705p:plain

Demos_StandardShader

  • ClippingExamples

    Mixed Reality Toolkit/Standard シェーダをクリッピングプリミティブ(ClippingPlane、ClippingSphere、およびClippingBox)と組み合わせて使用して、メッシュ上のピクセルを動的にクリップする方法を示したサンプル。

    f:id:HiromuKato:20201004155720p:plain

    f:id:HiromuKato:20201004155732p:plain

  • HoverLightExamples

    Mixed Reality Toolkit/Standard シェーダと HoverLights を組み合わせて使用し、メッシュをダイナミックにライトする方法を示したサンプル。

    f:id:HiromuKato:20201004155750p:plain

    f:id:HiromuKato:20201004155803p:plain

  • MaterialGallery

    Mixed Reality Toolkit/Standard シェーダで可能なマテリアルとシェーディング手法の例を示したサンプル。

    f:id:HiromuKato:20201004155819p:plain

    f:id:HiromuKato:20201004155831p:plain

  • OutlineExamples

    Mixed Reality Toolkit/Standard シェーダを MeshOutline、MeshOutlineHierarchy、および MeshSmoother コンポーネントと組み合わせて、さまざまな手法を使用してメッシュレンダラーの輪郭を描く方法を示したサンプル。

    f:id:HiromuKato:20201004155850p:plain

    f:id:HiromuKato:20201004155902p:plain

  • StandardMaterialComparison

    Unity/Standard シェーダと Mixed Reality Toolkit/Standard シェーダを比較したサンプル。

    f:id:HiromuKato:20201004155923p:plain

  • StandardMaterials

    Mixed Reality Toolkit によって提供される標準的なマテリアルを示したサンプル。

    f:id:HiromuKato:20201004155937p:plain

    f:id:HiromuKato:20201004155949p:plain

Demos_Utilities

  • MixedRealityCapabilityDemo

    プラットフォームとデータプロバイダーによってサポートされる Capability の状況を表示するサンプル。

    f:id:HiromuKato:20201004160018p:plain

Demos_UX

  • BoundingBox

    • BoundingBoxExamples

      様々なタイプのバウンディングボックスのサンプル。

      BoundingBox コンポーネントは deprecated となっているので注意。今後は BoundsControl を利用する。

      f:id:HiromuKato:20201004160045p:plain

    • BoundingBoxRuntimeExample

      実行時にバウンディングボックスを作成し、さまざまなバウンディングボックスのプロパティを変更する方法を示したサンプル。

      BoundingBox コンポーネントは deprecated となっているので注意。今後は BoundsControl を利用する。

      f:id:HiromuKato:20201004160100p:plain

  • BoundsControl

    • BoundsControlExamples

      様々なタイプのバウンディングボックスのサンプル。

      f:id:HiromuKato:20201004160149p:plain

    • BoundsControlRuntimeExample

      実行時にバウンディングボックスを作成し、さまざまなバウンディングボックスのプロパティを変更する方法を示したサンプル。

      f:id:HiromuKato:20201004160202p:plain

  • Collections

    • ObjectCollectionExamples

      オブジェクトコレクションのサンプル。

      オブジェクトコレクションは、あらかじめ定義された3次元形状のオブジェクトの配列をレイアウトするのに役立つレイアウトコントロール。

      f:id:HiromuKato:20201004160224p:plain

      f:id:HiromuKato:20201004160238p:plain

  • Interactables

    • InteractablesExamples

      Interactable コンポーネントのサンプル。

      Interactableは、ボタンやUIコントロールなどのインタラクティブなコンテンツを構築するための基本コンポーネントであり、インタラクティブな状態に基づいてフィードバックを提供する。

      f:id:HiromuKato:20201004160301p:plain

      f:id:HiromuKato:20201004160313p:plain

  • Lines

    • LineExamples

      ライン描画のサンプル。ラインは Data Provider と Renderer の2つのコンポーネントから成る。

      f:id:HiromuKato:20201004160331p:plain

  • ManipulationHandler

    • ManipulationHandlerExample

      ManipulationHandler のサンプル。 ManipulationHandler コンポーネントは deprecated となっているので注意。今後は ObjectManipulator を利用する。

      球が操作でき、 壁にぶつかると、インタラクションが停止する。

      f:id:HiromuKato:20201004160355p:plain

  • ObjectManipulator

    • ObjectManipulatorExample

      ObjectManipulator のサンプル。

      球が操作でき、 壁にぶつかると、インタラクションが停止する。

      f:id:HiromuKato:20201004160419p:plain

    • ObjectManipulatorExamplePhysics

      ObjectManipulator の物理機能をテストするためのサンプル。

      f:id:HiromuKato:20201004160431p:plain

  • PressableButton

    • PressableButtonExample

      PressableButton のさまざまなカスタマイズ例を示したサンプル。

      f:id:HiromuKato:20201004160448p:plain

      f:id:HiromuKato:20201004160459p:plain

  • ProgressIndicator

    • ProgressIndicatorExamples

      進行状況インジケーターのサンプル。

      f:id:HiromuKato:20201004160515p:plain

  • Slate

    • SlateExample

      Slate プレハブでの HandInteractionPanZoom スクリプトの使用を示したサンプル。

      f:id:HiromuKato:20201004160530p:plain

      f:id:HiromuKato:20201004160542p:plain

  • Slider

    • SliderExample

      ピンチスライダーコントロールの使用方法を示したサンプル。

      f:id:HiromuKato:20201004160554p:plain

      f:id:HiromuKato:20201004160606p:plain

  • Text

    • TextPrefabExamples

      テキストプレハブのサンプル。

      適切なスケーリング値と Text3DShader によるオクルージョンサポートにより、品質が最適化されている。

      f:id:HiromuKato:20201004160624p:plain

      f:id:HiromuKato:20201004160636p:plain

  • Tooltips

    • TooltipExamples

      ツールチップのサンプル。

      f:id:HiromuKato:20201004160652p:plain

      f:id:HiromuKato:20201004160704p:plain

MRTKのMixedRealityInputSystemの実装確認

以前の調べ物から一か月近く立ってしまいましたが、今回は入力に関する大本とも言える MixedRealityInputSystem について調べてみました(おかしなことを書いていたら教えてください)。

確認した MRTK のバージョンは v2.4.0 です。なお、今のタイミングでこのような調べ物をしているのは MRTK がかなり安定してきており v.2.5.0 だろうが v.2.6.0 だろうが大きく変わることはないと考えているためです(HoloToolKit → MRTK になったような破壊的変更が無い限りは問題ないはず)。

さっそく継承関係から見ていきます。

継承関係

f:id:HiromuKato:20200821204700p:plain

以下のインタフェース、クラスについては、こちら で確認済みのものになります。

  • IDisposable
  • IMixedRealityService
  • IMixedRealityEventSystem
  • BaseService
  • BaseEventSystem
  • BaseCoreSystem

IMixedRealityCapabilityCheck

登録済みデータプロバイダーが現在のプラットフォームで要求された機能をサポートしているかどうかを確認するためのインタフェース。

public interface IMixedRealityCapabilityCheck
{
    // 1つ以上の登録済みデータプロバイダーが現在のプラットフォームで要求された機能をサポートしているかどうかを確認する
    bool CheckCapability(MixedRealityCapability capability);
}

引数の MixedRealityCapability は以下のような enum です。

public enum MixedRealityCapability
{
    ArticulatedHand = 0,
    GGVHand,
    MotionController,
    EyeTracking,
    VoiceCommand,
    VoiceDictation,
    SpatialAwarenessMesh,
    SpatialAwarenessPlane,
    SpatialAwarenessPoint
}

IMixedRealityInputSystem

Mixed Reality Toolkitの入力システムのマネージャーインターフェイス。入力システム機能を提供するためのすべての代替システムは、このインターフェースから派生する必要がある。

かなりの要素を持ったインタフェースですが、大半がイベントを発生させるメソッドで、例えば Visual Studio で #region Input Events でアウトラインの折り畳みを行うとそれなりにすっきりします。また、残りのメソッドは主に入力に関する情報をスタックに出し入れするものであることが分かります。

public interface IMixedRealityInputSystem : IMixedRealityEventSystem
{
    // 入力が有効なときに発生するイベント
    event Action InputEnabled;

    // 入力が無効なときに発生するイベント
    event Action InputDisabled;

    // 手やモーションコントローラーなどの入力マネージャーによって検出されたインタラクション入力ソースのリスト
    HashSet<IMixedRealityInputSource> DetectedInputSources { get; }

    // 入力マネージャーによって現在検出されているIMixedRealityControllerのリスト
    // これは、リスト内のIMixedRealityInputSourcesのサブセットであるため、このプロパティはDetectedInputSourceに似ている
    HashSet<IMixedRealityController> DetectedControllers { get; }

    // ConfigurationProfileプロパティの型付き表現
    MixedRealityInputSystemProfile InputSystemProfile { get; }

    // この入力システムによって実装されている現在のフォーカスプロバイダー
    IMixedRealityFocusProvider FocusProvider { get; }

    // この入力システムによって実装されている現在のレイキャストプロバイダー
    IMixedRealityRaycastProvider RaycastProvider { get; }

    // この入力システムによって実装されている現在の Gazeプロバイダー
    IMixedRealityGazeProvider GazeProvider { get; }

    // この入力システムによって実装されている現在の EyeGazeプロバイダー
    IMixedRealityEyeGazeProvider EyeGazeProvider { get; }

    // 入力が現在有効かどうかを示す
    bool IsInputEnabled { get; }

    // 無効な入力状態を入力システムにプッシュする
    // 入力が無効になっている間、イベントは送信されず、カーソルは待機中のアニメーションを表示する
    void PushInputDisable();

    // 無効な入力状態を入力システムからポップする
    // 最後の無効状態が解除されると、スタック入力が再度有効になる
    void PopInputDisable();

    // 入力をすぐに再度有効にする入力無効スタックをクリアする
    void ClearInputDisableStack();

    // ゲームオブジェクトをモーダル入力スタックにプッシュする
    // ゲームオブジェクトの入力ハンドラは、フォーカスされたオブジェクトの前に入力イベントを優先する
    void PushModalInputHandler(GameObject inputHandler);

    // モーダル入力スタックから最後のゲームオブジェクトを削除する
    void PopModalInputHandler();

    // スタックからすべてのモーダル入力ハンドラーをクリアする
    void ClearModalInputStack();

    // ゲームオブジェクトをフォールバック入力スタックにプッシュする
    // モーダルオブジェクトまたはフォーカスオブジェクトがイベントを消費しない場合、ゲームオブジェクトの入力ハンドラーに入力イベントが与えられる
    void PushFallbackInputHandler(GameObject inputHandler);

    // フォールバック入力スタックから最後のゲームオブジェクトを削除する
    void PopFallbackInputHandler();

    // スタックからすべてのフォールバック入力ハンドラーをクリアする
    void ClearFallbackInputStack();

    #region Input Events  ★★★ ここから下は(上の2個を除いて)イベントを発生させるメソッド ★★★

    #region Input Source Events

    // 新しい一意の入力ソースIDを生成する
    // すべての入力ソースは、コンストラクターまたは初期化でこのメソッドを呼び出す必要がある
    uint GenerateNewSourceId();

    // 新しい入力ソースをリクエストする
    IMixedRealityInputSource RequestNewGenericInputSource(string name, IMixedRealityPointer[] pointers = null, InputSourceType sourceType = InputSourceType.Other);

    // 入力ソースが検出されたというイベントを発生させる
    void RaiseSourceDetected(IMixedRealityInputSource source, IMixedRealityController controller = null);

    // 入力ソースが失われたというイベントを発生させる
    void RaiseSourceLost(IMixedRealityInputSource source, IMixedRealityController controller = null);

    // 入力ソースの追跡状態が変更されたというイベントを発生させる
    void RaiseSourceTrackingStateChanged(IMixedRealityInputSource source, IMixedRealityController controller, TrackingState state);

    // 入力ソースの位置が変更されたというイベントを発生させる
    void RaiseSourcePositionChanged(IMixedRealityInputSource source, IMixedRealityController controller, Vector2 position);

    // 入力ソースの位置が変更されたというイベントを発生させる
    void RaiseSourcePositionChanged(IMixedRealityInputSource source, IMixedRealityController controller, Vector3 position);

    // 入力ソースの回転が変更されたというイベントを発生させる
    void RaiseSourceRotationChanged(IMixedRealityInputSource source, IMixedRealityController controller, Quaternion rotation);

    // 入力ソースのポーズ(位置・回転)が変更されたというイベントを発生させる
    void RaiseSourcePoseChanged(IMixedRealityInputSource source, IMixedRealityController controller, MixedRealityPose position);

    #endregion Input Source Events

    #region Focus Events

    // フォーカスが変更された事前イベントを発生させる
    // このイベントは、フォーカス変更イベントの前にロジックを実行するのに役立つ
    void RaisePreFocusChanged(IMixedRealityPointer pointer, GameObject oldFocusedObject, GameObject newFocusedObject);

    // フォーカス変更イベントを発生させる
    void RaiseFocusChanged(IMixedRealityPointer pointer, GameObject oldFocusedObject, GameObject newFocusedObject);

    // フォーカス開始イベントを発生させる
    void RaiseFocusEnter(IMixedRealityPointer pointer, GameObject focusedObject);

    // フォーカス終了イベントを発生させる
    void RaiseFocusExit(IMixedRealityPointer pointer, GameObject unfocusedObject);

    #endregion Focus Events

    #region Pointers

    #region Pointer Down

    // ポインタダウンイベントを発生させる
    void RaisePointerDown(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null);

    #endregion Pointer Down

    #region Pointer Dragged

    // ポインタのドラッグイベントを発生させる
    void RaisePointerDragged(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null);

    #endregion Pointer Dragged

    #region Pointer Click

    // ポインタクリックイベントを発生させる
    void RaisePointerClicked(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, int count, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null);

    #endregion Pointer Click

    #region Pointer Up

    // ポインタアップイベントを発生させる
    void RaisePointerUp(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null);

    #endregion Pointer Up

    #endregion Pointers

    #region Generic Input Events

    #region Input Down

    // 入力ダウンイベントを発生させる
    void RaiseOnInputDown(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction);

    #endregion Input Down

    #region Input Up

    // 入力アップイベントを発生させる
    void RaiseOnInputUp(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction);

    #endregion Input Up

    #region Float Input Changed

    // フロート入力の変更を発生させる
    void RaiseFloatInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, float inputValue);

    #endregion Float Input Changed

    #region Input Position Changed

    // 2自由度の入力イベントを発生させる
    void RaisePositionInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Vector2 position);

    // 3自由度の入力イベントを発生させる
    void RaisePositionInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Vector3 position);

    #endregion Input Position Changed

    #region Input Rotation Changed

    // 3自由度の入力イベントを発生させる
    void RaiseRotationInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Quaternion rotation);

    #endregion Input Rotation Changed

    #region Input Pose Changed

    // 6自由度の入力イベントを発生させる
    void RaisePoseInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, MixedRealityPose inputData);

    #endregion Input Pose Changed

    #endregion Generic Input Events

    #region Generic Gesture Events

    // ジェスチャー開始イベントを発生させる
    void RaiseGestureStarted(IMixedRealityController controller, MixedRealityInputAction action);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Vector2 inputData);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Vector3 inputData);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Quaternion inputData);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, MixedRealityPose inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Vector2 inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Vector3 inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Quaternion inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, MixedRealityPose inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCanceled(IMixedRealityController controller, MixedRealityInputAction action);

    #endregion

    #region Speech Keyword Events

    // 音声コマンドの認識イベントを発生させる
    void RaiseSpeechCommandRecognized(IMixedRealityInputSource source, RecognitionConfidenceLevel confidence, TimeSpan phraseDuration, DateTime phraseStartTime, SpeechCommands command);

    #endregion Speech Keyword Events

    #region Dictation Events

    // ディクテーション仮説イベントを発生させる
    void RaiseDictationHypothesis(IMixedRealityInputSource source, string dictationHypothesis, AudioClip dictationAudioClip = null);

    // ディクテーション結果イベントを発生させる
    void RaiseDictationResult(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip = null);

    // ディクテーション完了イベントを発生させる
    void RaiseDictationComplete(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip);

    // ディクテーションエラーイベントを発生させる
    void RaiseDictationError(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip = null);

    #endregion Dictation Events

    #region Hand Events

    // articulated hand の関節情報が更新されたことをシステムに通知する
    void RaiseHandJointsUpdated(IMixedRealityInputSource source, Handedness handedness, IDictionary<TrackedHandJoint, MixedRealityPose> jointPoses);

    // articulated hand のメッシュが更新されたことをシステムに通知する
    void RaiseHandMeshUpdated(IMixedRealityInputSource source, Handedness handedness, HandMeshInfo handMeshInfo);

    // タッチ開始イベントを発生させる
    void RaiseOnTouchStarted(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint);

    // タッチ更新イベントを発生させる
    void RaiseOnTouchUpdated(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint);

    // タッチ完了イベントを発生させる
    void RaiseOnTouchCompleted(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint);

    #endregion Hand Events

    #endregion Input Events
}

IMixedRealityDataProviderAccess

データプロバイダーを取得するためのインタフェース。

// システムが管理されたデータプロバイダーへのアクセスを提供できるようにする
public interface IMixedRealityDataProviderAccess
{
    // 登録されたデータプロバイダーのコレクションを取得する
    IReadOnlyList<IMixedRealityDataProvider> GetDataProviders();

    // 指定されたタイプの登録されたデータプロバイダーのコレクションを取得する
    IReadOnlyList<T> GetDataProviders<T>() where T : IMixedRealityDataProvider;

    // 指定した名前で登録されているデータプロバイダーを取得する
    // 指定された名前で複数のデータプロバイダーが登録されている場合、最初のプロバイダーが返される
    IMixedRealityDataProvider GetDataProvider(string name);

    // 指定された名前(オプション)で登録され、指定されたタイプに一致するデータプロバイダーを取得する
    // 指定された名前で複数のデータプロバイダーが登録されている場合、最初のプロバイダーが返される
    T GetDataProvider<T>(string name = null) where T : IMixedRealityDataProvider;
}

ここで出てくる IMixedRealityDataProvider の中身は以下の通り空です。

すべてのデータプロバイダーが IMixedRealityDataProvider を継承(というか中身がないけど実装)しなければいけない決め事と考えればよいでしょう。

// すべての Mixed Reality データプロバイダーに必要なインターフェイス
// データプロバイダーは、必要な情報(例:入力コントローラーの状態)を備えたサービスを提供するコンポーネント
public interface IMixedRealityDataProvider : IMixedRealityService
{
    // Reserved for future use.
}

BaseDataProviderAccessCoreSystem

IMixedRealityDataProvider の管理とアクセスのために定義された機能を持つ MRTK システムのコアとなる抽象クラス。

public abstract class BaseDataProviderAccessCoreSystem : BaseCoreSystem, IMixedRealityDataProviderAccess
{
    // データプロバイダーのリスト
        private readonly List<IMixedRealityDataProvider> dataProviders = new List<IMixedRealityDataProvider>();

    // 親のResetを呼んだあとに、具体的処理は各データプロバイダーに委譲
    public override void Reset()
    {
        base.Reset();

        foreach(var provider in dataProviders)
        {
            provider.Reset();
        }
    }
    // 以下も上に同じ
    public override void Enable() { }
    public override void Update() { }
    public override void LateUpdate() { }

    // コンストラクタ
    protected BaseDataProviderAccessCoreSystem(BaseMixedRealityProfile profile = null) : base(profile) { }

    // dataProviders リストからのデータプロバイダーを取得する
    #region IMixedRealityDataProviderAccess Implementation
    public virtual IReadOnlyList<IMixedRealityDataProvider> GetDataProviders() { }
    public virtual IReadOnlyList<T> GetDataProviders<T>() where T : IMixedRealityDataProvider { }
    public virtual IMixedRealityDataProvider GetDataProvider(string name) { }
    public virtual T GetDataProvider<T>(string name = null) where T : IMixedRealityDataProvider { }
    #endregion IMixedRealityDataProviderAccess Implementation

    // 指定されたタイプのデータプロバイダーを登録する
    protected bool RegisterDataProvider<T>(
        Type concreteType,
        SupportedPlatforms supportedPlatforms = (SupportedPlatforms)(-1),
        params object[] args) where T : IMixedRealityDataProvider
    {
        return RegisterDataProviderInternal<T>(
            true, // Retry with an added IMixedRealityService parameter
            concreteType,
            supportedPlatforms,
            args);
    }

    // 指定された具象型のインスタンスを作成し、プロバイダーを登録する内部メソッド
    private bool RegisterDataProviderInternal<T>(
        bool retryWithRegistrar,
        Type concreteType,
        SupportedPlatforms supportedPlatforms = (SupportedPlatforms)(-1),
        params object[] args) where T : IMixedRealityDataProvider
    {
        // 略...

        T dataProviderInstance;

        try
        {
            // ★データプロバイダーインスタンスの生成
            dataProviderInstance = (T)Activator.CreateInstance(concreteType, args);
        }
        // 略...

        return RegisterDataProvider(dataProviderInstance);
    }

    // 指定されたタイプのサービスを登録する
    protected bool RegisterDataProvider<T>(T dataProviderInstance) where T : IMixedRealityDataProvider
    {
        // 略...

        dataProviders.Add(dataProviderInstance);
        dataProviderInstance.Initialize();

        return true;
    }

    // 指定したタイプのデータプロバイダーの登録を解除する
    // name 引数が指定されていない場合、最初のインスタンスが登録解除される
    protected bool UnregisterDataProvider<T>(string name = null) where T : IMixedRealityDataProvider
    {
        T dataProviderInstance = GetDataProvider<T>(name);
        // 略...
        return UnregisterDataProvider(dataProviderInstance);
    }

    /// データプロバイダーの登録を解除する
    protected bool UnregisterDataProvider<T>(T dataProviderInstance) where T : IMixedRealityDataProvider
    {
        // 略...
        if (dataProviders.Contains(dataProviderInstance))
        {
            dataProviders.Remove(dataProviderInstance);

            dataProviderInstance.Disable();
            dataProviderInstance.Destroy();

            return true;
        }

        return false;
    }
}

簡単にまとめると内容としては、

  • dataProviders というデータプロバイダーのリストを持っており
  • データプロバイダーインスタンスを生成し dataProviders リストへの登録・解除を行い
  • データプロバイダーを取得する Get メソッドを持ち
  • 各データプロバイダーに対して、ライフサイクル関連のメソッド呼び出しを行う

クラスになっています。

MixedRealityInputSystem

ボリュームも多いのでポイントを絞って見ていきます。

まず、Proiory が 1 であり、他のすべてのマネージャーの前に処理されるためこの入力システムが重要なものであることがわかります。

public override uint Priority => 1;

次に初期化処理です。細かい部分を省くと以下の通りです。

// 初期化処理
public override void Initialize()
{
    // プロファイルの取得
    MixedRealityInputSystemProfile profile = ConfigurationProfile as MixedRealityInputSystemProfile;

    // インプットモジュールのチェック
    BaseInputModule[] inputModules = UnityEngine.Object.FindObjectsOfType<BaseInputModule>();

    // InputSystemProfileのチェック

    // InputActionRulesProfileのチェック

    // PointerProfileのチェック

    // イベントデータのインスタンス生成
    sourceStateEventData = new SourceStateEventData(EventSystem.current);
    ...

    // データプロバイダの生成
    CreateDataProviders();
}

データプロバイダの生成(CreateDataProviders)では、

  • GazeProvider が無ければ生成
  • プロファイルの Input Data Providers の内容(インスペクタで設定されているもの)に基づきデータプロバイダを生成し、dataProviders リストに登録

しています。

f:id:HiromuKato:20200821204657p:plain

次にポイントとなるのは、イベントハンドリングを行う Handle〇〇メソッドの部分になると思います。以下4つあります。

  • HandleEvent
  • HandlePointerEvent
  • HandleFocusChangedEvents
  • HandleFocusEvent

Handle〇〇メソッドが主に行っているのはイベントのディスパッチ処理で、イベントディスパッチメソッドは以下3つあります。

  • DispatchEventToGlobalListeners : グローバルリスナーにイベントをディスパッチ
  • DispatchEventToObjectFocusedByPointer : ポインターにフォーカスされたオブジェクトにイベントをディスパッチ
  • DispatchEventToFallbackHandlers : フォールバック処理(上記のどちらも行われなかった場合に行う処理)

グローバルリスナーとはオブジェクトにフォーカスしていない状態でイベントを実行するためのものになります。(例えば、何もフォーカスしていない状態でもエアタップしたらCubeを表示したい、といった場合などに利用する)

Handle〇〇メソッドは(大量にある)Raise△△メソッドから呼ばれています。

  • RaiseSourceDetected
  • RaiseSourceLost
  • RaiseSourceTrackingStateChanged
  • etc ...

イベント処理に関しては、すべて見ても重複する内容が多いので、例として ポインターをクリックした際に呼ばれる RaisePointerClicked メソッドについて確認をしてみます。

この RaisePointerClicked 自体は、ソリューションを検索をするとわかりますが何かしらのポインターから呼ばれるもので、 必要な情報が引数で渡ってきます。

f:id:HiromuKato:20200821204705p:plain

// まず public メソッドになっているここが外部から呼ばれる
public void RaisePointerClicked(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, int count, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null)
{
    // インプットイベントデータを生成する
    pointerEventData.Initialize(pointer, inputAction, handedness, inputSource, count);

    HandleClick();
}
private void HandleClick()
{
    // HandlePointerEventにイベントデータとハンドラーを渡す
    HandlePointerEvent(pointerEventData, OnInputClickedEventHandler);
}

// HandlePointerEvent の引数にある OnInputClickedEventHandler は以下のように定義された delegate
private static readonly ExecuteEvents.EventFunction<IMixedRealityPointerHandler> OnInputClickedEventHandler =
        delegate (IMixedRealityPointerHandler handler, BaseEventData eventData)
        {
            // OnInputClickedEventHandler が呼ばれたら以下が実行される
            var casted = ExecuteEvents.ValidateEventData<MixedRealityPointerEventData>(eventData);
            handler.OnPointerClicked(casted); // ←★イベントが発生すると最終的にこれが呼ばれる
        };

// ポインタイベントの処理
private void HandlePointerEvent<T>(BaseEventData eventData, ExecuteEvents.EventFunction<T> eventHandler) where T : IMixedRealityPointerHandler
{
    var baseInputEventData = ExecuteEvents.ValidateEventData<BaseInputEventData>(eventData);

    // グローバルリスナーにイベントをディスパッチする
    DispatchEventToGlobalListeners(baseInputEventData, eventHandler);

    if (baseInputEventData.used)
    {
        // 使用済みとマークされている場合、イベントがそれ以上進行しないようにする
        return;
    }

    // ポインターにフォーカスされたオブジェクトにイベントをディスパッチする
    DispatchEventToObjectFocusedByPointer(pointerEventData.Pointer, baseInputEventData, false, eventHandler);

    if (!baseInputEventData.used)
    {
        // フォールバック処理を行う
        DispatchEventToFallbackHandlers(baseInputEventData, eventHandler);
    }
}

流れを追うと

  1. クリック(エアタップ)をすると RaisePointerClicked メソッドが何かしらのポインターから呼ばれる
  2. HandlePointerEvent メソッドに何かしらのデータと実行させたいメソッド(ハンドラー)を渡して呼ぶ
  3. グローバルリスナーにデータとハンドラーを送信する(イベントをディスパッチする)
  4. eventHandler.Invoke((T)handlerEntry.handler, eventData); が呼ばれ、渡されたメソッドが実行される(詳細については BaseEventSystem の HandleEvent 参照)
  5. ポインターにフォーカスされたオブジェクトにデータとハンドラーを送信する(イベントをディスパッチする)
  6. ExecuteEvents.ExecuteHierarchy(focusedObject, baseInputEventData, eventHandler); が呼ばれ、渡されたメソッドが実行される
  7. 上記処理が行われなかった場合(イベントデータのusedフラグがfalseの場合)はフォールバック処理が行われる

という形になります。

上の流れの中から、最終的には OnInputClickedEventHandler の中の

handler.OnPointerClicked(casted);

という部分が呼ばれることになりますが、これにより開発者が独自に定義した部分が呼ばれ、またその際にイベントデータがパラメータとして渡ってきます。

イベントデータに関しては以下のような継承関係になっています。

f:id:HiromuKato:20200821204652p:plain

詳細の説明については省略しますが、ポインターのクリックに関するイベントは以下で定義されているものになります。

private MixedRealityPointerEventData pointerEventData;

この pointerEventData は RaisePointerClicked メソッドが呼ばれたときに以下の部分で生成されています。

    // インプットイベントデータを生成する
    pointerEventData.Initialize(pointer, inputAction, handedness, inputSource, count);

このデータがパラメータとして渡って来るので必要があれば利用することができます。 どのようなデータが渡ってきているのかは以下のように実際に見てみるのが早いでしょう。

f:id:HiromuKato:20200821204655p:plain

これらのイベント処理の確認として簡単なサンプルを以下に示します。

例えば以下のようなクラスを実装して Hierarchy の GameObject にアタッチすると、オブジェクトにフォーカスした状態でクリックすると OnPointerClicked メソッド内の処理が実行されます。

public class PointerSample : MonoBehaviour, IMixedRealityPointerHandler
{
    public void OnPointerClicked(MixedRealityPointerEventData eventData)
    {
        // ★クリックしたときに呼ばれる処理
    }

    public void OnPointerDown(MixedRealityPointerEventData eventData)
    {
        // 何かしらの実装
    }

    public void OnPointerDragged(MixedRealityPointerEventData eventData)
    {
        // 何かしらの実装
    }

    public void OnPointerUp(MixedRealityPointerEventData eventData)
    {
        // 何かしらの実装
    }
}

グローバルリスナーを利用する場合(オブジェクトにフォーカスしていなくてもクリック時に何かしらの処理を行いたい場合)には以下のような実装になります。

public class PointerSample : MonoBehaviour, IMixedRealityPointerHandler
{
    private void OnEnable()
    {
        // グローバルリスナーに登録
        CoreServices.InputSystem?.RegisterHandler<IMixedRealityPointerHandler>(this);
    }

    private void OnDisable()
    {
        // グローバルリスナーの登録を解除
        CoreServices.InputSystem?.UnregisterHandler<IMixedRealityPointerHandler>(this);
    }

    public void OnPointerClicked(MixedRealityPointerEventData eventData)
    {
        // ★クリックしたときに呼ばれる処理(オブジェクトにフォーカスしていなくても実行される)
    }

    ...
}

以上を簡単にまとめると 「クリックした、オブジェクトにフォーカスした、ジェスチャーを開始した」などのイベントがますどこかで発生し、MixedRealityInputSystem の対応する Raise△△メソッドが呼ばれ、最終的に開発者が実装した処理が実行される という流れになります。

その他の部分についてはモーダル処理を行う部分があります。 これについては、一般的なGUIのアプリケーションでもあるようにダイアログ表示中などに他のボタンを押したり操作できないようにするもので、同様の物と考えれば良いと思います。

まとめ

ということで、MixedRealityInputSystem の実装について確認してみました。

ざっくりとまとめると MixedRealityInputSystem とは

  • データプロバイダーへのアクセスを提供する
  • 各種イベントの処理を行う

ものであると言えるかと思います(入力を取り扱う門番的なイメージ)。

Inputに関しては、 プロファイルが階層構造になっており、さらにいくつかのプロファイルがあったり、データプロバイダーもいくつかの種類があるので今後そのあたりについても見ていきたいと思います。

UnityのリリースノートでHoloLensのキーワードが入っているもの

タイトルの通り、Unity のリリースノートから「HoloLens」のキーワードが入っているものを調べました。

確認したのは Unity 2018.4.23Unity 2020.1.0のリリースノートです。

HoloLensのキーワードが入っていなくても影響する改善はあるかもしれないのであくまで参考までに。


Unity 2019.1

  • XR: Fixed head-tracking and rendering for in-editor remoting to HoloLens devices (1138473, 1140716)

Unity 2019.1.7

  • XR: Fixed issue in HoloLens where we weren't using the correct anchor view for switching away from on screen keyboard in XAML. (1156224, 1156227)

Unity 2019.2.1

  • XR: Editor crash when using holographic emulator with hololens (1141385, 1163557)

  • XR: Fix issue with Depth Based LSR on HoloLens V2 that caused significant jitter. (1169760, 1169761)

Unity 2019.2.3

  • XR: Prevent Hololens app pause when switching focus to a 2D view within a running application.

Unity 2019.2.4

  • XR: Fixed a memory issue in HoloLens SurfaceObserver. (1176047, 1176063)

Unity 2019.2.9

  • XR: Fixed a instability issues with Depth based LSR for HoloLens V1 devices. (1174865, 1182448)

Unity 2019.2.11

  • Universal Windows Platform: Fixed TouchScreenKeyboard's SHIFT key duplicating last typed character on HoloLens. (1184623, 1191743)

  • XR: Added HoloLens 2 as a device name when running on HoloLens 2 devices. (1161756, 1188262)

Unity 2019.2.17

  • XR: Fixed an issue that would cause the Editor Hololens Remote device to improperly set before connecting.

Unity 2019.3.0

  • XR: Fix hololens remoting connect failure once connect has failed at least 1 time. (1204419)
    This has been backported and will not be mentioned in final notes.

  • XR: Fixed disconnecting after exiting play mode while remoting to Hololens. (1137090)
    This has been backported and will not be mentioned in final notes.

Unity 2019.3.3

  • XR: Fixed Hololens 2 pausing when UserPresence is lost with RunInBackground enabled. (1217943)

Unity 2019.3.6

  • XR: Fix crash when you connect to a Hololens 2 using the HolographicRemoting scripting api and then enable Windows Mixed Reality.

Unity 2019.4.5

  • XR: Add more details to error message when Hololens remoting fails to load the dll. (1161718)

  • XR: Fixed assert due to invalid camera pose on first frame of Hololens remoting connect. (1243433)

Unity 2020.1.0

  • XR: Fixed Holographic Emulation Window remoting bug that caused Unity to remote to Hololens 1 devices when Hololens 2 was selected.

  • XR: Fixed Hololens 2 camera snapshots not containing Unity app content.

  • XR: Prevent Hololens app pause when switching focus to a 2D view within a running application.


なお、以下は調べようと思ったきっかけです。

Bolt で HoloLens アプリ開発

f:id:HiromuKato:20200724211547p:plain

先日、Unity のビジュアルスクリプティングアセットである Bolt の無料化の発表がありました。Bolt を触るのは初めてですが、HoloLens 開発において使えるかどうか試してみました。

まず先に結論としては、簡単な動作確認程度ではありますが問題なく使えることが分かりました。

開発環境

  • Unity 2019.4.5f1
  • MRTK 2.4.0
  • Bolt 1.4.12

プロジェクト準備

  • Unity のプロジェクトを新規作成します。
  • MRTK をインポートし、HoloLens 用のシーン設定を行います(詳細については省略)。
  • Bolt をインポートします。

  • Bolt をインポートすると、Unityのメニューに Tools -> Install Bolt という項目が追加されるので選択します。

    f:id:HiromuKato:20200724211552p:plain

  • 以下の画面が出たので Api Compatibility Level を .NET Standard 2.0 にしたら動かないかもしれません(未確認)。ここでは素直に .NET 4.x で進めることにします。

    f:id:HiromuKato:20200724211555p:plain

    f:id:HiromuKato:20200724211617p:plain

  • Setup Wizard が表示されますがすべてデフォルト(およびお好みの設定)で進めます。

  • Assembly Options の項目については、MRTK の機能を Bolt で使う場合には必要になるものだと思いますが、こちらも後から設定ができるので変更なしで進めます。

    f:id:HiromuKato:20200724211604p:plain

サンプルプロジェクトの作成

キューブを Bolt により回転させるサンプルを作ります。

  1. まずは Cube を配置します。

  2. Cube に Flow Machine コンポーネントを追加します。
    f:id:HiromuKato:20200724211621p:plain

  3. Flow Machine の New ボタンを押します。
    f:id:HiromuKato:20200724211623p:plain

  4. エラーが表示されますが無視して続けます。
    f:id:HiromuKato:20200724211626p:plain

  5. Flow Machine の Edit Graph ボタンを押します。
    f:id:HiromuKato:20200724211630p:plain

  6. 以下のような画面が表示されます。
    f:id:HiromuKato:20200724211633p:plain

  7. 右クリックをして Rotate で検索をして、Rotate(xAngle, yAngle, zAngle, relativeTo) を選択します。 f:id:HiromuKato:20200724211648p:plain

  8. Update と Rotate をマウスドラッグでつなぎます。また yAngle に 1 を設定します。
    f:id:HiromuKato:20200724211651p:plain

  9. 以上で Play ボタンを押すとキューブが回転していることが確認できます。 f:id:HiromuKato:20200724211655g:plain

  10. UnityEditor で動作することが確認できるので、実機にデプロイしてみます。
    動きません。。
    以下は、PC で実行したものですが以下のようなエラーが出ました。
    f:id:HiromuKato:20200724211640p:plain

  11. 調べたところ、AOT Pre-Build というものが必要なようです。メニューにあるので実行します。
    f:id:HiromuKato:20200724211645p:plain

  12. 再度ビルドしなおして、デプロイしてみます。
    動きました!

まとめ

Bolt を利用して HoloLens アプリを開発できることがわかりました。

まだあまり詳しく触ってはいませんが、MRTK の機能を Bolt から使うには Setup Wizard で表示された Assembly Options での設定が必要になると思います。MRTK は dll が機能ごとに分割されているのですべて登録するのは大変かもしれませんが、今後そのあたりも見てみたいと思います。

参考サイト

light11.hatenadiary.com

MRTKのFocusProvider実装確認

FocusProvider

以前の記事でポインターが FocusProvider と関係していることが分かったので、今回は FocusProvider について見ていくことにしました(確認したバージョンはMRTK v2.4.0)。

かなり長くなってしまったので最初に FocusProvider とはどのようなものかを簡単にまとめると

  • 入力ソースを検出したときにソースに紐づいたポインターを FocusProvider 内に登録する

  • 各ポインターがフォーカスしている(レイがヒットしている)オブジェクトの詳細を取得する

  • フォーカスしているオブジェクトに対して必要に応じてイベントを発生させる(OnFocusEnter / OnFocusExit など)

というものです。

詳細について特に興味がなければここまでで大丈夫だと思います。

以降はほとんど調べたことのメモという感じなので興味のある方だけご覧ください。

では、まず継承関係を見てみます。

継承関係

f:id:HiromuKato:20200721032008p:plain

入り組んだ印象がありますが、順を追って見ていきます。

IDisposable

リソースを解放するためのインタフェース。Dispoaseメソッドを持つのみ。

IMixedRealityService

すべての Mixed Reality Services の汎用インターフェース。

以下は、コードからコメントを削除したもの。割とシンプルです。が、内容としてはオブジェクトのライフサイクルにおける処理を担っているので重要なものと言えます。Monobehaviour を継承せずにライフサイクルを管理するためのインタフェースと考えれば分かりやすいと思います。

public interface IMixedRealityService : IDisposable
{
    string Name { get; }
    uint Priority { get; }

    BaseMixedRealityProfile ConfigurationProfile { get; }

    void Initialize();
    void Reset();
    void Enable();
    void Update();
    void LateUpdate();
    void Disable();
    void Destroy();
}

IMixedRealityEventSystem

Mixed Reality Toolkit と互換性のあるイベントシステムを実装するために使用されるインターフェイス。

Obsolete な物を除いたコードは以下。3つのメソッドを持つのみですが、ちょっと分かりにくいと感じるかもしれません。ただ具体的な処理はこのインタフェースを実装したクラス次第なので、こういったメソッドがあるんだなとだけ理解しておけば良いと思います。

public interface IMixedRealityEventSystem : IMixedRealityService
{
    // すべてのイベントを処理して目的の受信者に転送するための主な機能
    void HandleEvent<T>(BaseEventData eventData, ExecuteEvents.EventFunction<T> eventHandler)
        where T : IEventSystemHandler;

    // 引数に渡されたハンドラを、T インターフェースを介して処理されるすべてのイベントのグローバルリスナーとして登録する
     void RegisterHandler<T>(IEventSystemHandler handler) where T : IEventSystemHandler;

    // 登録されたハンドラーを解除する
     void UnregisterHandler<T>(IEventSystemHandler handler) where T : IEventSystemHandler;
}

なお、3つのメソッドすべてにおいて、型パラメータの T として IEventSystemHandler(もしくはその派生型)であることが指定されています。そのためこれを理解するには IEventSystemHandler については理解しておく必要があります。

tsubakit1.hateblo.jp

BaseService

IMixedRealityService を実装し、すべてのサービスのデフォルトプロパティを提供する。

抽象クラスであまり中身はありません。気になるところは以下ぐらいです。

  • Priority のデフォルト値が 10 に設定されている
  • Dispose メソッドの実装

BaseEventSystem

このクラスを継承することで、他のシステムの機能にイベント機能を持たせることができる基本イベントシステム。

継承元にある IMixedRealityService の実装は Destroy メソッド以外は含まれていないので、主な内容としては IMixedRealityEventSystem のデフォルト実装をしたものであるということでしょうか。 IMixedRealityEventSystem で Obsolete とされている EventListeners / Register メソッド / Unregister メソッドに関連する部分については省略しつつ、実装を見てみます。

// (このイベントシステムに)登録された型によってグループ化されたすべてのイベントハンドラーのディクショナリ
public Dictionary<Type, List<EventHandlerEntry>> EventHandlersByType

// RegisterHandler API を介して登録されたすべてのハンドラーにイベントを送信する
public virtual void HandleEvent<T>(BaseEventData eventData, ExecuteEvents.EventFunction<T> eventHandler) where T : IEventSystemHandler
{
    (略...)

    List<EventHandlerEntry> handlers;
    if (EventHandlersByType.TryGetValue(typeof(T), out handlers))
    {
        for (int i = handlers.Count - 1; i >= 0; i--)
        {
            var handlerEntry = handlers[i];
            
            (略...)
            
            // ★このメソッド内で一番重要な部分
            // EventHandlersByTypeから一致する型のインタフェースのハンドラーを取り出しイベントを実行する
            // つまり、
            // T 型のインタフェースを継承した handler インスタンスの eventHandler メソッドに
            // eventData パラメータを渡して実行する
            eventHandler.Invoke((T)handlerEntry.handler, eventData);
        }
    }

    (略...)
}

今の段階では、具体的にどのようなデータやハンドラーが渡ってくるのかは分かりませんが、何かしらのインタフェース(T)を実装したインスタンスのメソッドを実行する部分になります。 (なお、呼び出し元は MixedRealityInputSystem クラスの DispatchEventToGlobalListeners メソッドです)

このイベントのディスパッチ中に EventHandlersByType ディクショナリへの追加・削除を行うことは安全ではないため、eventExecutionDepth という変数と postponedActions というリストを用いて制御するような処理も入っています。

// 引数に渡されたハンドラー(IEventSystemHandlerを継承したインスタンス)を、T インターフェースを介して処理されるすべてのイベントのグローバルリスナーとして登録する
public virtual void RegisterHandler<T>(IEventSystemHandler handler) where T : IEventSystemHandler
{
    (略...)

    // IEventSystemHandler を除いた handler の親インターフェースも登録する
    TraverseEventSystemHandlerHierarchy<T>(handler, RegisterHandler);
}

// 登録されたハンドラーを解除する
public virtual void UnregisterHandler<T>(IEventSystemHandler handler) where T : IEventSystemHandler
{
    (略...)

    // IEventSystemHandler を除いた handler の親インターフェースも登録を解除する
    TraverseEventSystemHandlerHierarchy<T>(handler, UnregisterHandler);
}

EventHandlersByType ディクショナリおよびその内部のリストに型とハンドラーを登録・解除しています。 グローバルリスナー(オブジェクトにフォーカスが当たっていなくても入力を受け付けるもの)として登録・解除するところがポイントかと思います。

上記以外の部分については private メソッドなので、上記処理を行うための内部処理になります。

BaseCoreSystem

コンストラクタで プロファイルの設定Priorityを 5 に設定している(コアシステムのデフォルトの優先度は他のサービスよりも高い)だけで、他はObsolete。

IEventSystemHandler

Unity で準備されたインタフェース、中身は空。

IMixedRealityBaseInputHandler

すべての入力ハンドラーの基本インターフェース。

これにより、ExecuteEvents.ExecuteHierarchy<IIMixedRealityBaseInputHandler> を使用して、すべての入力ハンドリングインターフェイスにイベントを送信できる。

コードは以下の通りで中身は空。

public interface IMixedRealityBaseInputHandler : IEventSystemHandler {}

IMixedRealitySpeechHandler

音声認識に反応するために実装するインターフェース。

メソッドを 1 つだけ持つ。

public interface IMixedRealitySpeechHandler : IMixedRealityBaseInputHandler
{
    void OnSpeechKeywordRecognized(SpeechEventData eventData);
}

IMixedRealitySourceStatehandler

入力ソースが検出または失われたときなど、ソースの状態変化に対応するために実装するインターフェイス。

メソッドは以下の 2 つを持つ。

public interface IMixedRealitySourceStateHandler : IEventSystemHandler
{
    // ソースが検出されると発生する
    void OnSourceDetected(SourceStateEventData eventData);

    // ソースが失われたときに発生する
    void OnSourceLost(SourceStateEventData eventData);
}

IMixedRealityFocusProvider

ポインターのフォーカスを処理するフォーカスプロバイダーを実装するためのインタフェース。

以下は、日本語コメントを付けたもの。どのようなものがあるかだけ目を通しておき、あとは、継承先の FocusProvider を見ていくのが良いと思います。

public interface IMixedRealityFocusProvider : IMixedRealityService, IMixedRealitySourceStateHandler, IMixedRealitySpeechHandler
{
    // オーバーライド範囲がない限り、すべてのポインターがGameObjectと衝突できる最大距離
    float GlobalPointingExtent { get; }

    // レイキャストの対象となるフォーカスポインターのレイヤーマスク
    LayerMask[] FocusLayerMasks { get; }

    // EventSystem がレイキャストに使用するカメラ
    Camera UIRaycastCamera { get; }

    // 現在のプライマリポインタ。 使用中のプライマリポインターセレクターによって決定される(MixedRealityPointerProfile.PrimaryPointerSelectorを参照)
    IMixedRealityPointer PrimaryPointer { get; }

    // ポインティングソースの現在フォーカスされているオブジェクトを取得する
    // ポインティングソースが登録されていない場合、GazeのFocused GameObjectが返される
    GameObject GetFocusedObject(IMixedRealityPointer pointingSource);

    // ポインティングソースの現在フォーカスされているオブジェクトの詳細情報を取得する
    bool TryGetFocusDetails(IMixedRealityPointer pointer, out FocusDetails focusDetails);

    // 指定されたポインターのFocusDetailsを設定し、現在設定されているフォーカスポイントをオーバーライドする
    // これは、フォーカスがロックされている場合でも、特定のポインターのFocusDetailsを変更するために使用できる
    bool TryOverrideFocusDetails(IMixedRealityPointer pointer, FocusDetails focusDetails);

    // 新しい一意のポインターIDを生成する
    uint GenerateNewPointerId();

    // ポインタがフォーカスプロバイダーに登録されているかどうかを確認する
    bool IsPointerRegistered(IMixedRealityPointer pointer);

    // ポインタをフォーカスプロバイダーに登録する
    bool RegisterPointer(IMixedRealityPointer pointer);

    // フォーカスプロバイダーに登録されているポインタの登録を解除する
    bool UnregisterPointer(IMixedRealityPointer pointer);

    // 指定されたタイプのすべての登録済みポインターへのアクセスを提供する
    IEnumerable<T> GetPointers<T>() where T : class, IMixedRealityPointer;

    // プライマリポインターの変更をサブスクライブする
    void SubscribeToPrimaryPointerChanged(PrimaryPointerChangedHandler handler, bool invokeHandlerWithCurrentPointer);

    // プライマリポインタの変更のサブスクライブを解除する
    void UnsubscribeFromPrimaryPointerChanged(PrimaryPointerChangedHandler handler);
}

IPointerPreferences

入力システムのポインターの動作や、その他の設定を取得・設定するためのインターフェースを提供する。

動作は、ポインターごとではなく、ポインタータイプと入力タイプに基づいて記述される。

これは、表示される新しいポインターが一貫した動作を維持するようにするため。

public interface IPointerPreferences
{
    // 指定されたポインタの PointerBehavior を取得する
    PointerBehavior GetPointerBehavior(IMixedRealityPointer pointer);

    // 指定されたポインタータイプ、利き手、および入力タイプの PointerBehavior を取得する
    PointerBehavior GetPointerBehavior<T>(
        Handedness handedness,
        InputSourceType sourceType) where T : class, IMixedRealityPointer;

    // 指定されたポインタータイプ、利き手、および入力タイプの PointerBehavior を設定する
    void SetPointerBehavior<T>(Handedness handedness, InputSourceType inputType, PointerBehavior pointerBehavior) where T : class, IMixedRealityPointer;

    // 視線ポインターのポインター動作
    // 内部クラスであるため、内部視線ポインターは実際にはここから参照できないため、視線ポインターを一意にする
    PointerBehavior GazePointerBehavior { get; set; }
}

なお、PointerBehavior とは以下のようなポインターの動作を示す enum です。

public enum PointerBehavior
{
    // ポインターのアクティブ状態はMRTK入力システムによって管理される
    // ニアポインター(グラブ、ポーク)の場合、常に有効になる
    // ニアポインターでない場合、同じ手のニアポインターがアクティブな場合は無効になる
    // これは、手がグラブ可能なものの近くにあるときにレイをオフにすることを可能にするもの
    Default = 0,
    // 他のどのポインターがアクティブであるかに関係なく、ポインターは常にオン
    AlwaysOn,
    // 他のどのポインターがアクティブであるかに関係なく、ポインターは常にオフ
    AlwaysOff
};

FocusProvider

フォーカスプロバイダーは、入力ソースごとにフォーカスされたオブジェクトを処理する。

さてついに本題の FocusProvider です。

言うまでもなく、上で書いてきたすべてのインタフェースの実装をしたもので、確認のために継承関係の図を再掲します。

f:id:HiromuKato:20200721032008p:plain

また、FocusProvider は内部に PointerHitResult / PointerData / PointerPreferences クラスを持っています。PointerData クラスについては継承元があるので、継承関係を掲載します。

f:id:HiromuKato:20200721032046p:plain

PointerHitResult

中間ヒット結果を格納するためのヘルパークラス。ポインターの可能なすべてのヒットが処理されたら、PointerDataに適用する必要がある。

IEquatable

インスタンスの等価性を判断するためのインタフェース。

IPointerResult

ポインタの結果を定義するインターフェイス。

public interface IPointerResult
{
    // ポインターレイステップの開始点。
    Vector3 StartPoint { get; }

    // 現在フォーカスされているゲームオブジェクトの詳細。
    FocusDetails Details { get; }

    // 現在のポインターのターゲットGameObject
    GameObject CurrentPointerTarget { get; }

    // 前のポインターターゲット。
    GameObject PreviousPointerTarget { get; }

    // 最後のレイキャストヒットを生成したステップのインデックス。レイキャストヒットがない場合は0。
    int RayStepIndex { get; }
}

PointerData

ポインターに関する情報を持つ。

コンストラクタでポインターを渡され生成される。

ヒットしているオブジェクトの詳細情報などのポインターに関連した付随情報を持つ。

PointerPreferences

ポインターの動作を取得・設定するためのクラス(入力ソースのタイプ、ポインターのタイプ、手に応じたポインタービヘイビアーを持つ)


以降は、FocusProvider の実装にコメントを日本語で付けたものです。重要そうなメソッドなどは中身を簡略化しつつコメントを入れました。

/// フォーカスプロバイダーは、入力ソースごとにフォーカスされたオブジェクトを処理します。
/// 必要に応じてGaze Pointerのみを取得するための便利なプロパティがあります。
public class FocusProvider : BaseCoreSystem,
    IMixedRealityFocusProvider,
    IPointerPreferences
{
    // コンストラクタ
    public FocusProvider(MixedRealityInputSystemProfile profile) : base(profile) { ...}

    // このディクショナリにポインター情報を格納する
    private readonly Dictionary<uint, PointerData> pointers = new Dictionary<uint, PointerData>();

    private readonly HashSet<GameObject> pendingOverallFocusEnterSet = new HashSet<GameObject>();
    private readonly Dictionary<GameObject, int> pendingOverallFocusExitSet = new Dictionary<GameObject, int>();
    private readonly List<PointerData> pendingPointerSpecificFocusChange = new List<PointerData>();

    private readonly Dictionary<uint, IMixedRealityPointerMediator> pointerMediators =
        new Dictionary<uint, IMixedRealityPointerMediator>();

    private readonly PointerHitResult hitResult3d = new PointerHitResult();
    private readonly PointerHitResult hitResultUi = new PointerHitResult();

    // シーンクエリタイプが SphereOverlap の場合に対象とするコライダーの最大数
    private readonly int maxQuerySceneResults = 128;
    // 複合コライダーの場合、個々のコライダーはフォーカスを受け取るかどうか
    private bool focusIndividualCompoundCollider = false;

    public IReadOnlyDictionary<uint, IMixedRealityPointerMediator> PointerMediators => pointerMediators;

    // アクティブな IMixedRealityNearPointers の数
    public int NumNearPointersActive { get; private set; }

    // ゲイズカーソルを除く、アクティブなファーインタラクション(モーションコントローラーレイ、ハンドレイなど)をサポートするポインターの数
    public int NumFarPointersActive { get; private set; }

    // プライマリーポインターを取得・設定する(変更時は変更イベント発火)
    public IMixedRealityPointer PrimaryPointer { ... }
    private IMixedRealityPointer primaryPointer;

    #region IMixedRealityService Properties

    // 名前
    public override string Name { get; protected set; } = "Focus Provider";

    // プライオリティー(Default値 10, コアシステム 5 なので重要であることがわかる)
    public override uint Priority => 2;

    // ポインターの届く距離(プロファイルに設定されている値を取得する、デフォルト値は10)
    float IMixedRealityFocusProvider.GlobalPointingExtent { ... }

    // フォーカスするレイヤーマスク(PointerProfileから取得する)
    private LayerMask[] focusLayerMasks = null;
    public LayerMask[] FocusLayerMasks { ... }

    // uiRaycastCamera用のレンダリングテクスチャ(デバイスディスプレイの解像度に関係なくドラッグの閾値が同じように扱われるようするために利用)
    private RenderTexture uiRaycastCameraTargetTexture = null;

    // UIコンポーネントへのヒット判定するためのUIレイキャストカメラ
    private Camera uiRaycastCamera = null;
    public Camera UIRaycastCamera => uiRaycastCamera;

    #endregion IMixedRealityService Properties

    // このサービスを開始するために MixedRealityToolkit が正しくセットアップされているかどうかを確認する
    private bool IsSetupValid { ... }

    // GazeProvider は特殊なため、登録されたポインタでなくても追跡するようにする
    //   StabilizationPlaneModifier およびユーザーがどこを見ているかを気にする可能性のある他のコンポーネントのために、
    //   フォーカスにゲイズが使用されていない場合でも、ゲイズのレイキャストを行う必要がある
    private PointerData gazeProviderPointingData;
    private PointerHitResult gazeHitResult;

    // 新しいレイキャスト位置のキャッシュ(UIレイキャスト結果の更新にのみ使用される)
    private Vector3 newUiRaycastPosition = Vector3.zero;

    // 中間ヒット結果を格納するためのヘルパークラス
    // ポインターの可能なすべてのヒットが処理されたら、PointerDataに適用する必要がある
    private class PointerHitResult
    {
        public MixedRealityRaycastHit raycastHit;
        public RaycastResult graphicsRaycastResult;

        public GameObject hitObject;
        public Vector3 hitPointOnObject = Vector3.zero;
        public Vector3 hitNormalOnObject = Vector3.zero;

        public RayStep ray;
        public int rayStepIndex = -1;
        public float rayDistance;

        // 略...
    }

    // ポインターに関する情報を持つクラス
    [Serializable]
    private class PointerData : IPointerResult, IEquatable<PointerData>
    {
        // ポインター(コンストラクタで設定される)
        public readonly IMixedRealityPointer Pointer;

        // フォーカスしているオブジェクトの詳細情報
        public FocusDetails Details { ... }
        private FocusDetails focusDetails = new FocusDetails();

        // 略...

        // ポインターのヒット情報を更新する
        // FocusProvider の Update > UpdatePointers > UpdatePointer メソッドから呼ばれる
        public void UpdateHit(PointerHitResult hitResult)
        {
            // hitResult をもとに focusDetails を更新する
        }

        // フォーカスがロックされているときにフォーカス情報を更新する
        // (ロックされている場合は、UpdateHitではなくこっちが呼ばれる)
        // オブジェクトが動いている場合、ヒットポイントが新しいワールドトランスフォームに更新されます。
        public void UpdateFocusLockedHit()
        {
            // 
        }

        // 略...
    }

    // ゲイズポインターの有効・無効を切り替えるステートマシン(手が表示されたらゲイズカーソルを非表示にしたりなど)
    private readonly GazePointerVisibilityStateMachine gazePointerStateMachine = new GazePointerVisibilityStateMachine();

    // プライマリポインターを選択するために使用されるインターフェイス
    private IMixedRealityPrimaryPointerSelector primaryPointerSelector;

    // プライマリポインターの変更時に発生するイベント
    private event PrimaryPointerChangedHandler PrimaryPointerChanged;

    #region IMixedRealityService Implementation

    // FocusProvider の初期化処理
    public override void Initialize() { ... }

    // FocusProvider の終了処理
    public override void Destroy() { ... }

    // 毎フレーム行う処理
    public override void Update()
    {
        if (!IsSetupValid)
        {
            return;
        }

        UpdatePointers();      // ポインターの更新
        UpdateGazeProvider();  // ゲイズは特別扱い
        UpdateFocusedObjects();// フォーカスオブジェクトの更新

        PrimaryPointer = primaryPointerSelector?.Update();
    }

    // ゲイズがフォーカスに使用されていないシナリオでも、ゲイズレイキャストプロバイダーを更新する
    private void UpdateGazeProvider() { ... }

    #endregion IMixedRealityService Implementation

    #region Focus Details by IMixedRealityPointer

    // ポインターがフォーカスしているオブジェクトを取得する
    public GameObject GetFocusedObject(IMixedRealityPointer pointingSource) { ... }

    // ポインターがフォーカスしているオブジェクトの詳細を取得する
    public bool TryGetFocusDetails(IMixedRealityPointer pointer, out FocusDetails focusDetails) { ... }

    // フォーカス情報を上書きする
    public bool TryOverrideFocusDetails(IMixedRealityPointer pointer, FocusDetails focusDetails) { ... }

    #endregion Focus Details by IMixedRealityPointer

    #region Utilities

    // 新しいポインターIDを生成する
    public uint GenerateNewPointerId() { ... }

    // UIRaycastCamera を作成するためのユーティリティ
    private void FindOrCreateUiRaycastCamera() { ... }

    // UIRaycastCamera を廃棄する
    private void CleanUpUiRaycastCamera() { ... }

    // ポインターが pointers ディクショナリに登録されているかどうかを返す
    public bool IsPointerRegistered(IMixedRealityPointer pointer) { ... }

    // pointers ディクショナリにポインターを登録する
    public bool RegisterPointer(IMixedRealityPointer pointer) { ... }

    // すべてのポインターをメディエイター・ pointers ディクショナリに登録する
    // 初期化時および入力ソースが検出されたときに呼ばれる
    private void RegisterPointers(IMixedRealityInputSource inputSource) { ... }

    // ポインターの登録を解除する
    public bool UnregisterPointer(IMixedRealityPointer pointer) { ... }

    // T型のポインターのコレクションを取得する
    public IEnumerable<T> GetPointers<T>() where T : class, IMixedRealityPointer { ... }

    // プライマリーポインターが変更したときに通知をするようサブスクライブする
    public void SubscribeToPrimaryPointerChanged(PrimaryPointerChangedHandler handler,
        bool invokeHandlerWithCurrentPointer) { ... }

    // プライマリーポインター変更通知のサブスクライブを解除する
    public void UnsubscribeFromPrimaryPointerChanged(PrimaryPointerChangedHandler handler) { ... }

    // 指定されたポインティング入力ソースの登録済み PointerData を返す
    private bool TryGetPointerData(IMixedRealityPointer pointer, out PointerData data) { ... }

    // すべてのポインターを更新する
    private void UpdatePointers() { ... }

    // ポインターを更新する
    private void UpdatePointer(PointerData pointerData) 
    {
        // ポインターのOnPreSceneQuery関数を呼び出す。これにより、レイキャストの準備をする機会が与えられる
        pointerData.Pointer.OnPreSceneQuery();

        // 略...

        // ポインターがロックされている場合は、フォーカスされているオブジェクトをそのままにする
        // これにより、ポインターがオブジェクトを指していない場合でも、これらのオブジェクトでイベントを確実に実行できる
        if (pointerData.Pointer.IsFocusLocked && pointerData.Pointer.IsTargetPositionLockedOnFocusLock)
        {
            pointerData.UpdateFocusLockedHit();

            // 略...
        }
        else
        {
            // 略...

            // フォーカスされているオブジェクトを特定する
            QueryScene(pointerData.Pointer, raycastProvider, prioritizedLayerMasks, hitResult3d, maxQuerySceneResults, focusIndividualCompoundCollider);

            // 略...

            // ここでのみヒット結果を適用して、現在のターゲットの変更がフレームごとに1回だけ検出されるようにする
            pointerData.UpdateHit(hit);

            // 略...
        }
        // 略...

        // ポインターのOnPostSceneQuery関数を呼び出す。これにより、レイキャストの結果に応答する機会が与えられる
        pointerData.Pointer.OnPostSceneQuery();
    }

    // terminus をオブジェクトがヒットした位置に書き換えてRayStepを更新している
    // (名称からも長く伸びているレイをオブジェクトとヒットした位置まで切り詰めるというイメージだと思う)
    private void TruncatePointerRayToHit(IMixedRealityPointer pointer, PointerHitResult hit) { ... }

    // 優先度の高いヒット結果を取得する
    private PointerHitResult GetPrioritizedHitResult(PointerHitResult hit1, PointerHitResult hit2,
        LayerMask[] prioritizedLayerMasks) { ... }

    // アクティブなポインターを整頓するために非アクティブなポインターを無効にする
    private void ReconcilePointers()
    {
        // 略...

        // ゲイズポインターのステートマシンを更新する(ゲイズポインターを有効にするか無効にするかを設定する)
        gazePointerStateMachine.UpdateState() { ... }

        // 略...
    }

    #region Physics Raycasting

    // 球のオーバーラップ結果を保存するために使用されるコライダー
    private static Collider[] colliders = null;

    // シーンクエリを実行して、コライダーを備えたどのシーンオブジェクトが現在注視されているかを特定する
    private static void QueryScene(IMixedRealityPointer pointer, IMixedRealityRaycastProvider raycastProvider,
        LayerMask[] prioritizedLayerMasks, PointerHitResult hit, int maxQuerySceneResults,
        bool focusIndividualCompoundCollider)
    {
        // ポインターがヒットしているオブジェクトを見つける(フォーカスしているオブジェクトを特定する)処理
    }

    #endregion Physics Raycasting

    #region uGUI Graphics Raycasting

    // Unity グラフィックレイキャストを実行して、現在どの uGUI 要素がポイントされているかを特定する
    private void RaycastGraphics(IMixedRealityPointer pointer, PointerEventData graphicEventData,
        LayerMask[] prioritizedLayerMasks, PointerHitResult hit)
    {
        // ポインターがヒットしているuGUI要素を見つける(フォーカスしているUIオブジェクトを特定する)処理
    }

    // 単一のグラフィック RayStep をレイキャストする
    private bool RaycastGraphicsStep(PointerEventData graphicEventData, RayStep step, LayerMask[] prioritizedLayerMasks,
        out RaycastResult uiRaycastResult) { ... }

    #endregion uGUI Graphics Raycasting

    // 必要に応じて、フォーカスイベントを入力マネージャーに発生させる
    private void UpdateFocusedObjects()
    {
        // MixedRealityInputSystem の RaiseFocusEnter や RaiseFocusExit メソッド等が呼ばれ、
        // そして ExecuteEvents.ExecuteHierarchy(eventTarget, focusEventData, eventHandler) がよればイベントを実行
        // 最終的にターゲットオブジェクト(フォーカスしているオブジェクト)の OnFocusEnter / OnFocusExit メソッド等が呼ばれる

        // ★つまりフォーカスイベントの発生源がここ★
    }

    #endregion Utilities

    #region ISourceState Implementation

    // ソースが検出されたときにポインターを登録する
    public void OnSourceDetected(SourceStateEventData eventData) { ... }

    // ソースの検出が失われたときにポインターの登録を解除する
    public void OnSourceLost(SourceStateEventData eventData) { ... }

    #endregion ISourceState Implementation

    #region IMixedRealitySpeechHandler Implementation

    // 「select」の音声が認識されると、目または頭に基づくゲイズカーソルを再アクティブ化するフラグを立てる
    public void OnSpeechKeywordRecognized(SpeechEventData eventData) { ... }

    #endregion

    #region IPointerPreferences Implementation

    private List<PointerPreferences> customPointerBehaviors = new List<PointerPreferences>();

    // ポインターの動作(ビヘイビアー)を取得する
    public PointerBehavior GetPointerBehavior(IMixedRealityPointer pointer) { ... }
    // 指定されたポインタ型の動作(ビヘイビアー)を取得する
    public PointerBehavior GetPointerBehavior<T>(Handedness handedness, InputSourceType sourceType)
        where T : class, IMixedRealityPointer { ... }
    private PointerBehavior GetPointerBehavior(Type type, Handedness handedness, InputSourceType sourceType) { ... }

    // ポインターの動作(ビヘイビアー)のプロパティ
    public PointerBehavior GazePointerBehavior { get; set; } = PointerBehavior.Default;

    // ポインターの動作(ビヘイビアー)を設定する(PointerUtils から呼ばれる)
    // PointerUtilsから呼ばれてポインターの有効・無効の切り替えを行っている
    public void SetPointerBehavior<T>(Handedness handedness, InputSourceType inputType, PointerBehavior pointerBehavior)
        where T : class, IMixedRealityPointer { ... }

    // ポインター設定クラス(ソースのタイプ、ポインターのタイプ、手に応じたポインタービヘイビアーを持つ)
    private class PointerPreferences { ... }

    #endregion
}

分かりにくいと感じた部分についての補足

  • フォーカスがロックされている状態とは?

    • ざっくりとは、何かしらのオブジェクトにフォーカスが当たった状態でクリック(エアタップ)していればロックするもの

    もう少し詳しく書くと

    • RaisePointerDown で IMixedRealityPointerHandler インタフェースを実装したコンポーネントを付けたオブジェクトにフォーカスが当たっている場合、IsFocusLocked = true に設定される
    • RaisePointerUp で IsFocusLocked = false に設定される

    • GGVPointer は OnInputDown 時にオブジェクトにフォーカスが当たっていれば IsFocusLocked = true そうでなければ false に設定される

    • GGVPointer は OnInputUp 時に IsFocusLocked = false に設定される

    ロックされている間は、ポインターがオブジェクトを指していない場合でもフォーカスされているものとして扱うためのもので、ポインターが当たっていないオブジェクトでもフォーカスイベントを確実に実行するための仕組み。

  • プライマリーポインターとは?

  • RayStep とは?

    • CurvePointerを生成するときに以下のように生成されています。

      protected int LineCastResolution = 10;

      Rays = new RayStep[LineCastResolution];

    • CurvePointer 以外の LinePointer や PokePointer や SpherePointer では 要素は1つしか持っていません。

      Rays = new RayStep[1];

    • GenericPointer も以下の通り要素は1つです。

      public RayStep[] Rays { get; protected set; } = { new RayStep(Vector3.zero, Vector3.forward) };

    つまり「レイを何分割するか」というもので、カーブを描いていなければ基本的に1直線なので1つのレイと考えればよさそうです。

  • TruncatePointerRayToHit メソッドは何をしているか?

    一番優先度が高いレイヤーのオブジェクトとヒットしている場合は(それ以上先にレイが伸びている必要がないので)レイの終点をこのヒット位置まで切り詰める(ということだと思う)。

その他ポイント

  • MixedRealityInputSystemProfile で Focus Provider Type として FocusProvider が設定されている

  • GazeProviderは特殊扱いする(登録されたポインタでなくても追跡する)

    • StabilizationPlaneModifierおよびユーザーがどこを見ているかを気にする可能性のある他のコンポーネントのために、フォーカスに視線が使用されていない場合でも、視線レイキャストを行う必要がある

まとめ

FocusProvider の実装について確認しました。

概要については冒頭に書いた通りです。

今後も入力周りを中心に、(自分のメモとして)徐々に内部の実装について確認していきたいと思います。

なお、FocusProvider は以下の通り MRTK の中ではライン数がベスト 10 に入るヘビー級のクラスです。