SHOTech Blog

プログラミングに関する記録です

HoloLens2チュートリアルの補足メモ(後編)

この記事はHoloLens Advent Calendar 2020の23日目の記事となります。

qiita.com

この記事はMicrosoftが公開しているHoloLens2のチュートリアルに関する補足的な内容です。

前回のこの記事の続きです。

sy0690.hateblo.jp

ユーザー インターフェイスの作成

docs.microsoft.com

ここからはこのドキュメントに沿って進めます。

ボタンの静的パネルの作成

f:id:syota-y1989:20201222221149p:plain
ドキュメントの通りRoverExplorer オブジェクトの子オブジェクトに空のオブジェクトを追加し、そのオブジェクトの名前を 「Buttons」にリネームしTransformを変更する。その後Buttonsオブジェクトの子オブジェクトとして、PressableRoundButton オブジェクトを3つ作成する。

f:id:syota-y1989:20201222235331p:plain
ButtonsオブジェクトにGridObjectCollectionコンポーネントを追加して設定値を編集する(設定値はドキュメント参照) 。設定後Update Collectionをクリック

f:id:syota-y1989:20201222235713p:plain
f:id:syota-y1989:20201223000537p:plain
各PressableRoundButtonオブジェクトの名前を変えて、その子オブジェクトのTextMeshProのTextコンポーネントの値をオブジェクト名と同じにする。

f:id:syota-y1989:20201223001446g:plain
HintsオブジェクトのInteractableコンポーネント内にあるOnClick()イベントを設定します。

f:id:syota-y1989:20201223002900p:plain
f:id:syota-y1989:20201223004536p:plain
f:id:syota-y1989:20201223004951p:plain
OnClick()に設定したことによってボタンをクリックしたときに、RoverAssemblyオブジェクトのコンポーネントにあるPlacement Hints Controller(Script)に書かれたTogglePlacementHints関数が実行されます。
この関数は引数に設定されている下記5つのオブジェクトを表示したり非表示にしたりします。

  • Camera_PlacementHintオブジェクト
  • Generator_PlacementHintオブジェクト
  • Lights_PlacementHintオブジェクト
  • UHFAntenna_PlacementHintオブジェクト
  • Spectrometer_PlacementHintオブジェクト

ボタンのExplodeオブジェクトについてもドキュメントに沿って同じように設定します。

補足

f:id:syota-y1989:20201223005614p:plain
実行したときに出るこのデバック用の情報を表示しているものが邪魔なので消したいと思います。

f:id:syota-y1989:20201223005831p:plain
MixedRealityToolkitオブジェクトの設定項目の中にあるDiagnosticsのEnable Diagnostics SystemのチェックボックスをOFFにします。これで、以降は実行時に表示されなくなります。

f:id:syota-y1989:20201223010211g:plain
これで、Hintsボタンをクリックすればオブジェクトの表示非表示を切り替え、Explodeボタンをクリックすると分解ビューの切り替えができるようになりました。
今回使用したInteractableコンポーネントは入力に対するアクションを簡単に設定することが可能で、MRTKを使って開発するときは必須と言っていいほどよく使います。 基本的には操作したいオブジェクト側に操作をするための関数を設定しておきさえすれば、ボタンのコンポーネントにInteractableを設定し今回のようにしてするだけで実装可能です。

f:id:syota-y1989:20201223010812p:plain
もちろん複数のオブジェクトを操作したい場合はこの「+」をクリックして追加できます。

ユーザーに追従する動的メニューの作成

f:id:syota-y1989:20201223063125p:plain
ドキュメントに従ってNearMenu4x1 プレハブを追加しTransformを調整します。その後 SolverHandler コンポーネントのTracked Target TypeがHeadになっていることを確認し、RadialView コンポーネントの名前の横のチェックボックスをONにします。このチェックを入れることによって既定で有効になります。ついでに名前も 「Menu」に変更します。
f:id:syota-y1989:20201223122755g:plain
これでメニューがカメラに追従するようになりました。

f:id:syota-y1989:20201223063851p:plain
f:id:syota-y1989:20201223130827g:plain
f:id:syota-y1989:20201223121726p:plain
ButtonOneオブジェクトの名前を「Indicator」に変更し、ドキュメントに沿って設定します。
ボタンのIndicatorオブジェクトのButton Config Helper (Script) コンポーネントのOnClickイベントには青い矢印のIndicatorオブジェクトをD&Dでセットします。Sceneビューでメニューの一番左のメニュー表示が虫眼鏡のアイコンになっていて、その下にIndicatorというテキストが表示されていればOKです。
設定したGameObject.SetActive(bool)関数は対象のオブジェクトのアクティブ状態を操作するものです。引数のチェックボックスをONにすれば対象オブジェクトが表示され、OFFにすれば非表示にするというものです。

