240. Общий доступ в смешанной реальности: несколько устройств HoloLensMR Sharing 240: Multiple HoloLens devices

Примечание

Руководства Mixed Reality Academy были разработаны для иммерсивных гарнитур HoloLens (1-го поколения) и иммерсивных гарнитур Mixed Reality.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.A new series of tutorials has been posted for HoloLens 2.

Голограммы задаются в нашем мире по мере перемещения о месте в пространстве.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.
  • Знакомство с другими людьми в нашей общей жизни.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
240. Общий доступ в смешанной реальности: несколько устройств HoloLensMR 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. В качестве имени папки используйте шаредхолограмс.Keep the folder name as SharedHolograms.

Примечание

Если вы хотите просмотреть исходный код перед загрузкой, он доступен на сайте GitHub.If you want to look through the source code before downloading, it's available on GitHub.

Глава 1 — Холо WorldChapter 1 - Holo World

В этой главе мы создадим наш первый проект Unity и пошаговым процессом сборки и развертывания.In this chapter, we'll setup our first Unity project and step through the build and deploy process.

ЗадачиObjectives

  • Настройте Unity для разработки holographic приложений.Setup Unity to develop holographic apps.
  • Ознакомьтесь с голограммой!See your hologram!

ИнструкцииInstructions

  • Запустите Unity.Start Unity.
  • Выберите Open (Открыть).Select Open.
  • Введите Location в качестве ранее неархивированной папки шаредхолограмс .Enter location as the SharedHolograms folder you previously unarchived.
  • Выберите имя проекта и щелкните выбрать папку.Select Project Name and click Select Folder.
  • В иерархии щелкните правой кнопкой мыши основную камеру и выберите Удалить.In the Hierarchy, right-click the Main Camera and select Delete.
  • В папке холотулкит-Shared-240/Prefabs/Camera найдите главную камеру prefab.In the HoloToolkit-Sharing-240/Prefabs/Camera folder, find the Main Camera prefab.
  • Перетащите основную камеру в иерархию.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 в холограмколлектион.Rename the GameObject to 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.
  • В папке голограмм на панели проект найдите ресурс енергихуб .In the Holograms folder in the Project panel, find the EnergyHub asset.
  • Перетащите объект енергихуб с панели проект в иерархию в качестве дочернего элемента холограмколлектион.Drag and drop the EnergyHub object from the Project panel to the Hierarchy as a child of HologramCollection.
  • Выберите файл > сохранить сцену как...Select File > Save Scene As...
  • Присвойте сцене имя шаредхолограмс и нажмите кнопку сохранить.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.

Экспорт проекта из Unity в Visual StudioExport the project from Unity to Visual Studio

  • В Unity выберите файл > параметры сборки.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.
  • Присвойте целевому устройству значение HoloLens , а для типа сборки UWPD3D.Set Target device to HoloLens and UWP Build Type to D3D.
  • Проверьте проекты C# для Unity.Check Unity C# Projects.
  • Щелкните Построить.Click Build.
  • В открывшемся окне проводника создайте новую папку с именем App.In the file explorer window that appears, create a New Folder named "App".
  • Щелкните папку приложения одним щелчком мыши.Single click the App folder.
  • Нажмите кнопку выбрать папку.Press Select Folder.
  • После завершения Unity появится окно проводника.When Unity is done, a File Explorer window will appear.
  • Откройте папку приложения .Open the App folder.
  • Откройте шаредхолограмс. 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.
    • Присвойте адресу имя или IP-адрес HoloLens.Set the Address to the name or IP address of your HoloLens. Если вы не знакомы с IP-адресом устройства, проверьте параметры > сеть & интернет > дополнительные параметры или спросите Кортану "Привет, Кортана," мой 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.
  • Поставьте на 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

  • На панели Иерархия выберите объект холограмколлектион .In the Hierarchy panel select the HologramCollection object.
  • На панели инспектора нажмите кнопку Добавить компонент .In the Inspector panel click the Add Component button.
  • В меню введите в поле поиска пункт Менеджер.In the menu, type in the search box Gaze Manager. Выберите результат поиска.Select the search result.
  • В папке HoloToolkit-Sharing-240\Prefabs\Input найдите ресурс cursor .In the HoloToolkit-Sharing-240\Prefabs\Input folder, find the Cursor asset.
  • Перетащите в иерархию ресурс с курсором .Drag and drop the Cursor asset onto the Hierarchy.

