注意

Mixed Reality Academy チュートリアルでは、HoloLens として設計された (第 1 世代) と混在の現実イマーシブ ヘッドセットに注意してください。The Mixed Reality Academy tutorials were designed with HoloLens (1st gen) and Mixed Reality Immersive Headsets in mind. そのため、これらのデバイス向けの開発にガイダンスがまだ必要な開発者のための場所でこれらのチュートリアルのままにすることが重要と思われます。As such, we feel it is important to leave these tutorials in place for developers who are still looking for guidance in developing for those devices. これらのチュートリアルは いない 最新のツールセットや相互作用が使用されている HoloLens 2 で更新されます。These tutorials will not be updated with the latest toolsets or interactions being used for HoloLens 2. サポートされているデバイスで作業を続行するが保持されます。They will be maintained to continue working on the supported devices. 一連の新しい HoloLens 2 を開発する方法を示すチュートリアルは、今後投稿があります。There will be a new series of tutorials that will be posted in the future that will demonstrate how to develop for HoloLens 2. この通知が投稿されるときにこれらのチュートリアルへのリンクが更新されます。This notice will be updated with a link to those tutorials when they are posted.


MR 240 の共有:複数の HoloLens デバイスMR Sharing 240: Multiple HoloLens devices

ホログラムには、場所に残りの領域で移動によって、世界でのプレゼンスが与えられます。Holograms are given presence in our world by remaining in place as we move about in space. HoloLens の場所でさまざまなを使用してホログラムを保持する座標系オブジェクトの向きと場所を追跡します。HoloLens keeps holograms in place by using various coordinate systems to keep track of the location and orientation of objects. 私たちは、デバイス間でこれらの座標システムを共有する場合、共有 holographic 世界に参加できるようにする共有のエクスペリエンスを作成できます。When we share these coordinate systems between devices, we can create a shared experience that allows us to take part in a shared holographic world.

このチュートリアルでご紹介します。In this tutorial, we will:

  • 共有のエクスペリエンスのためのネットワークをセットアップします。Setup a network for a shared experience.
  • HoloLens デバイス間でホログラムを共有します。Share holograms across HoloLens devices.
  • 共有の holographic 世界の他のユーザーを検出します。Discover other people in our shared holographic world.
  • -その他のプレーヤーの対象を何々 弾丸を起動できる共有の対話型エクスペリエンスを作成します。Create a shared interactive experience where you can target other players - and launch projectiles at them!

デバイスのサポートDevice support

コースCourse HoloLensHoloLens イマーシブ ヘッドセットImmersive headsets
MR 240 の共有:複数の HoloLens デバイスMR Sharing 240: Multiple HoloLens devices ✔️✔️

開始前の作業Before you start

前提条件Prerequisites

プロジェクト ファイルProject files

  • ダウンロード、ファイルプロジェクトに必要です。Download the files required by the project. Unity 2017.2 またはそれ以降が必要です。Requires Unity 2017.2 or later.
    • Unity 5.6 のサポートを引き続き必要がある場合を使用してくださいこのリリースします。If you still need Unity 5.6 support, please use this release.
    • Unity 5.5 のサポートを引き続き必要がある場合を使用してくださいこのリリースします。If you still need Unity 5.5 support, please use this release.
    • Unity 5.4 のサポートを引き続き必要がある場合を使用してくださいこのリリースします。If you still need Unity 5.4 support, please use this release.
  • 解除アーカイブをデスクトップまたは場所に到達する簡単なその他のファイル。Un-archive the files to your desktop or other easy to reach location. フォルダー名として保持SharedHologramsします。Keep the folder name as SharedHolograms.

注意

をダウンロードする前に、ソース コードを検索する場合があるGitHub で入手できますします。If you want to look through the source code before downloading, it's available on GitHub.

第 1 章 - Holo 世界Chapter 1 - Holo World

この章で、最初の Unity プロジェクトとビルド手順のセットアップがされプロセスを展開します。In this chapter, we'll setup our first Unity project and step through the build and deploy process.

目標Objectives

  • Holographic アプリを開発する Unity をセットアップします。Setup Unity to develop holographic apps.
  • ホログラムを参照してください。See your hologram!

手順Instructions

  • Unity を起動します。Start Unity.
  • [開く] を選択します。Select Open.
  • として場所を入力、 SharedHologramsフォルダーの以前にアーカイブ解除します。Enter location as the SharedHolograms folder you previously unarchived.
  • 選択プロジェクト名クリックフォルダーの選択Select Project Name and click Select Folder.
  • 階層を右クリックし、 Main Camera選択削除します。In the Hierarchy, right-click the Main Camera and select Delete.
  • HoloToolkit 共有-240/プレハブ/カメラフォルダー、検索、 Main Camera prefab します。In the HoloToolkit-Sharing-240/Prefabs/Camera folder, find the Main Camera prefab.
  • ドラッグ アンド ドロップ、 Main Cameraに、階層します。Drag and drop the Main Camera into the Hierarchy.
  • 階層、 をクリックして作成空アイテムの作成です。In the Hierarchy, click on Create and Create Empty.
  • 新しい右クリックしてGameObject選択の名前を変更します。Right-click the new GameObject and select Rename.
  • GameObject の名前を変更HologramCollectionします。Rename the GameObject to HologramCollection.
  • 選択、 HologramCollectionオブジェクト、階層します。Select the HologramCollection object in the Hierarchy.
  • インスペクター設定、位置を変換に。X:0、Y:-0.25、Z:2.In the Inspector set the transform position to: X: 0, Y: -0.25, Z: 2.
  • ホログラムフォルダーで、プロジェクト パネル、検索、 EnergyHub資産。In the Holograms folder in the Project panel, find the EnergyHub asset.
  • ドラッグ アンド ドロップ、 EnergyHubオブジェクトから、プロジェクト パネル階層として、 HologramCollection の子します。Drag and drop the EnergyHub object from the Project panel to the Hierarchy as a child of HologramCollection.
  • 選択ファイル > としてシーンを保存しています.Select File > Save Scene As...
  • 名前をシーンSharedHolograms をクリック保存します。Name the scene SharedHolograms and click Save.
  • キーを押して、再生ホログラムをプレビューする Unity でボタンをクリックします。Press the Play button in Unity to preview your holograms.
  • キーを押して再生をもう一度プレビュー モードを停止します。Press Play a second time to stop preview mode.