f:id:syota-y1989:20201223124658p:plain
f:id:syota-y1989:20201223130137p:plain
次は先ほどのメニューオブジェクトの子オブジェクトであるIndicatorではなく、青い矢印のオブジェクトであるIndicatorオブジェクトの設定を変更します。 IndicatorオブジェクトのInspectorの名前の横のチェックボックスをOFFにします。これでアプリ実行時は非アクティブな状態になります。そしてDirectional Indicator Controller (Script)コンポーネントを追加します。これによって、メニューのIndicatorボタンをクリックすると青い矢印が表示されるようになります。

f:id:syota-y1989:20201223131751g:plain
このように青い矢印はアプリ起動時は非アクティブなので、Roverが視界から外れても表示されませんが、メニューの Indicatorボタンをクリックすると表示されるようになります。

f:id:syota-y1989:20201223132635p:plain
f:id:syota-y1989:20201223134028g:plain
続いて2番目のボタンの名前を変更し、Button Config Helper (Script) コンポーネントの設定を変更していきます。

f:id:syota-y1989:20201223134920p:plain
RoverAssemblyオブジェクトのTap To Place (Script) コンポーネントの設定を変更します。 これによって、RoverAssemblyオブジェクトのTap To Placeコンポーネントはアプリ起動時は無効になり、メニューのTapToPlaceボタンをクリックした際に有効となります。さらにOn Placing Stopped () イベントに設定したことによって、Tap To Placeの処理が完了すると自動で無効になるようにできました。再度有効にするにはまたボタンをクリックすればよいです。
Tap To PlaceはHoloLensの環境認識によって床の部分に(表示はされないが)メッシュが張られそこをタップすると処理が起動します。Roverは基本的に環境認識によって作成された床の上に配置されます。

シーンへのテキストの追加

f:id:syota-y1989:20201223135821p:plain
ドキュメントに沿ってテキスト表示を追加します。

ヒントの追加

RoverParts オブジェクトの子オブジェクトをすべて選択し、ToolTipSpawner コンポーネントを追加してまとめて設定します。   その後各PartsオブジェクトのToolTipSpawner コンポーネントにあるTool Tip Textの値を変更します。

  • Camera_Partオブジェクト:Camera
  • Generator_Partオブジェクト: Generator
  • Lights_Partオブジェクト:Lights
  • UHFAntenna_Partオブジェクト:UHF Antenna field
  • Spectrometer_Partオブジェクト:Spectrometer

f:id:syota-y1989:20201223141648g:plain
このように視線のカーソルが当たるとツールチップが表示できるようになりました。

3D オブジェクトの操作

docs.microsoft.com

ここからはこのドキュメントに沿って進めます。

f:id:syota-y1989:20201223204953g:plain
RoverAssemblyオブジェクトとRoverParts オブジェクトの子オブジェクトの各パーツオブジェクトをすべて選択し、コンポーネントを追加します。

※Part Assembly Controller (Script) コンポーネント追加時に発生するエラーはいったん無視してよいです。

f:id:syota-y1989:20201223144616p:plain
コンポーネント追加後、 Object Manipulator (Script) コンポーネントのTwo Handed Manipulation Typeを「Move」と「Rotate」のみにチェックをいれます。これで、両手を使ったときに各パーツの移動と回転ができるようになりました。

f:id:syota-y1989:20201223145943p:plain
ドキュメントだと「Assets > MRTK > SDK > StandardAssets > Audio フォルダー」という順で書いてありますが、MRTK2.5だと「Assets > MRTK > StandardAssets > Audio フォルダー」にオーディオクリップがありますので注意してください。

先ほどと同じようにRoverAssemblyオブジェクトと各Partsオブジェクトをすべて選択してAudio Sourceコンポーネントを追加し、MRTK_Scale_StartをAudio Clipにアタッチします。
Spatial Blendは1に設定します。3D Sound Settingと合わせて設定することで3次元空間での音に対する影響度合い(聞こえ方)を設定するもです。ここではSpatial Blendのみ設定しておきます。

f:id:syota-y1989:20201223153535g:plain
RoverPartsの子オブジェクトであるCamera_PartオブジェクトのPart Assembly Controller (Script) コンポーネント内のLocation To Placeに、RoverModel_PlacementHints_XRayオブジェクトの子オブジェクトであるCamera_PlacementHintオブジェクトをアタッチします。
ここで実行されるPartAssemblyControllerスクリプトは2つのオブジェクトの距離をチェックしていて、一定範囲内に近づいたら引数で渡したオブジェクトの位置に固定するという処理を行います。