жестыGesture

  • На панели Иерархия выберите объект холограмколлектион .In the Hierarchy panel select the HologramCollection object.
  • Щелкните Добавить компонент и введите Диспетчер жестов в поле поиска.Click Add Component and type Gesture Manager in the search field. Выберите результат поиска.Select the search result.
  • На панели Иерархия разверните узел холограмколлектион.In the Hierarchy panel, expand HologramCollection.
  • Выберите дочерний объект енергихуб .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 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, переместите заголовок и обратите внимание на то, как Енергихуб соответствует вашему взгляду.Once the app launches on your HoloLens, move your head around and notice how the EnergyHub follows your gaze.
  • Обратите внимание на то, что курсор отображается при взгляде на голограмму, и меняется на светло-точечный, когда не облаками на голограмме.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. В настоящее время в нашем проекте можно разместить голограмму только один раз (повторное развертывание, чтобы повторить попытку).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!

Примечание

Для подключения приложения к серверу общего доступа необходимо объявить возможности интернетклиентсервер и приватенетворкклиентсервер .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".Click on the "Windows Store" tab
  3. В разделе "Параметры публикации > возможности" проверьте возможность интернетклиентсервер и возможности приватенетворкклиентсервер .In the "Publishing Settings > Capabilities" section, check the InternetClientServer capability and the PrivateNetworkClientServer capability

ИнструкцииInstructions

  • На панели "проект " перейдите в папку HoloToolkit-Sharing-240\Prefabs\Sharing .In the Project panel navigate to the HoloToolkit-Sharing-240\Prefabs\Sharing folder.
  • Перетащите prefab доступа на панель Иерархия.Drag and drop the Sharing prefab into the Hierarchy panel.

Далее необходимо запустить службу общего доступа.Next we need to launch the sharing service. Только один компьютер в общем интерфейсе должен выполнить этот шаг.Only one PC in the shared experience needs to do this step.

  • В Unity — в меню верхнего уровня — выберите меню холотулкит-Sharing-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.