Visual Studio Unity からプロジェクトにエクスポートします。Export the project from Unity to Visual Studio

  • Unity の select でファイル > Build Settingsします。In Unity select File > Build Settings.
  • をクリックして開くシーンを追加シーンを追加します。Click Add Open Scenes to add the scene.
  • 選択ユニバーサル Windows プラットフォームで、プラットフォームを一覧表示し、クリックしてスイッチ プラットフォームします。Select Universal Windows Platform in the Platform list and click Switch Platform.
  • 設定SDKユニバーサル 10します。Set SDK to Universal 10.
  • 設定ターゲット デバイスHoloLensUWP ビルド タイプD3Dします。Set Target device to HoloLens and UWP Build Type to D3D.
  • 確認UnityC#プロジェクトします。Check Unity C# Projects.
  • [Build] をクリックします。Click Build.
  • ファイル エクスプ ローラー ウィンドウが表示されますが、作成、新しいフォルダー "App"という名前です。In the file explorer window that appears, create a New Folder named "App".
  • 1 回のクリック、アプリフォルダー。Single click the App folder.
  • キーを押してフォルダーを選択しますします。Press Select Folder.
  • Unity を完了すると、ファイル エクスプ ローラー ウィンドウが表示されます。When Unity is done, a File Explorer window will appear.
  • 開く、アプリフォルダー。Open the App folder.
  • 開いているSharedHolograms.sln Visual Studio を起動します。Open SharedHolograms.sln to launch Visual Studio.
  • デバッグからターゲットを変更する Visual Studio で、上部のツールバーを使用してリリースを ARM からX86します。Using the top toolbar in Visual Studio, change the target from Debug to Release and from ARM to X86.
  • ローカル コンピューターの横にあるドロップダウン矢印をクリックし、リモート デバイスします。Click on the drop-down arrow next to Local Machine, and select Remote Device.
    • 設定、アドレスHoloLens の IP アドレス、名前にします。Set the Address to the name or IP address of your HoloLens. デバイスの IP アドレスがわからない場合に参照設定 > ネットワークとインターネット > 詳細オプションCortana に質問してまたは 」「コルタナさん自分の IP アドレスは何ですか?If you do not know your device IP address, look in Settings > Network & Internet > Advanced Options or ask Cortana "Hey Cortana, What's my IP address?"
    • ままに、認証モード設定ユニバーサルします。Leave the Authentication Mode set to Universal.
    • クリックしてを選択しますClick Select
  • クリックしてデバッグ > デバッグなしで開始またはキーを押しますctrl キーを押しながら f5 キーを押してします。Click Debug > Start Without debugging or press Ctrl + F5. 最初に、デバイスに展開するには、する必要がありますVisual Studio をペアリングします。If this is the first time deploying to your device, you will need to pair it with Visual Studio.
  • EnergyHub ホログラム、HoloLens の配置を見つけてください。Put on your HoloLens and find the EnergyHub hologram.

第 2 章 – 相互作用Chapter 2 - Interaction

この章で、ホログラムを操作します。In this chapter, we'll interact with our holograms. 最初に、視覚化するためのカーソルが追加、視線します。First, we'll add a cursor to visualize our Gaze. 次に、追加しますジェスチャ手の形を使用して、ホログラムを領域に配置するとします。Then, we'll add Gestures and use our hand to place our holograms in space.

目標Objectives

  • カーソルを制御する入力視線の先を使用します。Use gaze input to control a cursor.
  • ホログラムと対話する入力ジェスチャを使用します。Use gesture input to interact with holograms.

手順Instructions

視線入力Gaze

  • 階層パネル選択、 HologramCollectionオブジェクト。In the Hierarchy panel select the HologramCollection object.
  • インスペクター パネル をクリックして、コンポーネントの追加ボタンをクリックします。In the Inspector panel click the Add Component button.
  • メニューで、検索ボックスに入力視線 Managerします。In the menu, type in the search box Gaze Manager. 検索結果を選択します。Select the search result.
  • HoloToolkit 共有 240\Prefabs\Inputフォルダー、検索、カーソル資産。In the HoloToolkit-Sharing-240\Prefabs\Input folder, find the Cursor asset.
  • ドラッグ アンド ドロップ、カーソル上に資産、階層します。Drag and drop the Cursor asset onto the Hierarchy.

ジェスチャGesture

  • 階層パネル選択、 HologramCollectionオブジェクト。In the Hierarchy panel select the HologramCollection object.
  • をクリックしてコンポーネントの追加と種類ジェスチャ マネージャー検索フィールドにします。Click Add Component and type Gesture Manager in the search field. 検索結果を選択します。Select the search result.
  • 階層パネル、展開HologramCollectionします。In the Hierarchy panel, expand HologramCollection.
  • 子を選択EnergyHubオブジェクト。Select the child EnergyHub object.
  • インスペクター パネル をクリックして、コンポーネントの追加ボタンをクリックします。In the Inspector panel click the Add Component button.
  • メニューで、検索ボックスに入力ホログラム配置します。In the menu, type in the search box Hologram Placement. 検索結果を選択します。Select the search result.
  • シーンを選択して保存ファイル > Save Sceneします。Save the scene by selecting File > Save Scene.