このように、先ほどCamera_Partオブジェクトのコンポーネントとして設定したPart Assembly Controller (Script)の場合は、Camera_Partオブジェクトと引数で渡したCamera_PlacementHintオブジェクトの距離をチェックしており、設定範囲内に近づいたら位置をCamera_PlacementHintオブジェクトの位置で固定します。

 private IEnumerator CheckPlacement()
        {
            while (true)
            {
                yield return new WaitForSeconds(0.01f);

                if (!isPlaced)
                {
                    if (Vector3.Distance(transform.position, locationToPlace.position) > MinDistance &&
                        Vector3.Distance(transform.position, locationToPlace.position) < MaxDistance)
                        SetPlacement();
                }
                else if (isPlaced)
                {
                    if (!(Vector3.Distance(transform.position, locationToPlace.position) > MinDistance)) continue;
                    var trans = transform;
                    trans.position = locationToPlace.position;
                    trans.rotation = locationToPlace.rotation;
                }
                else
                {
                    break;
                }
            }

Vector3.Distance関数が2点間の距離を出すもので、第一引数がCamera_Partオブジェクトの位置、第二引数がCamera_PlacementHintオブジェクトの位置です。これが一定の距離の範囲内になったらSetPlacement関数を実行します。

        private void SetPlacement()
        {
            if (isPunEnabled)
                OnSetPlacement?.Invoke();
            else
                Set();
        }

        /// <summary>
        ///     Parents the part to the assembly and places the part at the target location.
        /// </summary>
        public void Set()
        {
            // Update placement state
            isPlaced = true;

            // Play audio snapping sound
            if (hasAudioSource) audioSource.Play();

            // Disable ability to manipulate object
            foreach (var col in colliders) col.enabled = false;

            // Disable tool tips
            if (hasToolTip) toolTipSpawner.enabled = false;

            // Set parent and placement of object to target
            var trans = transform;
            trans.SetParent(locationToPlace.parent);
            trans.position = locationToPlace.position;
            trans.rotation = locationToPlace.rotation;
        }

このSet関数の一番最後の行でCamera_Partオブジェクトの位置をCamera_PlacementHintオブジェクトの位置で上書きして固定します。
残りのPartsについても同じようにドキュメントに沿って設定します。
※この設定をやることでPart Assembly Controller (Script) コンポーネント追加時に発生したエラーが解消されるはずです。

f:id:syota-y1989:20201223222959g:plain
設定が終わるとこのように各パーツをRoverにセットした後は、Rover本体を動かしても一緒に移動するようになります。

f:id:syota-y1989:20201223210950g:plain
ドキュメントに沿ってResetボタンの設定を行います。

        public void ResetPlacement()
        {
            foreach (var controller in partAssemblyControllers)
                if (isPunEnabled)
                    controller.OnResetPlacement?.Invoke();
                else
                    controller.Reset();
        }

ResetボタンをクリックするとこのPartAssemblyControllerスクリプトのResetPlacement関数が実行されます。
For文にあるpartAssemblyControllersというList型変数には下記のオブジェクトが含まれます。

  • UHFAntenna_Part
  • Lights_Part
  • RoverAssembly
  • Generator_Part
  • Camera_Part
  • Spectrometer_Part
            partAssemblyControllers = new List<PartAssemblyController>();
            foreach (var controller in FindObjectsOfType<PartAssemblyController>())
                partAssemblyControllers.Add(controller);

変数partAssemblyControllersはStart関数の中でこのようにPartAssemblyControllerスクリプトを持つオブジェクトを検索するFindObjectsOfTypeによって取得されListに追加されています。

        public void Reset()
        {
            // Update placement state
            isPlaced = false;

            // Enable ability to manipulate object
            foreach (var col in colliders) col.enabled = true;

            // Enable tool tips
            if (hasToolTip) toolTipSpawner.enabled = true;

            // Reset parent and placement of object
            var trans = transform;
            trans.SetParent(originalParent);
            trans.localPosition = originalPosition;
            trans.localRotation = originalRotation;
        }

変数partAssemblyControllersにセットされた各オブジェクトのTransformをアプリ起動時の初期位置で上書きすることで元の位置に戻します。

境界ボックスの追加

f:id:syota-y1989:20201223223758p:plain
ドキュメントに沿ってRoverExplorerオブジェクトに

この2つのコンポーネントを追加します。各コンポーネントの名前の横のチェックボックスをOFFにして、既定で無効にします。

f:id:syota-y1989:20201223224830g:plain
その後メニューのボタンの3つ目を「BoundingBox_Enable」に名前を変更し設定を変更していきます。
これで、BoundingBox_Enableボタンをクリックしたら、BoundingBox による境界線の表示とObject ManipulatorによるRoverExplorerオブジェクトの移動や回転などの操作が可能になりました。
同じようにドキュメントに沿って4つ目のボタンのBoundingBox_Disableも設定します。

f:id:syota-y1989:20201223230009g:plain
実装するとこのように動きます。

基本的な手を使った操作に関する開発はこれで完成です。
HoloLens2のチュートリアルのドキュメントではこの後、アイトラッキングと音声による入力方法が紹介されていますが、この記事ではまず最初に覚えるべき基礎となる手の操作に絞った補足をしているので今回は割愛します。機会があれば追加してまとめます。