Следуйте остальным инструкциям на всех ПК , которые будут присоединяться к общему интерфейсу.Follow the rest of the instructions on all PCs that will join the shared experience.

  • В иерархии выберите объект общего доступа .In the Hierarchy, select the Sharing object.
  • В инспекторе в компоненте " этап предоставления общего доступа " измените адрес сервера с "localhost" на IPv4-адрес компьютера, на котором выполняется SharingService.exe.In the Inspector, on the Sharing Stage component, change the Server Address from 'localhost' to the IPv4 address of the machine running SharingService.exe.
  • В иерархии выберите объект холограмколлектион .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.
  • На панели проект перейдите к папке Scripts .In the Project panel navigate to the Scripts folder.
  • Дважды щелкните скрипт холограмплацемент , чтобы открыть его в 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, выберите холограмколлектион на панели Иерархия.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.
  • Назначьте один HoloLens для развертывания первым.Designate one HoloLens to deploy to first. Необходимо подождать, пока привязка будет отправлена в службу, прежде чем можно будет поместить Енергихуб (это может занять около 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.
  • После размещения Енергихуб его расположение загружается в службу, а затем можно выполнить развертывание на всех других устройствах 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 впервые присоединяется к сеансу, расположение Енергихуб может быть неправильным на этом устройстве.When a new HoloLens first joins the session, the location of the EnergyHub may not be correct on that device. Однако, как только расположения привязки и Енергихуб загружаются из службы, Енергихуб должен перейти к новому общему расположению.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.
  • Когда устройства готовы и запускают приложение, найдите Енергихуб.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! Теперь давайте посмотрим все, кто еще подключен к нашему миру.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.
  • Вложите аватар игрока рядом с головами всех.Attach the player avatar next to everyone's heads.

ИнструкцииInstructions

  • На панели проект перейдите к папке голограмм .In the Project panel navigate to the Holograms folder.
  • Перетащите плайераватарсторе в иерархию.Drag and drop the PlayerAvatarStore into the Hierarchy.
  • На панели проект перейдите к папке Scripts .In the Project panel navigate to the Scripts folder.
  • Дважды щелкните скрипт аватарселектор , чтобы открыть его в 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;
    }
}
  • В иерархии выберите объект холограмколлектион .In the Hierarchy select the HologramCollection object.
  • В инспекторе щелкните Добавить компонент.In the Inspector click Add Component.
  • В поле поиска введите " диспетчер локальных игроков".In the search box, type Local Player Manager. Выберите результат поиска.Select the search result.
  • В иерархии выберите объект холограмколлектион .In the Hierarchy select the HologramCollection object.
  • В инспекторе щелкните Добавить компонент.In the Inspector click Add Component.
  • В поле поиска введите Remote Player Manager.In the search box, type Remote Player Manager. Выберите результат поиска.Select the search result.
  • Откройте скрипт холограмплацемент в 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.
    }
}
  • Откройте скрипт аппстатеманажер в 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.
  • Когда вы слышите звуковой сигнал, найдите меню выбора аватара и выберите аватар с помощью жеста касания.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!
  • Взгляните на других пользователей, подключенных к своему модулю. в этом случае у вас будет один и тот же робот, а затем копируя свои головные движения!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 mapping mesh based on players’ head position.

ИнструкцииInstructions

  • На панели проект перейдите к папке голограмм .In the Project panel navigate to the Holograms folder.
  • Перетащите кустомспатиалмаппинг prefab на иерархию.Drag and drop the CustomSpatialMapping prefab onto the Hierarchy.
  • На панели проект перейдите к папке Scripts .In the Project panel navigate to the Scripts folder.
  • Дважды щелкните скрипт аппстатеманажер , чтобы открыть его в 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;
        }
    }
}
  • На панели проект перейдите к папке Scripts .In the Project panel navigate to the Scripts folder.
  • Дважды щелкните скрипт холограмплацемент , чтобы открыть его в 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.
  • Когда приложение будет готово, наведите на окружность и обратите внимание на то, как Енергихуб отображается в центре всех.When the app is ready, stand in a circle and notice how the EnergyHub appears in the center of everyone.
  • Коснитесь, чтобы поместить Енергихуб.Tap to place the 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-Real-Worldая физика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

  • В иерархии выберите объект холограмколлектион .In the Hierarchy select the HologramCollection object.
  • В инспекторе щелкните Добавить компонент.In the Inspector click Add Component.
  • В поле поиска введите Прожектиле Launcher.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.
  • Перетащите этот ресурс в качестве дочернего для холограмколлектион.Drag and drop the Underworld asset as a child of HologramCollection.
  • Выбрав холограмколлектион , нажмите кнопку Добавить компонент в инспекторе.With HologramCollection selected, click the Add Component button in the Inspector.
  • В меню введите експлодетаржет в поле поиска.In the menu, type in the search box ExplodeTarget. Выберите результат поиска.Select the search result.
  • Выбрав холограмколлектион , из иерархии перетащите объект енергихуб в целевое поле в инспекторе.With HologramCollection selected, from the Hierarchy drag the EnergyHub object to the Target field in the 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.
  • После запуска приложения совместная работа для запуска снаряды на Енергихуб.When the app has launched, collaborate together to launch projectiles at the EnergyHub.
  • Когда появится подсветка, запустите снаряды в подсвете роботов (нажмите робота три раза, чтобы получить дополнительные развлечения).When the underworld appears, launch projectiles at underworld robots (hit a robot three times for extra fun).