Compartilhamento do MR 240: vários dispositivos HoloLensMR Sharing 240: Multiple HoloLens devices

Observação

Os tutoriais do Mixed Reality Academy foram projetados com o HoloLens (1ª geração) e os headsets imersivos de realidade misturada em mente.The Mixed Reality Academy tutorials were designed with HoloLens (1st gen) and Mixed Reality Immersive Headsets in mind. Dessa forma, achamos que é importante continuar disponibilizando esses tutoriais para os desenvolvedores que ainda buscam obter diretrizes para o desenvolvimento visando esses dispositivos.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. Esses tutoriais não serão atualizados com os conjuntos de ferramentas mais recentes nem com as interações usadas para o HoloLens 2.These tutorials will not be updated with the latest toolsets or interactions being used for HoloLens 2. Eles serão mantidos para continuar funcionando nos dispositivos compatíveis.They will be maintained to continue working on the supported devices. Uma nova série de tutoriais foi postada para o HoloLens 2.A new series of tutorials has been posted for HoloLens 2.

Os hologramas têm a presença em nosso mundo, permanecendo em vigor conforme avançamos no espaço.Holograms are given presence in our world by remaining in place as we move about in space. O HoloLens mantém os hologramas em vigor usando vários sistemas de coordenadas para controlar o local e a orientação dos objetos.HoloLens keeps holograms in place by using various coordinate systems to keep track of the location and orientation of objects. Quando compartilhamos esses sistemas de coordenadas entre dispositivos, podemos criar uma experiência compartilhada que nos permite participar de um mundo Holographic compartilhado.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.

Neste tutorial, nós iremos:In this tutorial, we will:

  • Configure uma rede para uma experiência compartilhada.Setup a network for a shared experience.
  • Compartilhe hologramas entre dispositivos de HoloLens.Share holograms across HoloLens devices.
  • Descubra outras pessoas em nosso mundo Holographic compartilhado.Discover other people in our shared holographic world.
  • Crie uma experiência interativa compartilhada onde você pode ter como alvo outros jogadores – e inicie o projéteis neles!Create a shared interactive experience where you can target other players - and launch projectiles at them!

Suporte a dispositivosDevice support

CursoCourse HoloLensHoloLens Headsets imersivosImmersive headsets
Compartilhamento do MR 240: vários dispositivos HoloLensMR Sharing 240: Multiple HoloLens devices ✔️✔️

Antes de começarBefore you start

Pré-requisitosPrerequisites

Arquivos de projetoProject files

  • Baixe os arquivos exigidos pelo projeto.Download the files required by the project. Requer o Unity 2017,2 ou posterior.Requires Unity 2017.2 or later.
    • Se você ainda precisar de suporte do Unity 5,6, use esta versão.If you still need Unity 5.6 support, please use this release.
    • Se você ainda precisar de suporte do Unity 5,5, use esta versão.If you still need Unity 5.5 support, please use this release.
    • Se você ainda precisar de suporte do Unity 5,4, use esta versão.If you still need Unity 5.4 support, please use this release.
  • Cancele o arquivamento dos arquivos em sua área de trabalho ou outro local fácil de acessar.Un-archive the files to your desktop or other easy to reach location. Mantenha o nome da pasta como SharedHolograms.Keep the folder name as SharedHolograms.

Observação

Se você quiser examinar o código-fonte antes de baixá-lo, ele estará disponível no GitHub.If you want to look through the source code before downloading, it's available on GitHub.

Capítulo 1 – holo WorldChapter 1 - Holo World

Neste capítulo, vamos configurar nosso primeiro projeto do Unity e percorrer o processo de compilação e implantação.In this chapter, we'll setup our first Unity project and step through the build and deploy process.

ObjetivosObjectives

  • Configurar o Unity para desenvolver aplicativos Holographic.Setup Unity to develop holographic apps.
  • Veja seu holograma!See your hologram!