展開し、利用Deploy and enjoy

  • 構築し、前の章の手順を使用して、HoloLens にデプロイします。Build and deploy to your HoloLens, using the instructions from the previous chapter.
  • アプリが起動して、HoloLens、頭の中を移動、EnergyHub が、視線の先を追跡する方法に注意してください。Once the app launches on your HoloLens, move your head around and notice how the EnergyHub follows your gaze.
  • カーソルの場合、ホログラム時にこれが表示されますおよびホログラムでない gazing とポイント ライトに変更確認します。Notice how the cursor appears when you gaze upon the hologram, and changes to a point light when not gazing at a hologram.
  • ホログラムを配置するエア タップを実行します。Perform an air-tap to place the hologram. プロジェクト内のこの時点でのみホログラムを 1 回 (もう一度お試しに再デプロイ) を配置することができます。At this time in our project, you can only place the hologram once (redeploy to try again).

第 3 章 - 共有座標します。Chapter 3 - Shared Coordinates

楽しくホログラムで表示し、操作するですが、さらに見ていきましょう。It's fun to see and interact with holograms, but let's go further. 最初共有経験にまとめて確認できるホログラムを設定します。We'll set up our first shared experience - a hologram everyone can see together.

目標Objectives

  • 共有のエクスペリエンスのためのネットワークをセットアップします。Setup a network for a shared experience.
  • 一般的な参照ポイントを確立します。Establish a common reference point.
  • 座標系は、デバイス間で共有します。Share coordinate systems across devices.
  • すべてのユーザーには、同じホログラムが表示されます。Everyone sees the same hologram!

注意

InternetClientServerPrivateNetworkClientServer共有サーバーに接続するアプリの機能を宣言する必要があります。The InternetClientServer and PrivateNetworkClientServer capabilities must be declared for an app to connect to the sharing server. これを行うことを既にホログラム 240 が独自のプロジェクトに注意してください。 保存しておきます。This is done for you already in Holograms 240, but keep this in mind for your own projects.

  1. Unity エディターは、「> プロジェクトの設定 > プレーヤーの編集」に移動して、player の設定に移動します。In the Unity Editor, go to the player settings by navigating to "Edit > Project Settings > Player"
  2. "Windows Store" タブをクリックします。Click on the "Windows Store" tab
  3. 「発行の設定 > 機能」セクションでは、確認、 InternetClientServer機能とPrivateNetworkClientServer機能In the "Publishing Settings > Capabilities" section, check the InternetClientServer capability and the PrivateNetworkClientServer capability

手順Instructions

  • プロジェクト パネルに移動し、 HoloToolkit 共有 240\Prefabs\Sharingフォルダー。In the Project panel navigate to the HoloToolkit-Sharing-240\Prefabs\Sharing folder.
  • ドラッグ アンド ドロップ、共有にプレハブ、階層パネルします。Drag and drop the Sharing prefab into the Hierarchy panel.

次に、共有サービスを起動する必要があります。Next we need to launch the sharing service. のみ1 台の PC共有で発生するこの手順を実行する必要があります。Only one PC in the shared experience needs to do this step.

  • Unity - の上のメニューの選択、 HoloToolkit 共有 240 メニューします。In Unity - in the top-hand menu - select the HoloToolkit-Sharing-240 menu.
  • 選択、共有サービスの起動ドロップダウン リスト内の項目。Select the Launch Sharing Service item in the drop-down.
  • チェック、プライベート ネットワークオプションを選択し、をクリックしてアクセスを許可するファイアウォール プロンプトが表示されます。Check the Private Network option and click Allow Access when the firewall prompt appears.
  • 共有サービスのコンソール ウィンドウに表示される IPv4 アドレスをメモします。Note down the IPv4 address displayed in the Sharing Service console window. これは、上、サービスが実行されているマシンと同じ ip アドレスです。This is the same IP as the machine the service is being run on.

残りの手順に従って、すべての Pc共有エクスペリエンスに参加します。Follow the rest of the instructions on all PCs that will join the shared experience.

  • 階層を選択、共有オブジェクト。In the Hierarchy, select the Sharing object.
  • インスペクター共有ステージコンポーネント、変更、サーバー アドレスSharingService.exe を実行しているコンピューターの IPv4 アドレスに 'localhost' から。In the Inspector, on the Sharing Stage component, change the Server Address from 'localhost' to the IPv4 address of the machine running SharingService.exe.
  • 階層選択、 HologramCollectionオブジェクト。In the Hierarchy select the HologramCollection object.
  • インスペクタークリックして、コンポーネントの追加ボタンをクリックします。In the Inspector click the Add Component button.
  • 検索ボックスに「インポート エクスポート アンカー マネージャーします。In the search box, type Import Export Anchor Manager. 検索結果を選択します。Select the search result.
  • プロジェクト パネルに移動し、スクリプトフォルダー。In the Project panel navigate to the Scripts folder.
  • ダブルクリックして、 HologramPlacementスクリプトを Visual Studio で開きます。Double-click the HologramPlacement script to open it in Visual Studio.
  • 内容を次のコードに置き換えます。Replace the contents with the code below.
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Windows.Speech;
using Academy.HoloToolkit.Unity;
using Academy.HoloToolkit.Sharing;

public class HologramPlacement : Singleton<HologramPlacement>
{
    /// <summary>
    /// Tracks if we have been sent a transform for the anchor model.
    /// The anchor model is rendered relative to the actual anchor.
    /// </summary>
    public bool GotTransform { get; private set; }

    private bool animationPlayed = false;

    void Start()
    {
        // We care about getting updates for the anchor transform.
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.StageTransform] = this.OnStageTransform;

