GitHub 1年チャレンジ
ポエムというかただのメモです(せっかくなのでリンクはアフェリエイトになっているので、踏みたくない方はご注意ください)。
2020年4月から、毎日(最低)25分、業務とはあまり関係ないプログラミングをして、GitHubにプッシュするという個人的なチャレンジをしていました。ちょうど1年たって穴をあけることなく目標達成したのでメモとして残しておきます。
なお、ほとんどが 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 を始めるならまず初めにこちらを呼んでおくのが良いと思います。
Node.js デザインパターン
JavaScript, TypeScript に興味を持ち始めたのでその勢いのまま Node.js にも手を出してみました。知識がまだ十分に追いついておらずもう一度読み直そうと思っていますが、これも買ってよかったと思える1冊です。
JavaScript グラフィックスプログラミング入門
丁寧にかかれているので JavaScript の勉強としても良い本だと思います。Typescript に書き換えながら実装していました(たまにany使ったりしながら...)。
2020/10
いまどきのJSプログラマーのための Node.jsとReactアプリケーション開発テクニック
少し古めの本なので、積極的にお勧めはしずらいですが、そのあたりをカバーできる程度の基礎知識が身についているのであれば、参考になる内容も多いと思います。
りあクトⅠ.React言語・環境編
りあクトⅡ.React基礎編
りあクトⅢ.React応用編
りあクト TypeScriptで極める現場のReact開発
React に関してはこれらの本が個人的には一番お勧めです。
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++で簡単なシューティングゲームのようなものが作れるようになります。
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 の紹介
製品紹介の動画が流されていました。興味のある方はすでに見たことがあるかもしれませんが、こちらは動画を見ていただくのが一番分かりやすいと思います。
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)
HoloLens 2 アプリの作成方法 + UX Tools
プロジェクトの初期設定から始まり、UX Tools のインポート方法やキューブをハンド操作できるものをストリーミングで行うところまで説明されています。
この内容とほぼ同様のことができる状態のものをテンプレートとして用意しているので、興味があればお試しください。
またこちらの動画でもほぼ同じ内容を「日本語で」説明しています。
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
UXTools を利用したハンドメニューの作成方法
UX Tools を利用してハンドメニューを実装する方法が1から説明されておりとても参考になります。 また実機へのデプロイ方法も最後に説明されています。
MR アプリにおけるベストプラクティス
パフォーマンスを考慮して以下の内容が説明されています。 とても参考になる内容のため、別途ブログにでも書きたいと思います。
- Project Settings
- Debugging/Profiling
- Tips
以上、現時点において UX Tools の使い方やパフォーマンスに関する内容はとても貴重なものであると思いますので興味のある方はご覧になられると良いと思います。
UE4 HoloLens Template
UE4 で HoloLes 用のプロジェクトを作成する場合、最初にプラグインを追加したりといくつか設定が必要になります。毎回これを行うのは大変なのでテンプレート化し、プロジェクト作成時に選択できるようにする方法をまとめてみました。
テンプレートとなるプロジェクト作成
まず最初に、テンプレートにするためのプロジェクトを作成します。
プロジェクト名については、すでに 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 のコンポーネントも利用可能な状態になっています。
テンプレートは以下で公開しています。 1drv.ms
こういった便利なテンプレートはおそらくそのうち公式から公開されるとは思いますが、それまでのつなぎとして利用されると良いと思います。
参考サイト
UNREAL FEST EXTREME 2020 WINTER
UNREAL FEST EXTREME 2020 WINTER で登壇しました。 事前に録画したものを配信するものでしたが、質疑応答にはライブ配信で対応をしました。
本日のアンリアルフェスのNON-GAME講演は株式会社ホロラボ(@HoloLabInc)の加藤さま(@hi_rom_)による
— アンリアルエンジン (@UnrealEngineJP) 2020年11月18日
「MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発」です
こちらもライブによる質疑応答ありです!https://t.co/NthGBxxlOk #ue4 #ue4fest #HoloLens #MRTK pic.twitter.com/RKv9x9n2Sh
動画は以下で公開されています。
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
UE4 を利用した HoloLens 2 アプリ開発に興味がある方は是非ご参照ください。
MRTK v2.5.0 Examples 一覧(Experimental編)
本記事は、以下記事の続きで、MRTK v2.5.0 に含まれている Examples の Experimental シーンの説明パネルのキャプチャと概要を記載したものです。
MRTK Examples
Experimental_CameraOffset
CameraOffsetTest
CameraOffsetInputSimulationProfile を試すシーンのようです。
説明がないので詳しいことはわかりませんが Oculus Quest 2 など別のデバイスで必要となるカメラオフセット調整用でしょうか。
Experimental_ColorPicker
ColorPickerExample
実行時にさまざまなマテリアルのカラー値を変更するカラーピッカーと対話するためのいくつかの異なる方法を示したサンプル。
Experimental_Dialog
DialogExample
ダイアログコントロールの使用法を示したサンプル。
Experimental_Dock
DockExample
ドックのサンプル。このコントロールを使用すると、オブジェクトを所定の位置に出し入れして、パレット、シェルフ、およびナビゲーションバーを作成できる。
Experimental_Dwell
DwellExample
Dwell のサンプル。項目に視線を合わせ、見つめ続けることでトリガーするコントロール。
Experimental_Elastic
ElasticSystemExamples
弾性システムのサンプル。MRTK 内で動的、応答性、および手続き型のフィードバックとアニメーションを可能にする。
Experimental_ExamplesHub
MRTKExamplesHub
MRTKExamplesHubMainMenu の親シーン。
MRTKExamplesHubMainMenu
MRTK の様々なサンプルを確認することができるシーン。
複数のシーンを切り替えるための Scene System の参考にもなる。 詳細については MRTK Examples Hub (日本語)参照。
Experimental_HandCoach
HandCoachExample
体験で使用できるさまざまなハンドコーチの例を示したサンプル。
Experimental_HandMenuLayout
HandMenuLayoutExamples
ハンドメニューレイアウトの例をいくつか示したサンプル。
Experimental_Joystick
JoystickExample
ジョイスティックのサンプル。
ジョイスティックを使用して大きなオブジェクトを移動、拡大縮小、回転する方法を示している。また、ジョイスティックの動作を微調整するスライダーが下にある。
Experimental_MixedRealityKeyboard
MixedRealityKeyboardExample
OS標準のキーボードを表示するサンプル。
Unity エディター上ではキーボードは表示されないが、物理キーボードで入力は可能。
Experimental_NonNativeKeyboard
NonNativeKeyboardExample
OS標準ではない独自のキーボードを表示するサンプル。
Unity エディター上でも表示される。
Experimental_PulseShader
PulseShaderExamples
ハンドメッシュと空間メッシュの視覚化にパルスシェーダーを使用する方法を示したサンプル。
Experimental_RiggeHandVisualizer
RiggedHandVisualizer
SkinnedMeshを制御して手を視覚化するサンプル。(詳細)
Experimental_Solvers
DirectionalIndicatorExample
方向インジケーターの使用法を示したサンプル。
FollowSolverExample
Follow ソルバー(追跡対象のターゲットの前に要素を配置するソルバー)のサンプル。
MRTK v2.5.0 Examples 一覧(Demos編)
MRTK v2.5.0 に含まれている Examples の Demos シーンについて確認したので、 それぞれの説明パネルのキャプチャと概要を記載しました。ボリュームが多かったので Experimental のシーンについては別記事でまとめています。
MRTK v2.5.0 の更新内容については、リリースノート をご参照ください。
確認環境
- Unity 2019.11f1 (LTS)
- MRTK v2.5.0
MRTK Examples
MRTK v2.5.0 では UPM(Unity Package Manager) を利用した場合 Examples を個別にインストールする必要があります。
UPM によるインストールについては、以下を参照ください。
Mixed Reality Toolkit and Unity Package Manager
以降、各シーンの概要です。
Demos_Audio
AudioLoFiEffectExamples
ローファイオーディオのサンプル。球をタップすることで3種類の効果を確認できる。
AudioOcclusionExamples
音の遮蔽を確認することができるサンプル。壁の前と後ろで音の聞こえ方が変わる。
Demos_Boundary
BoundaryVisualizationExample
境界を視覚化するサンプル(VR向け機能)。
Demos_Diagnostics
DiagnosticsDemo
診断システムを使用して実行時のアプリケーションパフォーマンスを確認するサンプル。
Demos_EyeTracking
EyeTrackingDemo-00-RootScene
アイトラッキングの他のサンプル(02~05)の親シーン。
EyeTrackingDemo-01-BasicSetup
アイトラッキングを行うための最小設定のシーン。
EyeTrackingDemo-02-TargetSelection
アイゲイズと選択(エアタップまたは音声コマンド)で宝石を破壊するサンプル。
EyeTrackingDemo-03-Navigation
アイゲイズとスクロール、パン、ズームイン/アウトのサンプル。
EyeTrackingDemo-04-TargetPositioning
目とハンドまたはボイスによるオブジェクトの選択、移動を行うサンプル。
EyeTrackingDemo-05-Visualizer
アイトラッキングデータをビジュアライズするサンプル。
Demos_Gltf
Glb-Loading-Demo
ネット経由で Glb ファイルを読み込むサンプル。
Gltf-Loading-Demo
StreamingAssets にある Gltf ファイルを読み込むサンプル。
予め StreamingAssets 配下に gltfファイルを準備しておくか、または以下の [Copy models to StreamingAssets] ボタンを押してモデルを取得しておく必要がある。
Demos_HandTracking
HandInteractionExamples
プレス、タッチ、グラブ、スクロール、移動、回転、スケールなど、さまざまなタイプのハンドトラッキングインタラクションを示したサンプル。
HandInteractionGestureEventsExample
IMixedRealityGestureHandler を使用したさまざまなタイプのジェスチャーイベント(Select, Hold, Manipulation, Navigation)を示したサンプル。
HandInteractionRecordArticulatedHandPose
手のポーズ(位置・回転)を記録するサンプル。Record Right Hand、もしくは Record Left Hand のボイスコマンドで記録を開始し、Stop ボイスコマンドで記録を停止する。Console に記録内容が json で表示される。
HandInteractionTouchablesExample
さまざまなタッチ可能なコンポーネントと、それらがさまざまなコライダーとどのようにインタラクションするかを示したサンプル。
HandMenuExamples
ハンドメニューを表示するサンプル(追跡されたオブジェクトを手で拘束されたコンテンツの安全な領域に拘束するソルバーである HandConstraint の使用方法を示している)。ハンドメニューを表示するには、手を上げて手のひらを見る必要がある。
LeapMotionHandTrackingExample
Leap Motion によるハンドトラッキングのサンプル。本サンプルを試すには、Leap Motion Controller が必要。
NearMenuExamples
様々なメニューのサンプル。ピン止めしたり、掴んで移動したりできる。
Demos_Input
DictationExample
音声を録音し、音声のテキスト化を行うことができるサンプル。日本語を表示する場合は日本語のフォントを入れる必要がある。
DisablePointersExample
PointerUtils の利用方法を示したサンプル。
コードから Hand Ray、Gaze、Grab、Poke などのポインターをオンオフすることができ、 HoloLens 1 と 2 などのスタイルのインタラクションを切り替えることができる。
InputActionsExample
異なるソースからの入力をユーザー定義の入力アクションにマッピングし、単一のリスナーを介してそれを処理する方法を示したサンプル。
回転可能なオブジェクトにフォーカスを当てた状態で、以下のアクションのいずれかを行うと Cube が回転する。
- Xbox コントローラの B ボタンを押す
- Rotate と言う
- Hold ジェスチャーを実行する
InputDataExample
手、頭、目、モーションコントローラーの位置・ポジションなどの入力データにアクセスする方法を示したサンプル。
PointerResultExample
ポインタのヒット位置を利用して、ポインタクリック時にオブジェクトを生成する方法を示したサンプル。
PrimaryPointerExample
プライマリポインタ変更イベントを使用してプライマリポインタを追跡する方法を示したサンプル。
感覚を掴むために、異なるコントローラを使用してチーズを選択して操作したり、コントローラを切断したり、トラッキングロスを強制したりして遊ぶと良いらしい。
SpeechInputExamples
音声コマンドの構成および使用する方法を示したサンプル。
Open, Close, Change Color, (Select) の音声コマンドが登録されており、音声コマンド発声時にフォーカスが必要な Local Speech Handler と フォーカスの必要がない Global Speech Handler の例が示されている。
Demos_ScrollingObjectCollection
ScrollingObjectCollection
ScrolingObjectCollection コンポーネントを使用したさまざまなタイプのスクロールインタラクションを示したサンプル。
Demos_Solvers
SolverExamples
ソルバーのサンプル。
ソルバーは、あらかじめ定義されたアルゴリズムに従ってオブジェクトの位置と向きを計算する手段を容易にするコンポーネント。
SurfaceMagnetismSpatialAwarenessExample
Spatial Awarenessを使ってSurface Magnetism Solver の使用を示したサンプル。
現実空間の壁の表面にオブジェクトがスナップして、ポインタに追従する。
TapToPlaceExample
タップしてオブジェクトを配置するサンプル。
タップして配置すると、オブジェクトがサーフェスに配置される。 サーフェスが検出されない場合、オブジェクトは、ソルバーハンドラーにあるTrackedTargetType(ヘッド、コントローラーレイ、ハンドジョイント)を基準にしたデフォルトの距離に配置される。
Demos_SpatialAwareness
SpatialAwarenessMeshDemo
空間認識のサンプル。現実環境をワイヤーフレームメッシュで表示する。
球のクリックで環境認識のオブザーバーの動作を停止・再開できる。
Demos_StandardShader
ClippingExamples
Mixed Reality Toolkit/Standard シェーダをクリッピングプリミティブ(ClippingPlane、ClippingSphere、およびClippingBox)と組み合わせて使用して、メッシュ上のピクセルを動的にクリップする方法を示したサンプル。
HoverLightExamples
Mixed Reality Toolkit/Standard シェーダと HoverLights を組み合わせて使用し、メッシュをダイナミックにライトする方法を示したサンプル。
MaterialGallery
Mixed Reality Toolkit/Standard シェーダで可能なマテリアルとシェーディング手法の例を示したサンプル。
OutlineExamples
Mixed Reality Toolkit/Standard シェーダを MeshOutline、MeshOutlineHierarchy、および MeshSmoother コンポーネントと組み合わせて、さまざまな手法を使用してメッシュレンダラーの輪郭を描く方法を示したサンプル。
StandardMaterialComparison
Unity/Standard シェーダと Mixed Reality Toolkit/Standard シェーダを比較したサンプル。
StandardMaterials
Mixed Reality Toolkit によって提供される標準的なマテリアルを示したサンプル。
Demos_Utilities
MixedRealityCapabilityDemo
プラットフォームとデータプロバイダーによってサポートされる Capability の状況を表示するサンプル。
Demos_UX
BoundingBox
BoundingBoxExamples
様々なタイプのバウンディングボックスのサンプル。
BoundingBox コンポーネントは deprecated となっているので注意。今後は BoundsControl を利用する。
BoundingBoxRuntimeExample
実行時にバウンディングボックスを作成し、さまざまなバウンディングボックスのプロパティを変更する方法を示したサンプル。
BoundingBox コンポーネントは deprecated となっているので注意。今後は BoundsControl を利用する。
BoundsControl
BoundsControlExamples
様々なタイプのバウンディングボックスのサンプル。
BoundsControlRuntimeExample
実行時にバウンディングボックスを作成し、さまざまなバウンディングボックスのプロパティを変更する方法を示したサンプル。
Collections
ObjectCollectionExamples
オブジェクトコレクションのサンプル。
オブジェクトコレクションは、あらかじめ定義された3次元形状のオブジェクトの配列をレイアウトするのに役立つレイアウトコントロール。
Interactables
InteractablesExamples
Interactable コンポーネントのサンプル。
Interactableは、ボタンやUIコントロールなどのインタラクティブなコンテンツを構築するための基本コンポーネントであり、インタラクティブな状態に基づいてフィードバックを提供する。
Lines
LineExamples
ライン描画のサンプル。ラインは Data Provider と Renderer の2つのコンポーネントから成る。
ManipulationHandler
ManipulationHandlerExample
ManipulationHandler のサンプル。 ManipulationHandler コンポーネントは deprecated となっているので注意。今後は ObjectManipulator を利用する。
球が操作でき、 壁にぶつかると、インタラクションが停止する。
ObjectManipulator
ObjectManipulatorExample
ObjectManipulator のサンプル。
球が操作でき、 壁にぶつかると、インタラクションが停止する。
ObjectManipulatorExamplePhysics
ObjectManipulator の物理機能をテストするためのサンプル。
PressableButton
PressableButtonExample
PressableButton のさまざまなカスタマイズ例を示したサンプル。
ProgressIndicator
ProgressIndicatorExamples
進行状況インジケーターのサンプル。
Slate
SlateExample
Slate プレハブでの HandInteractionPanZoom スクリプトの使用を示したサンプル。
Slider
SliderExample
ピンチスライダーコントロールの使用方法を示したサンプル。
Text
TextPrefabExamples
テキストプレハブのサンプル。
適切なスケーリング値と Text3DShader によるオクルージョンサポートにより、品質が最適化されている。
Tooltips
TooltipExamples
ツールチップのサンプル。
MRTKのMixedRealityInputSystemの実装確認
以前の調べ物から一か月近く立ってしまいましたが、今回は入力に関する大本とも言える MixedRealityInputSystem について調べてみました(おかしなことを書いていたら教えてください)。
確認した MRTK のバージョンは v2.4.0 です。なお、今のタイミングでこのような調べ物をしているのは MRTK がかなり安定してきており v.2.5.0 だろうが v.2.6.0 だろうが大きく変わることはないと考えているためです(HoloToolKit → MRTK になったような破壊的変更が無い限りは問題ないはず)。
さっそく継承関係から見ていきます。
継承関係
以下のインタフェース、クラスについては、こちら で確認済みのものになります。
- 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 リストに登録
しています。
次にポイントとなるのは、イベントハンドリングを行う Handle〇〇メソッドの部分になると思います。以下4つあります。
- HandleEvent
- HandlePointerEvent
- HandleFocusChangedEvents
- HandleFocusEvent
Handle〇〇メソッドが主に行っているのはイベントのディスパッチ処理で、イベントディスパッチメソッドは以下3つあります。
- DispatchEventToGlobalListeners : グローバルリスナーにイベントをディスパッチ
- DispatchEventToObjectFocusedByPointer : ポインターにフォーカスされたオブジェクトにイベントをディスパッチ
- DispatchEventToFallbackHandlers : フォールバック処理(上記のどちらも行われなかった場合に行う処理)
グローバルリスナーとはオブジェクトにフォーカスしていない状態でイベントを実行するためのものになります。(例えば、何もフォーカスしていない状態でもエアタップしたらCubeを表示したい、といった場合などに利用する)
Handle〇〇メソッドは(大量にある)Raise△△メソッドから呼ばれています。
- RaiseSourceDetected
- RaiseSourceLost
- RaiseSourceTrackingStateChanged
- etc ...
イベント処理に関しては、すべて見ても重複する内容が多いので、例として ポインターをクリックした際に呼ばれる RaisePointerClicked メソッドについて確認をしてみます。
この RaisePointerClicked 自体は、ソリューションを検索をするとわかりますが何かしらのポインターから呼ばれるもので、 必要な情報が引数で渡ってきます。
// まず 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); } }
流れを追うと
- クリック(エアタップ)をすると RaisePointerClicked メソッドが何かしらのポインターから呼ばれる
- HandlePointerEvent メソッドに何かしらのデータと実行させたいメソッド(ハンドラー)を渡して呼ぶ
- グローバルリスナーにデータとハンドラーを送信する(イベントをディスパッチする)
- eventHandler.Invoke((T)handlerEntry.handler, eventData); が呼ばれ、渡されたメソッドが実行される(詳細については BaseEventSystem の HandleEvent 参照)
- ポインターにフォーカスされたオブジェクトにデータとハンドラーを送信する(イベントをディスパッチする)
- ExecuteEvents.ExecuteHierarchy(focusedObject, baseInputEventData, eventHandler); が呼ばれ、渡されたメソッドが実行される
- 上記処理が行われなかった場合(イベントデータのusedフラグがfalseの場合)はフォールバック処理が行われる
という形になります。
上の流れの中から、最終的には OnInputClickedEventHandler の中の
handler.OnPointerClicked(casted);
という部分が呼ばれることになりますが、これにより開発者が独自に定義した部分が呼ばれ、またその際にイベントデータがパラメータとして渡ってきます。
イベントデータに関しては以下のような継承関係になっています。
詳細の説明については省略しますが、ポインターのクリックに関するイベントは以下で定義されているものになります。
private MixedRealityPointerEventData pointerEventData;
この pointerEventData は RaisePointerClicked メソッドが呼ばれたときに以下の部分で生成されています。
// インプットイベントデータを生成する
pointerEventData.Initialize(pointer, inputAction, handedness, inputSource, count);
このデータがパラメータとして渡って来るので必要があれば利用することができます。 どのようなデータが渡ってきているのかは以下のように実際に見てみるのが早いでしょう。
これらのイベント処理の確認として簡単なサンプルを以下に示します。
例えば以下のようなクラスを実装して 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.23 ~ Unity 2020.1.0のリリースノートです。
HoloLensのキーワードが入っていなくても影響する改善はあるかもしれないのであくまで参考までに。
- XR: Fixed head-tracking and rendering for in-editor remoting to HoloLens devices (1138473, 1140716)
- 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)
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)
- XR: Prevent Hololens app pause when switching focus to a 2D view within a running application.
- XR: Fixed a memory issue in HoloLens SurfaceObserver. (1176047, 1176063)
- XR: Fixed a instability issues with Depth based LSR for HoloLens V1 devices. (1174865, 1182448)
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)
- XR: Fixed an issue that would cause the Editor Hololens Remote device to improperly set before connecting.
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.
- XR: Fixed Hololens 2 pausing when UserPresence is lost with RunInBackground enabled. (1217943)
- XR: Fix crash when you connect to a Hololens 2 using the HolographicRemoting scripting api and then enable Windows Mixed Reality.
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)
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.
なお、以下は調べようと思ったきっかけです。
InputField は特に設定の必要なしに配置したものを押すだけでキーボード出てくるはずだよなーと思ったけど、出てこなくて結構時間がかかってしまった。結果としては、Unity 2018.4.23f1 だと出てこなくて Unity 2019.4.5f1 だと出てきた。対応するようになったのどのverからだろう? #HoloLens2
— 広務(Hiromu) (@hi_rom_) 2020年8月3日
Bolt で HoloLens アプリ開発
先日、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 という項目が追加されるので選択します。
以下の画面が出たので Api Compatibility Level を .NET Standard 2.0 にしたら動かないかもしれません(未確認)。ここでは素直に .NET 4.x で進めることにします。
Setup Wizard が表示されますがすべてデフォルト(およびお好みの設定)で進めます。
Assembly Options の項目については、MRTK の機能を Bolt で使う場合には必要になるものだと思いますが、こちらも後から設定ができるので変更なしで進めます。
サンプルプロジェクトの作成
キューブを Bolt により回転させるサンプルを作ります。
まずは Cube を配置します。
Cube に Flow Machine コンポーネントを追加します。
Flow Machine の New ボタンを押します。
エラーが表示されますが無視して続けます。
Flow Machine の Edit Graph ボタンを押します。
以下のような画面が表示されます。
右クリックをして Rotate で検索をして、Rotate(xAngle, yAngle, zAngle, relativeTo) を選択します。
Update と Rotate をマウスドラッグでつなぎます。また yAngle に 1 を設定します。
以上で Play ボタンを押すとキューブが回転していることが確認できます。
UnityEditor で動作することが確認できるので、実機にデプロイしてみます。
動きません。。
以下は、PC で実行したものですが以下のようなエラーが出ました。
調べたところ、AOT Pre-Build というものが必要なようです。メニューにあるので実行します。
再度ビルドしなおして、デプロイしてみます。
動きました!
はい、動いた。 #HoloLens2 #Bolt pic.twitter.com/7rNh83OtbL
— 広務(Hiromu) (@hi_rom_) 2020年7月24日
まとめ
Bolt を利用して HoloLens アプリを開発できることがわかりました。
まだあまり詳しく触ってはいませんが、MRTK の機能を Bolt から使うには Setup Wizard で表示された Assembly Options での設定が必要になると思います。MRTK は dll が機能ごとに分割されているのですべて登録するのは大変かもしれませんが、今後そのあたりも見てみたいと思います。
参考サイト
MRTKのFocusProvider実装確認
FocusProvider
以前の記事でポインターが FocusProvider と関係していることが分かったので、今回は FocusProvider について見ていくことにしました(確認したバージョンはMRTK v2.4.0)。
かなり長くなってしまったので最初に FocusProvider とはどのようなものかを簡単にまとめると
入力ソースを検出したときにソースに紐づいたポインターを FocusProvider 内に登録する
各ポインターがフォーカスしている(レイがヒットしている)オブジェクトの詳細を取得する
フォーカスしているオブジェクトに対して必要に応じてイベントを発生させる(OnFocusEnter / OnFocusExit など)
というものです。
詳細について特に興味がなければここまでで大丈夫だと思います。
以降はほとんど調べたことのメモという感じなので興味のある方だけご覧ください。
では、まず継承関係を見てみます。
継承関係
入り組んだ印象がありますが、順を追って見ていきます。
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 については理解しておく必要があります。
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 です。
言うまでもなく、上で書いてきたすべてのインタフェースの実装をしたもので、確認のために継承関係の図を再掲します。
また、FocusProvider は内部に PointerHitResult / PointerData / PointerPreferences クラスを持っています。PointerData クラスについては継承元があるので、継承関係を掲載します。
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 に入るヘビー級のクラスです。
MRTKのライン数の多いファイルベスト10 pic.twitter.com/tpiMVdvFyp
— 広務(Hiromu) (@hi_rom_) 2020年6月12日