InstruçõesInstructions

  • Inicie o Unity.Start Unity.
  • Selecione Abrir.Select Open.
  • Insira o local como a pasta SharedHolograms que você desarquivou anteriormente.Enter location as the SharedHolograms folder you previously unarchived.
  • Selecione nome do projeto e clique em Selecionar pasta.Select Project Name and click Select Folder.
  • Na hierarquia, clique com o botão direito do mouse na câmera principal e selecione excluir.In the Hierarchy, right-click the Main Camera and select Delete.
  • Na pasta HoloToolkit-Sharing-240/pré-fabricados/Camera , localize a câmera principal pré-fabricado.In the HoloToolkit-Sharing-240/Prefabs/Camera folder, find the Main Camera prefab.
  • Arraste e solte a câmera principal na hierarquia.Drag and drop the Main Camera into the Hierarchy.
  • Na hierarquia, clique em criar e em criar vazio.In the Hierarchy, click on Create and Create Empty.
  • Clique com o botão direito do mouse no novo gameobject e selecione renomear.Right-click the new GameObject and select Rename.
  • Renomeie o gameobject para hologramacollection.Rename the GameObject to HologramCollection.
  • Selecione o objeto hologramacollection na hierarquia.Select the HologramCollection object in the Hierarchy.
  • No Inspetor , defina a posição de transformação como: X: 0, Y:-0,25, Z: 2.In the Inspector set the transform position to: X: 0, Y: -0.25, Z: 2.
  • Na pasta hologramas no painel Projeto, localize o ativo EnergyHub .In the Holograms folder in the Project panel, find the EnergyHub asset.
  • Arraste e solte o objeto EnergyHub do painel Projeto para a hierarquia como um filho de hologramacollection.Drag and drop the EnergyHub object from the Project panel to the Hierarchy as a child of HologramCollection.
  • Selecione arquivo > salvar cena como...Select File > Save Scene As...
  • Nomeie a cena SharedHolograms e clique em salvar.Name the scene SharedHolograms and click Save.
  • Pressione o botão reproduzir no Unity para visualizar os hologramas.Press the Play button in Unity to preview your holograms.
  • Pressione executar uma segunda vez para parar o modo de visualização.Press Play a second time to stop preview mode.

Exportar o projeto do Unity para o Visual StudioExport the project from Unity to Visual Studio

  • Em Unity, selecione arquivo > configurações de Build.In Unity select File > Build Settings.
  • Clique em Adicionar abrir cenas para adicionar a cena.Click Add Open Scenes to add the scene.
  • Selecione plataforma universal do Windows na lista plataforma e clique em alternar plataforma.Select Universal Windows Platform in the Platform list and click Switch Platform.
  • Defina o SDK como Universal 10.Set SDK to Universal 10.
  • Defina o dispositivo de destino para o tipo de compilação HoloLens e UWP como D3D.Set Target device to HoloLens and UWP Build Type to D3D.
  • Verifique os projetos do Unity C#.Check Unity C# Projects.
  • Clique em Compilar.Click Build.
  • Na janela Explorador de arquivos que aparece, crie uma nova pasta chamada "aplicativo".In the file explorer window that appears, create a New Folder named "App".
  • Clique uma vez na pasta do aplicativo .Single click the App folder.
  • Pressione Selecionar pasta.Press Select Folder.
  • Quando o Unity for concluído, uma janela Explorador de arquivos será exibida.When Unity is done, a File Explorer window will appear.
  • Abra a pasta do aplicativo .Open the App folder.
  • Abra SharedHolograms. sln para iniciar o Visual Studio.Open SharedHolograms.sln to launch Visual Studio.
  • Usando a barra de ferramentas superior no Visual Studio, altere o destino de debug para Release e de ARM para x86.Using the top toolbar in Visual Studio, change the target from Debug to Release and from ARM to X86.
  • Clique na seta suspensa ao lado de computador local e selecione dispositivo remoto.Click on the drop-down arrow next to Local Machine, and select Remote Device.
    • Defina o endereço para o nome ou endereço IP do seu HoloLens.Set the Address to the name or IP address of your HoloLens. Se você não souber o endereço IP do dispositivo, procure configurações > rede & Internet > opções avançadas ou pergunte ao Cortana "Ei Cortana, qual é meu endereço 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?"
    • Deixe o modo de autenticação definido como Universal.Leave the Authentication Mode set to Universal.
    • Clique em selecionarClick Select
  • Clique em depurar > iniciar sem Depurar ou pressione Ctrl + F5.Click Debug > Start Without debugging or press Ctrl + F5. Se esta for a primeira vez que você está implantando em seu dispositivo, será necessário emparelhar com o Visual Studio.If this is the first time deploying to your device, you will need to pair it with Visual Studio.
  • Coloque em seu HoloLens e encontre o holograma EnergyHub.Put on your HoloLens and find the EnergyHub hologram.