        // And when a new user join we will send the anchor transform we have.
        SharingSessionTracker.Instance.SessionJoined += Instance_SessionJoined;
    }

    /// <summary>
    /// When a new user joins we want to send them the relative transform for the anchor if we have it.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Instance_SessionJoined(object sender, SharingSessionTracker.SessionJoinedEventArgs e)
    {
        if (GotTransform)
        {
            CustomMessages.Instance.SendStageTransform(transform.localPosition, transform.localRotation);
        }
    }

    void Update()
    {
        if (GotTransform)
        {
            if (ImportExportAnchorManager.Instance.AnchorEstablished &&
                animationPlayed == false)
            {
                // This triggers the animation sequence for the anchor model and 
                // puts the cool materials on the model.
                GetComponent<EnergyHubBase>().SendMessage("OnSelect");
                animationPlayed = true;
            }
        }
        else
        {
            transform.position = Vector3.Lerp(transform.position, ProposeTransformPosition(), 0.2f);
        }
    }

    Vector3 ProposeTransformPosition()
    {
        // Put the anchor 2m in front of the user.
        Vector3 retval = Camera.main.transform.position + Camera.main.transform.forward * 2;

        return retval;
    }

    public void OnSelect()
    {
        // Note that we have a transform.
        GotTransform = true;
        
        // And send it to our friends.
        CustomMessages.Instance.SendStageTransform(transform.localPosition, transform.localRotation);
    }

    /// <summary>
    /// When a remote system has a transform for us, we'll get it here.
    /// </summary>
    /// <param name="msg"></param>
    void OnStageTransform(NetworkInMessage msg)
    {
        // We read the user ID but we don't use it here.
        msg.ReadInt64();

        transform.localPosition = CustomMessages.Instance.ReadVector3(msg);
        transform.localRotation = CustomMessages.Instance.ReadQuaternion(msg);

        // The first time, we'll want to send the message to the anchor to do its animation and
        // swap its materials.
        if (GotTransform == false)
        {
            GetComponent<EnergyHubBase>().SendMessage("OnSelect");
        }

        GotTransform = true;
    }

    public void ResetStage()
    {
        // We'll use this later.
    }
}
  • Unity でバックアップを選択、 HologramCollectionで、階層パネルします。Back in Unity, select the HologramCollection in the Hierarchy panel.
  • インスペクター パネル をクリックして、コンポーネントの追加ボタンをクリックします。In the Inspector panel click the Add Component button.
  • メニューで、検索ボックスに入力アプリ状態マネージャーします。In the menu, type in the search box App State Manager. 検索結果を選択します。Select the search result.

展開し、利用Deploy and enjoy

  • HoloLens デバイス プロジェクトをビルドします。Build the project for your HoloLens devices.
  • 最初に展開する 1 つの HoloLens を指定します。Designate one HoloLens to deploy to first. アンカー、EnergyHub を配置する前に、サービスにアップロードするを待機する必要があります (かかることが最大で 30 ~ 60 秒)。You will need to wait for the Anchor to be uploaded to the service before you can place the EnergyHub (this can take ~30-60 seconds). アップロードが完了するまで、タップ ジェスチャは無視されます。Until the upload is done, your tap gestures will be ignored.
  • EnergyHub を配置した後の場所は、サービスにアップロードして、し、他のすべての HoloLens デバイスに展開することができます。After the EnergyHub has been placed, its location will be uploaded to the service and you can then deploy to all other HoloLens devices.
  • 新しい HoloLens 最初に参加させるとき、セッション、EnergyHub の場所をそのデバイスに正しいできない可能性があります。When a new HoloLens first joins the session, the location of the EnergyHub may not be correct on that device. ただし、アンカーと EnergyHub 場所をサービスからダウンロードすると、すぐ、EnergyHub は、新しい共有の場所にジャンプする必要があります。However, as soon as the anchor and EnergyHub locations have been downloaded from the service, the EnergyHub should jump to the new, shared location. 30 ~ 60 内でこれが発生しない場合 (秒単位) は、複数の環境の手がかりを収集するためにアンカーを設定するときの元の HoloLens がについて説明します。If this does not happen within ~30-60 seconds, walk to where the original HoloLens was when setting the anchor to gather more environment clues. 場所はロックしない場合、デバイスへの再デプロイします。If the location still does not lock on, redeploy to the device.
  • デバイスがすべて準備完了と、EnergyHub を探して、アプリが実行されます。When the devices are all ready and running the app, look for the EnergyHub. ホログラムの場所に同意できるすべての方向、テキストが直面しているとしますか?Can you all agree on the hologram's location and which direction the text is facing?

第 4 章 - 検出Chapter 4 - Discovery

同じホログラムを確認できるようになりました。Everyone can now see the same hologram! ここでそれ以外の場合、共有 holographic 世界に接続されているすべてのユーザーを見てみましょう。Now let's see everyone else connected to our shared holographic world. この章で、ヘッドの場所と同じ共有セッションで他のすべての HoloLens デバイスの回転角度を取得します。In this chapter, we'll grab the head location and rotation of all other HoloLens devices in the same sharing session.

目標Objectives

  • 共有の経験には、互いを検出します。Discover each other in our shared experience.
  • 選択して、プレーヤーのアバターを共有します。Choose and share a player avatar.
  • すべてのユーザーの頭の横にある player アバターをアタッチします。Attach the player avatar next to everyone's heads.

手順Instructions

  • プロジェクト パネルに移動し、ホログラムフォルダー。In the Project panel navigate to the Holograms folder.
  • ドラッグ アンド ドロップ、 PlayerAvatarStoreに、階層します。Drag and drop the PlayerAvatarStore into the Hierarchy.
  • プロジェクト パネルに移動し、スクリプトフォルダー。In the Project panel navigate to the Scripts folder.
  • ダブルクリックして、 AvatarSelectorスクリプトを Visual Studio で開きます。Double-click the AvatarSelector script to open it in Visual Studio.
  • 内容を次のコードに置き換えます。Replace the contents with the code below.
using UnityEngine;
using Academy.HoloToolkit.Unity;

/// <summary>
/// Script to handle the user selecting the avatar.
/// </summary>
public class AvatarSelector : MonoBehaviour
{
    /// <summary>
    /// This is the index set by the PlayerAvatarStore for the avatar.
    /// </summary>
    public int AvatarIndex { get; set; }

    /// <summary>
    /// Called when the user is gazing at this avatar and air-taps it.
    /// This sends the user's selection to the rest of the devices in the experience.
    /// </summary>
    void OnSelect()
    {
        PlayerAvatarStore.Instance.DismissAvatarPicker();

        LocalPlayerManager.Instance.SetUserAvatar(AvatarIndex);        
    }