Capítulo 2-interaçãoChapter 2 - Interaction

Neste capítulo, vamos interagir com nossos hologramas.In this chapter, we'll interact with our holograms. Primeiro, vamos adicionar um cursor para visualizar nosso olhar.First, we'll add a cursor to visualize our Gaze. Em seguida, vamos adicionar gestos e usar nossa mão para colocar nossos hologramas em espaço.Then, we'll add Gestures and use our hand to place our holograms in space.

ObjetivosObjectives

  • Use a entrada olhar para controlar um cursor.Use gaze input to control a cursor.
  • Use a entrada de gestos para interagir com os hologramas.Use gesture input to interact with holograms.

InstruçõesInstructions

FocoGaze

  • No painel hierarquia , selecione o objeto hologramacollection .In the Hierarchy panel select the HologramCollection object.
  • No painel Inspetor , clique no botão Adicionar componente .In the Inspector panel click the Add Component button.
  • No menu, digite na caixa de pesquisa olhar Manager.In the menu, type in the search box Gaze Manager. Selecione o resultado da pesquisa.Select the search result.
  • Na pasta HoloToolkit-Sharing-240\Prefabs\Input , localize o ativo de cursor .In the HoloToolkit-Sharing-240\Prefabs\Input folder, find the Cursor asset.
  • Arraste e solte o ativo do cursor na hierarquia.Drag and drop the Cursor asset onto the Hierarchy.

GestoGesture

  • No painel hierarquia , selecione o objeto hologramacollection .In the Hierarchy panel select the HologramCollection object.
  • Clique em Adicionar componente e digite Gerenciador de gestos no campo de pesquisa.Click Add Component and type Gesture Manager in the search field. Selecione o resultado da pesquisa.Select the search result.
  • No painel hierarquia, expanda hologramacollection.In the Hierarchy panel, expand HologramCollection.
  • Selecione o objeto EnergyHub filho.Select the child EnergyHub object.
  • No painel Inspetor , clique no botão Adicionar componente .In the Inspector panel click the Add Component button.
  • No menu, digite o posicionamento do holograma da caixa de pesquisa.In the menu, type in the search box Hologram Placement. Selecione o resultado da pesquisa.Select the search result.
  • Salve a cena selecionando arquivo > salvar cena.Save the scene by selecting File > Save Scene.

Implantar e desfrutarDeploy and enjoy

  • Crie e implante em seu HoloLens, usando as instruções do capítulo anterior.Build and deploy to your HoloLens, using the instructions from the previous chapter.
  • Depois que o aplicativo for iniciado no seu HoloLens, mova seu rumo e observe como o EnergyHub segue o olhar.Once the app launches on your HoloLens, move your head around and notice how the EnergyHub follows your gaze.
  • Observe como o cursor aparece quando você olhar sobre o holograma e muda para uma luz pontual quando não nuvens em um holograma.Notice how the cursor appears when you gaze upon the hologram, and changes to a point light when not gazing at a hologram.
  • Execute um toque de ar para posicionar o holograma.Perform an air-tap to place the hologram. No momento em nosso projeto, você só pode posicionar o holograma uma vez (reimplantar para tentar novamente).At this time in our project, you can only place the hologram once (redeploy to try again).

Capítulo 3-coordenadas compartilhadasChapter 3 - Shared Coordinates

É divertido ver e interagir com os hologramas, mas vamos continuar.It's fun to see and interact with holograms, but let's go further. Vamos configurar nossa primeira experiência compartilhada-um holograma que todos possam ver em conjunto.We'll set up our first shared experience - a hologram everyone can see together.

ObjetivosObjectives

  • Configure uma rede para uma experiência compartilhada.Setup a network for a shared experience.
  • Estabeleça um ponto de referência comum.Establish a common reference point.
  • Compartilhe sistemas de coordenadas entre dispositivos.Share coordinate systems across devices.
  • Todos veem o mesmo holograma!Everyone sees the same hologram!

Observação

Os recursos InternetClientServer e PrivateNetworkClientServer devem ser declarados para que um aplicativo se conecte ao servidor de compartilhamento.The InternetClientServer and PrivateNetworkClientServer capabilities must be declared for an app to connect to the sharing server. Isso é feito para você já nos hologramas 240, mas tenha isso em mente para seus próprios projetos.This is done for you already in Holograms 240, but keep this in mind for your own projects.

  1. No editor do Unity, vá para as configurações do Player navegando até "Editar configurações do projeto > > Player"In the Unity Editor, go to the player settings by navigating to "Edit > Project Settings > Player"
  2. Clique na guia "Windows Store"Click on the "Windows Store" tab
  3. Na seção "publicando configurações > recursos", verifique o recurso InternetClientServer e o recurso PrivateNetworkClientServerIn the "Publishing Settings > Capabilities" section, check the InternetClientServer capability and the PrivateNetworkClientServer capability

InstruçõesInstructions

  • No painel Projeto , navegue até a pasta HoloToolkit-Sharing-240\Prefabs\SharingIn the Project panel navigate to the HoloToolkit-Sharing-240\Prefabs\Sharing folder.
  • Arraste e solte o pré-fabricado de compartilhamento no painel hierarquia.Drag and drop the Sharing prefab into the Hierarchy panel.

Em seguida, precisamos iniciar o serviço de compartilhamento.Next we need to launch the sharing service. Apenas um PC na experiência compartilhada precisa fazer essa etapa.Only one PC in the shared experience needs to do this step.

  • No Unity-no menu superior, selecione o menu HoloToolkit-Sharing-240.In Unity - in the top-hand menu - select the HoloToolkit-Sharing-240 menu.
  • Selecione o item Iniciar compartilhamento de serviço na lista suspensa.Select the Launch Sharing Service item in the drop-down.
  • Marque a opção rede privada e clique em permitir acesso quando o prompt de firewall for exibido.Check the Private Network option and click Allow Access when the firewall prompt appears.
  • Anote o endereço IPv4 exibido na janela do console do serviço de compartilhamento.Note down the IPv4 address displayed in the Sharing Service console window. Esse é o mesmo IP que o computador no qual o serviço está sendo executado.This is the same IP as the machine the service is being run on.