    void Start()
    {
        // Add Billboard component so the avatar always faces the user.
        Billboard billboard = gameObject.GetComponent<Billboard>();
        if (billboard == null)
        {
            billboard = gameObject.AddComponent<Billboard>();
        }

        // Lock rotation along the Y axis.
        billboard.PivotAxis = PivotAxis.Y;
    }
}
  • 階層選択、 HologramCollectionオブジェクト。In the Hierarchy select the HologramCollection object.
  • インスペクタークリックしてコンポーネントの追加します。In the Inspector click Add Component.
  • 検索ボックスに「ローカル Player Managerします。In the search box, type Local Player Manager. 検索結果を選択します。Select the search result.
  • 階層選択、 HologramCollectionオブジェクト。In the Hierarchy select the HologramCollection object.
  • インスペクタークリックしてコンポーネントの追加します。In the Inspector click Add Component.
  • 検索ボックスに「リモート Player Managerします。In the search box, type Remote Player Manager. 検索結果を選択します。Select the search result.
  • 開く、 HologramPlacement Visual Studio でのスクリプト。Open the HologramPlacement script in Visual Studio.
  • 内容を次のコードに置き換えます。Replace the contents with the code below.
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Windows.Speech;
using Academy.HoloToolkit.Unity;
using Academy.HoloToolkit.Sharing;

public class HologramPlacement : Singleton<HologramPlacement>
{
    /// <summary>
    /// Tracks if we have been sent a transform for the model.
    /// The model is rendered relative to the actual anchor.
    /// </summary>
    public bool GotTransform { get; private set; }

    /// <summary>
    /// When the experience starts, we disable all of the rendering of the model.
    /// </summary>
    List<MeshRenderer> disabledRenderers = new List<MeshRenderer>();

    void Start()
    {
        // When we first start, we need to disable the model to avoid it obstructing the user picking a hat.
        DisableModel();

        // We care about getting updates for the model transform.
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.StageTransform] = this.OnStageTransform;

        // And when a new user join we will send the model transform we have.
        SharingSessionTracker.Instance.SessionJoined += Instance_SessionJoined;
    }

    /// <summary>
    /// When a new user joins we want to send them the relative transform for the model if we have it.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Instance_SessionJoined(object sender, SharingSessionTracker.SessionJoinedEventArgs e)
    {
        if (GotTransform)
        {
            CustomMessages.Instance.SendStageTransform(transform.localPosition, transform.localRotation);
        }
    }

    /// <summary>
    /// Turns off all renderers for the model.
    /// </summary>
    void DisableModel()
    {
        foreach (MeshRenderer renderer in gameObject.GetComponentsInChildren<MeshRenderer>())
        {
            if (renderer.enabled)
            {
                renderer.enabled = false;
                disabledRenderers.Add(renderer);
            }
        }

        foreach (MeshCollider collider in gameObject.GetComponentsInChildren<MeshCollider>())
        {
            collider.enabled = false;
        }
    }

    /// <summary>
    /// Turns on all renderers that were disabled.
    /// </summary>
    void EnableModel()
    {
        foreach (MeshRenderer renderer in disabledRenderers)
        {
            renderer.enabled = true;
        }

        foreach (MeshCollider collider in gameObject.GetComponentsInChildren<MeshCollider>())
        {
            collider.enabled = true;
        }

        disabledRenderers.Clear();
    }


    void Update()
    {
        // Wait till users pick an avatar to enable renderers.
        if (disabledRenderers.Count > 0)
        {
            if (!PlayerAvatarStore.Instance.PickerActive &&
            ImportExportAnchorManager.Instance.AnchorEstablished)
            {
                // After which we want to start rendering.
                EnableModel();

                // And if we've already been sent the relative transform, we will use it.
                if (GotTransform)
                {
                    // This triggers the animation sequence for the model and 
                    // puts the cool materials on the model.
                    GetComponent<EnergyHubBase>().SendMessage("OnSelect");
                }
            }
        }
        else if (GotTransform == false)
        {
            transform.position = Vector3.Lerp(transform.position, ProposeTransformPosition(), 0.2f);
        }
    }

    Vector3 ProposeTransformPosition()
    {
        // Put the model 2m in front of the user.
        Vector3 retval = Camera.main.transform.position + Camera.main.transform.forward * 2;

        return retval;
    }

    public void OnSelect()
    {
        // Note that we have a transform.
        GotTransform = true;

        // And send it to our friends.
        CustomMessages.Instance.SendStageTransform(transform.localPosition, transform.localRotation);
    }

    /// <summary>
    /// When a remote system has a transform for us, we'll get it here.
    /// </summary>
    /// <param name="msg"></param>
    void OnStageTransform(NetworkInMessage msg)
    {
        // We read the user ID but we don't use it here.
        msg.ReadInt64();

        transform.localPosition = CustomMessages.Instance.ReadVector3(msg);
        transform.localRotation = CustomMessages.Instance.ReadQuaternion(msg);

        // The first time, we'll want to send the message to the model to do its animation and
        // swap its materials.
        if (disabledRenderers.Count == 0 && GotTransform == false)
        {
            GetComponent<EnergyHubBase>().SendMessage("OnSelect");
        }

        GotTransform = true;
    }

    public void ResetStage()
    {
        // We'll use this later.
    }
}
  • 開く、 AppStateManager Visual Studio でのスクリプト。Open the AppStateManager script in Visual Studio.
  • 内容を次のコードに置き換えます。Replace the contents with the code below.
using UnityEngine;
using Academy.HoloToolkit.Unity;

/// <summary>
/// Keeps track of the current state of the experience.
/// </summary>
public class AppStateManager : Singleton<AppStateManager>
{
    /// <summary>
    /// Enum to track progress through the experience.
    /// </summary>
    public enum AppState
    {
        Starting = 0,
        WaitingForAnchor,
        WaitingForStageTransform,
        PickingAvatar,
        Ready
    }

    /// <summary>
    /// Tracks the current state in the experience.
    /// </summary>
    public AppState CurrentAppState { get; set; }