Siga o restante das instruções em todos os PCs que ingressarão na experiência compartilhada.Follow the rest of the instructions on all PCs that will join the shared experience.

  • Na hierarquia, selecione o objeto de compartilhamento .In the Hierarchy, select the Sharing object.
  • No Inspetor, no componente estágio de compartilhamento , altere o endereço do servidor de ' localhost ' para o endereço IPv4 do computador que executa o 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.
  • Na hierarquia , selecione o objeto hologramacollection .In the Hierarchy select the HologramCollection object.
  • No Inspetor , clique no botão Adicionar componente .In the Inspector click the Add Component button.
  • Na caixa de pesquisa, digite Gerenciador de importação e exportação.In the search box, type Import Export Anchor Manager. Selecione o resultado da pesquisa.Select the search result.
  • No painel Projeto , navegue até a pasta scripts .In the Project panel navigate to the Scripts folder.
  • Clique duas vezes no script HologramPlacement para abri-lo no Visual Studio.Double-click the HologramPlacement script to open it in Visual Studio.
  • Substitua o conteúdo pelo código abaixo.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.
    }
}
  • De volta ao Unity, selecione hologramacollection no painel hierarquia.Back in Unity, select the HologramCollection in the Hierarchy panel.
  • No painel Inspetor , clique no botão Adicionar componente .In the Inspector panel click the Add Component button.
  • No menu, digite o Gerenciador de estado do aplicativo da caixa de pesquisa.In the menu, type in the search box App State Manager. Selecione o resultado da pesquisa.Select the search result.

Implantar e desfrutarDeploy and enjoy

  • Compile o projeto para seus dispositivos de HoloLens.Build the project for your HoloLens devices.
  • Designe um HoloLens para implantar primeiro.Designate one HoloLens to deploy to first. Você precisará aguardar a âncora ser carregada no serviço antes de poder colocar o EnergyHub (isso pode levar aproximadamente 30-60 segundos).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). Até que o upload seja feito, seus gestos de toque serão ignorados.Until the upload is done, your tap gestures will be ignored.
  • Depois que o EnergyHub tiver sido colocado, seu local será carregado para o serviço e você poderá implantá-lo em todos os outros dispositivos do 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.
  • Quando um novo HoloLens ingressa primeiro na sessão, o local do EnergyHub pode não estar correto nesse dispositivo.When a new HoloLens first joins the session, the location of the EnergyHub may not be correct on that device. No entanto, assim que os locais de âncora e EnergyHub tiverem sido baixados do serviço, o EnergyHub deverá ir para o local novo e compartilhado.However, as soon as the anchor and EnergyHub locations have been downloaded from the service, the EnergyHub should jump to the new, shared location. Se isso não acontecer em cerca de 30-60 segundos, passe para o local em que o HoloLens original estava ao definir a âncora para reunir mais pistas de ambiente.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. Se o local ainda não for bloqueado, reimplante-o no dispositivo.If the location still does not lock on, redeploy to the device.
  • Quando os dispositivos estiverem prontos e executando o aplicativo, procure o EnergyHub.When the devices are all ready and running the app, look for the EnergyHub. Você pode concordar com o local do holograma e em qual direção o texto está voltado?Can you all agree on the hologram's location and which direction the text is facing?

Capítulo 4-descobertaChapter 4 - Discovery

Agora, todos podem ver o mesmo holograma!Everyone can now see the same hologram! Agora, vamos ver todos os outros conectados ao nosso mundo Holographic compartilhado.Now let's see everyone else connected to our shared holographic world. Neste capítulo, vamos pegar o local e a rotação do cabeçalho de todos os outros dispositivos HoloLens na mesma sessão de compartilhamento.In this chapter, we'll grab the head location and rotation of all other HoloLens devices in the same sharing session.

ObjetivosObjectives

  • Descubra um ao outro em nossa experiência compartilhada.Discover each other in our shared experience.
  • Escolha e compartilhe um avatar de jogador.Choose and share a player avatar.
  • Anexe o avatar do jogador ao lado dos cabeçotes de todos.Attach the player avatar next to everyone's heads.

InstruçõesInstructions

  • No painel Projeto , navegue até a pasta hologramas .In the Project panel navigate to the Holograms folder.
  • Arraste e solte o PlayerAvatarStore na hierarquia.Drag and drop the PlayerAvatarStore into the Hierarchy.
  • No painel Projeto , navegue até a pasta scripts .In the Project panel navigate to the Scripts folder.
  • Clique duas vezes no script AvatarSelector para abri-lo no Visual Studio.Double-click the AvatarSelector script to open it in Visual Studio.
  • Substitua o conteúdo pelo código abaixo.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;
    }
}
  • Na hierarquia , selecione o objeto hologramacollection .In the Hierarchy select the HologramCollection object.
  • No Inspetor , clique em Adicionar componente.In the Inspector click Add Component.
  • Na caixa de pesquisa, digite Gerenciador do player local.In the search box, type Local Player Manager. Selecione o resultado da pesquisa.Select the search result.
  • Na hierarquia , selecione o objeto hologramacollection .In the Hierarchy select the HologramCollection object.
  • No Inspetor , clique em Adicionar componente.In the Inspector click Add Component.
  • Na caixa de pesquisa, digite Gerenciador de Player remoto.In the search box, type Remote Player Manager. Selecione o resultado da pesquisa.Select the search result.
  • Abra o script HologramPlacement no Visual Studio.Open the HologramPlacement script in Visual Studio.
  • Substitua o conteúdo pelo código abaixo.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.
    }
}
  • Abra o script AppStateManager no Visual Studio.Open the AppStateManager script in Visual Studio.
  • Substitua o conteúdo pelo código abaixo.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;
        }
    }
}

Implantar e desfrutarDeploy and Enjoy

  • Crie e implante o projeto em seus dispositivos de HoloLens.Build and deploy the project to your HoloLens devices.
  • Quando você ouvir um som de ping, localize o menu de seleção de avatar e selecione um avatar com o gesto de toque do ar.When you hear a pinging sound, find the avatar selection menu and select an avatar with the air-tap gesture.
  • Se você não estiver olhando para os hologramas, o ponto leve em volta do cursor desligará uma cor diferente quando o seu HoloLens estiver se comunicando com o serviço: inicializando (roxo escuro), baixando a âncora (verde), importando/exportando dados do local (amarelo), carregando a âncora (azul).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). Se o seu ponto leve em volta do cursor for a cor padrão (roxo claro), você estará pronto para interagir com outros jogadores em sua sessão!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!
  • Examine outras pessoas conectadas ao seu espaço – haverá um robô Holographic flutuante acima de seus ressaltos e imitandondo seus movimentos de cabeça!Look at other people connected to your space - there will be a holographic robot floating above their shoulder and mimicking their head motions!

Capítulo 5 – posicionamentoChapter 5 - Placement

Neste capítulo, vamos tornar a âncora capaz de ser colocada em superfícies do mundo real.In this chapter, we'll make the anchor able to be placed on real-world surfaces. Usaremos coordenadas compartilhadas para colocar essa âncora no ponto central entre todos conectados à experiência compartilhada.We'll use shared coordinates to place that anchor in the middle point between everyone connected to the shared experience.

ObjetivosObjectives

  • Coloque os hologramas na malha de mapeamento espacial com base na posição de cabeçalho dos jogadores.Place holograms on the spatial mapping mesh based on players’ head position.

InstruçõesInstructions

  • No painel Projeto , navegue até a pasta hologramas .In the Project panel navigate to the Holograms folder.
  • Arraste e solte o CustomSpatialMapping pré-fabricado na hierarquia.Drag and drop the CustomSpatialMapping prefab onto the Hierarchy.
  • No painel Projeto , navegue até a pasta scripts .In the Project panel navigate to the Scripts folder.
  • Clique duas vezes no script AppStateManager para abri-lo no Visual Studio.Double-click the AppStateManager script to open it in Visual Studio.
  • Substitua o conteúdo pelo código abaixo.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;
        }
    }
}
  • No painel Projeto , navegue até a pasta scripts .In the Project panel navigate to the Scripts folder.
  • Clique duas vezes no script HologramPlacement para abri-lo no Visual Studio.Double-click the HologramPlacement script to open it in Visual Studio.
  • Substitua o conteúdo pelo código abaixo.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();
    }
}