    void Start()
    {
        // We start in the 'picking avatar' mode.
        CurrentAppState = AppState.PickingAvatar;

        // We start by showing the avatar picker.
        PlayerAvatarStore.Instance.SpawnAvatarPicker();
    }

    void Update()
    {
        switch (CurrentAppState)
        {
            case AppState.PickingAvatar:
                // Avatar picking is done when the avatar picker has been dismissed.
                if (PlayerAvatarStore.Instance.PickerActive == false)
                {
                    CurrentAppState = AppState.WaitingForAnchor;
                }
                break;
            case AppState.WaitingForAnchor:
                if (ImportExportAnchorManager.Instance.AnchorEstablished)
                {
                    CurrentAppState = AppState.WaitingForStageTransform;
                    GestureManager.Instance.OverrideFocusedObject = HologramPlacement.Instance.gameObject;
                }
                break;
            case AppState.WaitingForStageTransform:
                // Now if we have the stage transform we are ready to go.
                if (HologramPlacement.Instance.GotTransform)
                {
                    CurrentAppState = AppState.Ready;
                    GestureManager.Instance.OverrideFocusedObject = null;
                }
                break;
        }
    }
}

展開し、利用Deploy and Enjoy

  • ビルドし、プロジェクト、HoloLens デバイスをデプロイします。Build and deploy the project to your HoloLens devices.
  • Ping の音が聞こえるようにする場合は、選択メニューのアバターを見つけてエア タップ ジェスチャにアバターを選択します。When you hear a pinging sound, find the avatar selection menu and select an avatar with the air-tap gesture.
  • サービスと通信している場合、HoloLens、ライト、カーソルの周囲のポイントが別の色に場合は、ホログラムが表示されていません: (濃い紫) の初期化中、インポート/エクスポートの場所データ (黄)、アンカー (緑) のダウンロードアンカー (青) をアップロードしています。If you're not looking at any holograms, the point light around your cursor will turn a different color when your HoloLens is communicating with the service: initializing (dark purple), downloading the anchor (green), importing/exporting location data (yellow), uploading the anchor (blue). カーソルの周囲のライト、ポイントが既定の色 (明るい紫) の場合は、セッションで他のプレーヤーと対話できる状態には!If your point light around your cursor is the default color (light purple), then you are ready to interact with other players in your session!
  • 他のユーザーを見て、領域 - に接続されているがある holographic ロボットの肩の上に浮動小数点と、ヘッドの動きを模倣します。Look at other people connected to your space - there will be a holographic robot floating above their shoulder and mimicking their head motions!

第 5 章 - 配置Chapter 5 - Placement

この章では、アンカーの実際の画面に配置することをしましょう。In this chapter, we'll make the anchor able to be placed on real-world surfaces. 共有のエクスペリエンスに接続されているすべてのユーザーの間の中間点でそのアンカーを配置するのに共有の座標を使用します。We'll use shared coordinates to place that anchor in the middle point between everyone connected to the shared experience.

目標Objectives

  • プレーヤーのヘッドの位置に基づく空間マップ ホログラムを配置します。Place holograms on the spatial map based on players’ head position.

手順Instructions

  • プロジェクト パネルに移動し、ホログラムフォルダー。In the Project panel navigate to the Holograms folder.
  • ドラッグ アンド ドロップ、 CustomSpatialMappingにプレハブ、階層します。Drag and drop the CustomSpatialMapping prefab onto the Hierarchy.
  • プロジェクト パネルに移動し、スクリプトフォルダー。In the Project panel navigate to the Scripts folder.
  • ダブルクリックして、 AppStateManagerスクリプトを Visual Studio で開きます。Double-click the AppStateManager script to open it in Visual Studio.
  • 内容を次のコードに置き換えます。Replace the contents with the code below.
using UnityEngine;
using Academy.HoloToolkit.Unity;

/// <summary>
/// Keeps track of the current state of the experience.
/// </summary>
public class AppStateManager : Singleton<AppStateManager>
{
    /// <summary>
    /// Enum to track progress through the experience.
    /// </summary>
    public enum AppState
    {
        Starting = 0,
        PickingAvatar,
        WaitingForAnchor,
        WaitingForStageTransform,
        Ready
    }

    // The object to call to make a projectile.
    GameObject shootHandler = null;

    /// <summary>
    /// Tracks the current state in the experience.
    /// </summary>
    public AppState CurrentAppState { get; set; }

    void Start()
    {
        // The shootHandler shoots projectiles.
        if (GetComponent<ProjectileLauncher>() != null)
        {
            shootHandler = GetComponent<ProjectileLauncher>().gameObject;
        }

        // We start in the 'picking avatar' mode.
        CurrentAppState = AppState.PickingAvatar;

        // Spatial mapping should be disabled when we start up so as not
        // to distract from the avatar picking.
        SpatialMappingManager.Instance.StopObserver();
        SpatialMappingManager.Instance.gameObject.SetActive(false);

        // On device we start by showing the avatar picker.
        PlayerAvatarStore.Instance.SpawnAvatarPicker();
    }

    public void ResetStage()
    {
        // If we fall back to waiting for anchor, everything needed to 
        // get us into setting the target transform state will be setup.
        if (CurrentAppState != AppState.PickingAvatar)
        {
            CurrentAppState = AppState.WaitingForAnchor;
        }

        // Reset the underworld.
        if (UnderworldBase.Instance)
        {
            UnderworldBase.Instance.ResetUnderworld();
        }
    }