Implantar e desfrutarDeploy and enjoy

  • Crie e implante o projeto em seus dispositivos de HoloLens.Build and deploy the project to your HoloLens devices.
  • Quando o aplicativo estiver pronto, aguarde um círculo e observe como o EnergyHub aparece no centro de todos.When the app is ready, stand in a circle and notice how the EnergyHub appears in the center of everyone.
  • Toque para posicionar o EnergyHub.Tap to place the EnergyHub.
  • Experimente o comando de voz "redefinir destino" para escolher o EnergyHub de backup e trabalhar juntos como um grupo para mover o holograma para um novo local.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.

Capítulo 6-Real-World físicaChapter 6 - Real-World Physics

Neste capítulo, adicionaremos hologramas que separam superfícies do mundo real.In this chapter we'll add holograms that bounce off real-world surfaces. Assista ao seu espaço preencha com projetos iniciados por você e seus amigos!Watch your space fill up with projects launched by both you and your friends!

ObjetivosObjectives

  • Inicie o projéteis que salta as superfícies do mundo real.Launch projectiles that bounce off real-world surfaces.
  • Compartilhe o projéteis para que outros jogadores possam vê-los.Share the projectiles so other players can see them.

InstruçõesInstructions

  • Na hierarquia , selecione o objeto hologramacollection .In the Hierarchy select the HologramCollection object.
  • No Inspetor , clique em Adicionar componente.In the Inspector click Add Component.
  • Na caixa de pesquisa, digite Projectile Launcher.In the search box, type Projectile Launcher. Selecione o resultado da pesquisa.Select the search result.

Implantar e desfrutarDeploy and enjoy

  • Crie e implante em seus dispositivos de HoloLens.Build and deploy to your HoloLens devices.
  • Quando o aplicativo estiver em execução em todos os dispositivos, execute um toque de ar para iniciar o Projectile em superfícies do mundo real.When the app is running on all devices, perform an air-tap to launch projectile at real world surfaces.
  • Veja o que acontece quando seu Projectile colide com o Avatar de outro jogador!See what happens when your projectile collides with another player's avatar!

Capítulo 7 – final geralChapter 7 - Grand Finale

Neste capítulo, vamos descobrir um portal que só pode ser descoberto com a colaboração.In this chapter, we'll uncover a portal that can only be discovered with collaboration.

ObjetivosObjectives

  • Trabalhe em conjunto para iniciar o projéteis suficiente na âncora para descobrir um portal secreto!Work together to launch enough projectiles at the anchor to uncover a secret portal!

InstruçõesInstructions

  • No painel Projeto , navegue até a pasta hologramas .In the Project panel navigate to the Holograms folder.
  • Arraste e solte o ativo Underworld como um filho de hologramacollection.Drag and drop the Underworld asset as a child of HologramCollection.
  • Com o hologramacollection selecionado, clique no botão Adicionar componente no Inspetor.With HologramCollection selected, click the Add Component button in the Inspector.
  • No menu, digite na caixa de pesquisa ExplodeTarget.In the menu, type in the search box ExplodeTarget. Selecione o resultado da pesquisa.Select the search result.
  • Com o hologramacollection selecionado, na hierarquia , arraste o objeto EnergyHub para o campo de destino no Inspetor.With HologramCollection selected, from the Hierarchy drag the EnergyHub object to the Target field in the Inspector.
  • Com o hologramacollection selecionado, na hierarquia , arraste o objeto Underworld para o campo Underworld no Inspetor.With HologramCollection selected, from the Hierarchy drag the Underworld object to the Underworld field in the Inspector.

Implantar e desfrutarDeploy and enjoy

  • Crie e implante em seus dispositivos de HoloLens.Build and deploy to your HoloLens devices.
  • Quando o aplicativo for iniciado, colabore em conjunto para iniciar o projéteis no EnergyHub.When the app has launched, collaborate together to launch projectiles at the EnergyHub.
  • Quando o Underworld for exibido, inicie projéteis em Underworld robots (pressione um robô três vezes para diversão extra).When the underworld appears, launch projectiles at underworld robots (hit a robot three times for extra fun).