    void Update()
    {
        switch (CurrentAppState)
        {
            case AppState.PickingAvatar:
                // Avatar picking is done when the avatar picker has been dismissed.
                if (PlayerAvatarStore.Instance.PickerActive == false)
                {
                    CurrentAppState = AppState.WaitingForAnchor;
                }
                break;
            case AppState.WaitingForAnchor:
                // Once the anchor is established we need to run spatial mapping for a 
                // little while to build up some meshes.
                if (ImportExportAnchorManager.Instance.AnchorEstablished)
                {
                    CurrentAppState = AppState.WaitingForStageTransform;
                    GestureManager.Instance.OverrideFocusedObject = HologramPlacement.Instance.gameObject;

                    SpatialMappingManager.Instance.gameObject.SetActive(true);
                    SpatialMappingManager.Instance.DrawVisualMeshes = true;
                    SpatialMappingDeformation.Instance.ResetGlobalRendering();
                    SpatialMappingManager.Instance.StartObserver();
                }
                break;
            case AppState.WaitingForStageTransform:
                // Now if we have the stage transform we are ready to go.
                if (HologramPlacement.Instance.GotTransform)
                {
                    CurrentAppState = AppState.Ready;
                    GestureManager.Instance.OverrideFocusedObject = shootHandler;
                }
                break;
        }
    }
}
  • プロジェクト パネルに移動し、スクリプトフォルダー。In the Project panel navigate to the Scripts folder.
  • ダブルクリックして、 HologramPlacementスクリプトを Visual Studio で開きます。Double-click the HologramPlacement script to open it in Visual Studio.
  • 内容を次のコードに置き換えます。Replace the contents with the code below.
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Windows.Speech;
using Academy.HoloToolkit.Unity;
using Academy.HoloToolkit.Sharing;

public class HologramPlacement : Singleton<HologramPlacement>
{
    /// <summary>
    /// Tracks if we have been sent a transform for the model.
    /// The model is rendered relative to the actual anchor.
    /// </summary>
    public bool GotTransform { get; private set; }

    /// <summary>
    /// When the experience starts, we disable all of the rendering of the model.
    /// </summary>
    List<MeshRenderer> disabledRenderers = new List<MeshRenderer>();

    /// <summary>
    /// We use a voice command to enable moving the target.
    /// </summary>
    KeywordRecognizer keywordRecognizer;

    void Start()
    {
        // When we first start, we need to disable the model to avoid it obstructing the user picking a hat.
        DisableModel();

        // We care about getting updates for the model transform.
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.StageTransform] = this.OnStageTransform;

        // And when a new user join we will send the model transform we have.
        SharingSessionTracker.Instance.SessionJoined += Instance_SessionJoined;

        // And if the users want to reset the stage transform.
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.ResetStage] = this.OnResetStage;

        // Setup a keyword recognizer to enable resetting the target location.
        List<string> keywords = new List<string>();
        keywords.Add("Reset Target");
        keywordRecognizer = new KeywordRecognizer(keywords.ToArray());
        keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
        keywordRecognizer.Start();
    }

    /// <summary>
    /// When the keyword recognizer hears a command this will be called.  
    /// In this case we only have one keyword, which will re-enable moving the 
    /// target.
    /// </summary>
    /// <param name="args">information to help route the voice command.</param>
    private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
    {
        ResetStage();
    }

    /// <summary>
    /// Resets the stage transform, so users can place the target again.
    /// </summary>
    public void ResetStage()
    {
        GotTransform = false;

        // AppStateManager needs to know about this so that
        // the right objects get input routed to them.
        AppStateManager.Instance.ResetStage();

        // Other devices in the experience need to know about this as well.
        CustomMessages.Instance.SendResetStage();

        // And we need to reset the object to its start animation state.
        GetComponent<EnergyHubBase>().ResetAnimation();
    }

    /// <summary>
    /// When a new user joins we want to send them the relative transform for the model if we have it.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Instance_SessionJoined(object sender, SharingSessionTracker.SessionJoinedEventArgs e)
    {
        if (GotTransform)
        {
            CustomMessages.Instance.SendStageTransform(transform.localPosition, transform.localRotation);
        }
    }

    /// <summary>
    /// Turns off all renderers for the model.
    /// </summary>
    void DisableModel()
    {
        foreach (MeshRenderer renderer in gameObject.GetComponentsInChildren<MeshRenderer>())
        {
            if (renderer.enabled)
            {
                renderer.enabled = false;
                disabledRenderers.Add(renderer);
            }
        }

        foreach (MeshCollider collider in gameObject.GetComponentsInChildren<MeshCollider>())
        {
            collider.enabled = false;
        }
    }

    /// <summary>
    /// Turns on all renderers that were disabled.
    /// </summary>
    void EnableModel()
    {
        foreach (MeshRenderer renderer in disabledRenderers)
        {
            renderer.enabled = true;
        }

        foreach (MeshCollider collider in gameObject.GetComponentsInChildren<MeshCollider>())
        {
            collider.enabled = true;
        }

        disabledRenderers.Clear();
    }


    void Update()
    {
        // Wait till users pick an avatar to enable renderers.
        if (disabledRenderers.Count > 0)
        {
            if (!PlayerAvatarStore.Instance.PickerActive &&
            ImportExportAnchorManager.Instance.AnchorEstablished)
            {
                // After which we want to start rendering.
                EnableModel();

                // And if we've already been sent the relative transform, we will use it.
                if (GotTransform)
                {
                    // This triggers the animation sequence for the model and 
                    // puts the cool materials on the model.
                    GetComponent<EnergyHubBase>().SendMessage("OnSelect");
                }
            }
        }
        else if (GotTransform == false)
        {
            transform.position = Vector3.Lerp(transform.position, ProposeTransformPosition(), 0.2f);
        }
    }

    Vector3 ProposeTransformPosition()
    {
        Vector3 retval;
        // We need to know how many users are in the experience with good transforms.
        Vector3 cumulatedPosition = Camera.main.transform.position;
        int playerCount = 1;
        foreach (RemotePlayerManager.RemoteHeadInfo remoteHead in RemotePlayerManager.Instance.remoteHeadInfos)
        {
            if (remoteHead.Anchored && remoteHead.Active)
            {
                playerCount++;
                cumulatedPosition += remoteHead.HeadObject.transform.position;
            }
        }

        // If we have more than one player ...
        if (playerCount > 1)
        {
            // Put the transform in between the players.
            retval = cumulatedPosition / playerCount;
            RaycastHit hitInfo;

            // And try to put the transform on a surface below the midpoint of the players.
            if (Physics.Raycast(retval, Vector3.down, out hitInfo, 5, SpatialMappingManager.Instance.LayerMask))
            {
                retval = hitInfo.point;
            }
        }
        // If we are the only player, have the model act as the 'cursor' ...
        else
        {
            // We prefer to put the model on a real world surface.
            RaycastHit hitInfo;

            if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hitInfo, 30, SpatialMappingManager.Instance.LayerMask))
            {
                retval = hitInfo.point;
            }
            else
            {
                // But if we don't have a ray that intersects the real world, just put the model 2m in
                // front of the user.
                retval = Camera.main.transform.position + Camera.main.transform.forward * 2;
            }
        }
        return retval;
    }

    public void OnSelect()
    {
        // Note that we have a transform.
        GotTransform = true;

        // And send it to our friends.
        CustomMessages.Instance.SendStageTransform(transform.localPosition, transform.localRotation);
    }

    /// <summary>
    /// When a remote system has a transform for us, we'll get it here.
    /// </summary>
    /// <param name="msg"></param>
    void OnStageTransform(NetworkInMessage msg)
    {
        // We read the user ID but we don't use it here.
        msg.ReadInt64();

        transform.localPosition = CustomMessages.Instance.ReadVector3(msg);
        transform.localRotation = CustomMessages.Instance.ReadQuaternion(msg);

        // The first time, we'll want to send the message to the model to do its animation and
        // swap its materials.
        if (disabledRenderers.Count == 0 && GotTransform == false)
        {
            GetComponent<EnergyHubBase>().SendMessage("OnSelect");
        }

        GotTransform = true;
    }

    /// <summary>
    /// When a remote system has a transform for us, we'll get it here.
    /// </summary>
    void OnResetStage(NetworkInMessage msg)
    {
        GotTransform = false;

        GetComponent<EnergyHubBase>().ResetAnimation();
        AppStateManager.Instance.ResetStage();
    }
}

展開し、利用Deploy and enjoy

  • ビルドし、プロジェクト、HoloLens デバイスをデプロイします。Build and deploy the project to your HoloLens devices.
  • アプリの準備ができたら、円でし、EnergyHub がすべてのユーザーの中央に表示する方法に注意してください。When the app is ready, stand in a circle and notice how the EnergyHub appears in the center of everyone.
  • EnergyHub を配置する をタップします。Tap to place the EnergyHub.
  • ホログラムを新しい場所に移動するには、音声コマンドをバックアップ、EnergyHub を選択し、グループとして連携して動作する 'ターゲットのリセット' を再試行してください。Try the voice command 'Reset Target' to pick the EnergyHub back up and work together as a group to move the hologram to a new location.

第 6 章 - 実際の物理運動Chapter 6 - Real-World Physics

この章では、現実世界のサーフェスから跳ね返りますホログラムを追加します。In this chapter we'll add holograms that bounce off real-world surfaces. プロジェクトを開始して、友人の両方でいっぱいに自分のスペースをご覧ください。Watch your space fill up with projects launched by both you and your friends!

目標Objectives

  • 実際のサーフェスから跳ね返ります弾丸を起動します。Launch projectiles that bounce off real-world surfaces.
  • 他のプレイヤーが確認できるように、弾丸を共有します。Share the projectiles so other players can see them.

手順Instructions

  • 階層選択、 HologramCollectionオブジェクト。In the Hierarchy select the HologramCollection object.
  • インスペクタークリックしてコンポーネントの追加します。In the Inspector click Add Component.
  • 検索ボックスに「の光線ランチャーします。In the search box, type Projectile Launcher. 検索結果を選択します。Select the search result.

展開し、利用Deploy and enjoy

  • ビルドして、HoloLens デバイスに展開します。Build and deploy to your HoloLens devices.
  • アプリは、すべてのデバイスで実行中は、現実世界のサーフェスでの光線を起動するエア タップを実行します。When the app is running on all devices, perform an air-tap to launch projectile at real world surfaces.
  • 別のプレーヤーのアバターの光線競合したときの動作を参照してください。See what happens when your projectile collides with another player's avatar!

第 7 章 – フィナーレChapter 7 - Grand Finale

この章で、共同作業でのみ検出可能なポータルが明らかにします。In this chapter, we'll uncover a portal that can only be discovered with collaboration.

目標Objectives

  • シークレットのポータルを明らかにするためのアンカーに十分な弾丸を起動するには、共同作業します。Work together to launch enough projectiles at the anchor to uncover a secret portal!

手順Instructions

  • プロジェクト パネルに移動し、ホログラムフォルダー。In the Project panel navigate to the Holograms folder.
  • ドラッグ アンド ドロップ、黄泉資産として、 HologramCollection の子します。Drag and drop the Underworld asset as a child of HologramCollection.
  • HologramCollection選択すると、をクリックして、コンポーネントの追加ボタン、インスペクターします。With HologramCollection selected, click the Add Component button in the Inspector.
  • メニューで、検索ボックスに入力ExplodeTargetします。In the menu, type in the search box ExplodeTarget. 検索結果を選択します。Select the search result.
  • HologramCollectionから、選択した、階層ドラッグ、 EnergyHubオブジェクトをターゲットフィールドに、 インスペクターします。With HologramCollection selected, from the Hierarchy drag the EnergyHub object to the Target field in the Inspector.
  • HologramCollectionから、選択した、階層ドラッグ、黄泉オブジェクトを黄泉フィールドInspectorします。With HologramCollection selected, from the Hierarchy drag the Underworld object to the Underworld field in the Inspector.

展開し、利用Deploy and enjoy

  • ビルドして、HoloLens デバイスに展開します。Build and deploy to your HoloLens devices.
  • アプリの起動時に、EnergyHub 弾丸を起動する共同します。When the app has launched, collaborate together to launch projectiles at the EnergyHub.
  • 黄泉が表示されたら、弾丸黄泉ロボット (ヒット、ロボットを 3 回の楽し) でを起動します。When the underworld appears, launch projectiles at underworld robots (hit a robot three times for extra fun).