HoloLens (1. generace) Sdílení 240: Více zařízení HoloLens

Důležité

Kurzy Mixed Reality Academy byly navrženy s ohledem na HoloLens (1. generace), Unity 2017 a Mixed Reality Asistivní náhlavní soupravy. Proto se domníváme, že je důležité ponechat tyto kurzy pro vývojáře, kteří stále hledají pokyny k vývoji pro tato zařízení. Tyto kurzy nebudou aktualizovány nejnovějšími sadami nástrojů nebo interakcemi používanými pro HoloLens 2 a nemusí být kompatibilní s novějšími verzemi Unity. Budou zachovány, aby mohly pokračovat v práci na podporovaných zařízeních. Byla publikována nová série kurzů pro HoloLens 2.

Hologramy jsou v našem světě dány tím, že zůstávají na místě, když se pohybujeme ve vesmíru. HoloLens udržuje hologramy na místě pomocí různých souřadnicových systémů , které sledují umístění a orientaci objektů. Když tyto souřadnicové systémy sdílíme mezi zařízeními, můžeme vytvořit sdílené prostředí, které nám umožní podílet se na sdíleném holografickém světě.

V tomto kurzu provedeme následující:

  • Nastavte síť pro sdílené prostředí.
  • Sdílejte hologramy napříč zařízeními HoloLens.
  • Objevte další lidi v našem sdíleném holografickém světě.
  • Vytvořte sdílené interaktivní prostředí, ve kterém můžete cílit na jiné hráče – a spustit na ně projektily!

Podpora zařízení

Kurz HoloLens Imerzivní náhlavní soupravy
MR Sharing 240: Více zařízení HoloLens ✔️

Než začnete

Požadavky

Soubory projektu

  • Stáhněte si soubory vyžadované projektem. Vyžaduje Unity 2017.2 nebo novější.
  • Zrušte archivaci souborů na ploše nebo na jiné snadno dostupné místo. Název složky ponechte jako SharedHolograms.

Poznámka

Pokud si chcete před stažením projít zdrojový kód, je k dispozici na GitHubu.

Kapitola 1 - Holo Svět

V této kapitole nastavíme náš první projekt Unity a provedeme proces sestavení a nasazení.

Cíle

  • Nastavte Unity pro vývoj holografických aplikací.
  • Podívejte se na svůj hologram!

Pokyny

  • Spusťte Unity.
  • Vyberte Otevřít.
  • Jako složku SharedHolograms , kterou jste dříve nearchivovali, zadejte umístění.
  • Vyberte Název projektu a klikněte na Vybrat složku.
  • V hierarchii klikněte pravým tlačítkem na hlavní kameru a vyberte Odstranit.
  • Ve složce HoloToolkit-Sharing-240/Prefabs/Camera najděte prefab Hlavní fotoaparát .
  • Přetáhněte hlavní kameru do hierarchie.
  • V hierarchii klikněte na Vytvořit a Vytvořit prázdné.
  • Klikněte pravým tlačítkem na nový objekt GameObject a vyberte Přejmenovat.
  • Přejmenujte GameObject na HologramCollection.
  • Vyberte objekt HologramCollection v hierarchii.
  • V inspektoru nastavte pozici transformace na X: 0, Y: -0,25, Z: 2.
  • Ve složce Hologramy na panelu Projekt najděte asset EnergyHub .
  • Přetáhněte objekt EnergyHub z panelu Projekt do hierarchie jako podřízený objekt HologramCollection.
  • Vyberte Soubor > Uložit scénu jako...
  • Pojmenujte scénu SharedHolograms a klikněte na Uložit.
  • Stisknutím tlačítka Přehrát v Unity zobrazíte náhled hologramů.
  • Pokud chcete režim náhledu ukončit, stiskněte přehrát podruhé.

Export projektu z Unity do sady Visual Studio

  • V Unity vyberte Nastavení sestavení souboru>.
  • Kliknutím na Přidat otevřené scény přidejte scénu.
  • V seznamu Platforma vyberte Univerzální platforma Windows a klikněte na Přepnout platformu.
  • Nastavte sadu SDK na Universal 10.
  • Nastavte Cílové zařízení na HoloLens a Typ sestavení UPW na D3D.
  • Zkontrolujte projekty Unity C#.
  • Klikněte na Sestavit.
  • V okně Průzkumníka souborů, které se zobrazí, vytvořte novou složku s názvem "Aplikace".
  • Jedním kliknutím klikněte na složku Aplikace .
  • Stiskněte Vybrat složku.
  • Po dokončení Unity se zobrazí okno Průzkumník souborů.
  • Otevřete složku Aplikace .
  • Otevřete Soubor SharedHolograms.sln a spusťte Visual Studio.
  • Pomocí horního panelu nástrojů v sadě Visual Studio změňte cíl z Ladění na Vydání a z ARM na X86.
  • Klikněte na šipku rozevíracího seznamu vedle položky Místní počítač a vyberte Vzdálené zařízení.
    • Nastavte Adresu na název nebo IP adresu vašeho HoloLensu. Pokud ip adresu svého zařízení neznáte, podívejte se do části Nastavení > Síť & Upřesnit možnosti internetu > nebo se Cortany zeptejte "Hey Cortana, What's my IP address?" (Ahoj Cortano, jaká je moje IP adresa?).
    • Režim ověřování ponechte nastavený na Univerzální.
    • Klikněte na Vybrat.
  • Klikněte na Spustit ladění > bez ladění nebo stiskněte ctrl + F5. Pokud se do zařízení nasazuje poprvé, budete ho muset spárovat se sadou Visual Studio.
  • Nasaďte holoLens a najděte hologram EnergyHubu.

Kapitola 2 – Interakce

V této kapitole budeme pracovat s hologramy. Nejprve přidáme kurzor pro vizualizaci pohledu. Pak přidáme gesta a pomocí ruky umístíme hologramy do prostoru.

Cíle

  • Ovládání kurzoru pomocí vstupu pohledu
  • Používejte zadávání gest k interakci s hologramy.

Pokyny

Pohled

  • Na panelu Hierarchie vyberte objekt HologramCollection .
  • Na panelu Inspektor klikněte na tlačítko Přidat komponentu .
  • V nabídce zadejte do vyhledávacího pole Správce pohledu. Vyberte výsledek hledání.
  • Ve složce HoloToolkit-Sharing-240\Prefabs\Input najděte asset Kurzor .
  • Přetáhněte asset Kurzor do hierarchie.

Gesto

  • Na panelu Hierarchie vyberte objekt HologramCollection .
  • Klikněte na Přidat komponentu a do vyhledávacího pole zadejte Správce gest . Vyberte výsledek hledání.
  • Na panelu Hierarchie rozbalte Položku HologramCollection.
  • Vyberte podřízený objekt EnergyHub .
  • Na panelu Inspektor klikněte na tlačítko Přidat komponentu .
  • V nabídce zadejte do vyhledávacího pole Umístění hologramu. Vyberte výsledek hledání.
  • Uložte scénu tak, že vyberete Soubor > Uložit scénu.

Nasazení a užijte si

  • Sestavte a nasaďte do holoLensu podle pokynů z předchozí kapitoly.
  • Jakmile se aplikace spustí na holoLensu, pohněte hlavou a všimněte si, jak EnergyHub sleduje váš pohled.
  • Všimněte si, jak se zobrazí kurzor, když se podíváte na hologram, a když se na hologram nezadíváte, změní se na bodové světlo.
  • Pomocí klepnutí vzduchem umístěte hologram. V tomto okamžiku v našem projektu můžete hologram umístit pouze jednou (znovu nasadit a zkusit to znovu).

Kapitola 3 - Sdílené souřadnice

Je zábavné vidět hologramy a pracovat s nimi, ale pojďme to ještě dál. Nastavíme naše první sdílené prostředí – hologram, který uvidí všichni společně.

Cíle

  • Nastavte síť pro sdílené prostředí.
  • Vytvořte společný referenční bod.
  • Sdílejte souřadnicové systémy mezi zařízeními.
  • Všichni vidí stejný hologram!

Poznámka

Funkce InternetClientServer a PrivateNetworkClientServer musí být deklarovány, aby se aplikace připojila k serveru pro sdílení. V Hologramech 240 už se to dělá za vás, ale mějte to na paměti pro své vlastní projekty.

  1. V Editoru Unity přejděte do nastavení přehrávače tak, že přejdete na Upravit > přehrávač nastavení > projektu.
  2. Klikněte na kartu Windows Store.
  3. V části Možnosti nastavení > publikování zkontrolujte možnosti InternetClientServer a PrivateNetworkClientServer .

Pokyny

  • Na panelu Projekt přejděte do složky HoloToolkit-Sharing-240\Prefabs\Sharing .
  • Přetáhněte panel Sdílení na panel Hierarchie.

Dále musíme spustit službu sdílení. Tento krok musí provést pouze jeden počítač ve sdíleném prostředí.

  • V Unity – v horní nabídce – vyberte nabídku HoloToolkit-Sharing-240.
  • V rozevíracím seznamu vyberte položku Spustit službu sdílení .
  • Zaškrtněte možnost Privátní síť a po zobrazení výzvy brány firewall klikněte na Povolit přístup .
  • Poznamenejte si adresu IPv4 zobrazenou v okně konzoly Služby sdílení. Jedná se o stejnou IP adresu jako počítač, na který služba běží.

Postupujte podle zbývajících pokynů na všech počítačích , které se připojí ke sdílenému prostředí.

  • V hierarchii vyberte objekt Sdílení .
  • V inspektoru v komponentě Fáze sdílení změňte adresu serveru z localhost na IPv4 adresu počítače, na kterém běží SharingService.exe.
  • V hierarchii vyberte objekt HologramCollection .
  • V inspektoru klikněte na tlačítko Přidat komponentu .
  • Do vyhledávacího pole zadejte Import Export Anchor Manager. Vyberte výsledek hledání.
  • Na panelu Projekt přejděte do složky Skripty .
  • Poklikáním na skript HologramPlacement ho otevřete v sadě Visual Studio.
  • Nahraďte obsah následujícím kódem.
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.
    }
}
  • Zpět v Unity vyberte na panelu Hierarchiepoložku HologramCollection.
  • Na panelu Inspektor klikněte na tlačítko Přidat komponentu .
  • V nabídce zadejte do vyhledávacího pole Správce stavu aplikací. Vyberte výsledek hledání.

Nasazení a užijte si

  • Sestavte projekt pro zařízení HoloLens.
  • Určete jeden HoloLens, který se má nasadit jako první. Před umístěním EnergyHubu budete muset počkat na nahrání ukotvení do služby (může to trvat přibližně 30 až 60 sekund). Dokud se nahrávání neskončí, budou vaše gesta klepnutí ignorována.
  • Po umístění EnergyHubu se jeho umístění nahraje do služby a pak ji můžete nasadit na všechna ostatní zařízení HoloLens.
  • Když se k relaci poprvé připojí nový HoloLens, nemusí být umístění EnergyHubu na daném zařízení správné. Jakmile se ale kotva a umístění EnergyHubu stáhnou ze služby, měl by EnergyHub přejít na nové sdílené umístění. Pokud k tomu nedojde během ~30-60 sekund, přejděte k místu, kde byl původní HoloLens, když nastavíte kotvu, abyste získali další vodítka prostředí. Pokud se umístění stále nezamkne, nasaďte ho do zařízení znovu.
  • Až budou všechna zařízení připravená a spustí aplikaci, vyhledejte EnergyHub. Můžete se všichni dohodnout na umístění hologramu a na směru, kterým text směřuje?

Kapitola 4 – Zjišťování

Všichni teď vidí stejný hologram! Teď uvidíme všechny ostatní připojené k našemu sdílenému holografickému světu. V této kapitole si vezmeme umístění hlavy a otočení všech ostatních zařízení HoloLens ve stejné relaci sdílení.

Cíle

  • Objevte se navzájem v našem sdíleném prostředí.
  • Vyberte a sdílejte avatara hráče.
  • Připojte avatara hráče vedle hlav všech.

Pokyny

  • Na panelu Projekt přejděte do složky Hologramy .
  • Přetáhněte PlayerAvatarStore do hierarchie.
  • Na panelu Projekt přejděte do složky Skripty .
  • Poklikáním na skript AvatarSelector ho otevřete v sadě Visual Studio.
  • Nahraďte obsah následujícím kódem.
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;
    }
}
  • V hierarchii vyberte objekt HologramCollection .
  • V inspektoru klikněte na Přidat komponentu.
  • Do vyhledávacího pole zadejte Správce místního přehrávače. Vyberte výsledek hledání.
  • V hierarchii vyberte objekt HologramCollection .
  • V inspektoru klikněte na Přidat komponentu.
  • Do vyhledávacího pole zadejte Správce vzdáleného přehrávače. Vyberte výsledek hledání.
  • Otevřete skript HologramPlacement v sadě Visual Studio.
  • Nahraďte obsah následujícím kódem.
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.
    }
}
  • Otevřete skript AppStateManager v sadě Visual Studio.
  • Nahraďte obsah následujícím kódem.
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;
        }
    }
}

Nasazení a užijte si to

  • Sestavte a nasaďte projekt do zařízení HoloLens.
  • Když uslyšíte zvuk pingování, najděte nabídku výběru avatara a vyberte avatara pomocí gesta pro klepnutí vzduchem.
  • Pokud se nedíváte na žádné hologramy, rozsvícené bodové světlo kolem kurzoru se změní na jinou barvu, když HoloLens komunikuje se službou: inicializace (tmavě fialová), stažení ukotvení (zelená), import/export dat o poloze (žlutá), nahrání ukotvení (modrá). Pokud je vaše bodová kontrolka kolem kurzoru výchozí barvou (světle fialová), pak jste připraveni komunikovat s ostatními hráči ve vaší relaci!
  • Podívejte se na další lidi spojené s vaším prostorem - tam bude holografický robot plovoucí nad jejich ramenem a napodobovat jejich pohyby hlavy!

Kapitola 5 - Umístění

V této kapitole nastavíme, aby bylo možné kotvu umístit na skutečné povrchy. Sdílené souřadnice použijeme k umístění tohoto ukotvení do prostředního bodu mezi všechny, kteří jsou připojení ke sdílenému prostředí.

Cíle

  • Umístěte hologramy do prostorové mapovací sítě na základě pozice hlavy hráče.

Pokyny

  • Na panelu Projekt přejděte do složky Hologramy .
  • Přetáhněte prefab CustomSpatialMapping do hierarchie.
  • Na panelu Projekt přejděte do složky Skripty .
  • Poklikáním otevřete skript AppStateManager v sadě Visual Studio.
  • Nahraďte obsah následujícím kódem.
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;
        }
    }
}
  • Na panelu Projekt přejděte do složky Skripty .
  • Poklikáním na skript HologramPlacement ho otevřete v sadě Visual Studio.
  • Nahraďte obsah následujícím kódem.
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();
    }
}

Nasazení a užijte si

  • Sestavte a nasaďte projekt do zařízení HoloLens.
  • Až bude aplikace připravená, postavte se do kruhu a všimněte si, jak se EnergyHub zobrazuje uprostřed všech.
  • Klepnutím umístěte EnergyHub.
  • Zkuste hlasovým příkazem Reset Target (Resetovat cíl) vybrat EnergyHub a společně jako skupina přesuňte hologram do nového umístění.

Kapitola 6 – fyzika Real-World

V této kapitole přidáme hologramy, které se odrazí od reálných povrchů. Sledujte, jak se váš prostor zaplní projekty, které jste spustili vy i vaši přátelé!

Cíle

  • Spusťte projektily, které se odrazí od reálných povrchů.
  • Sdílejte projektily, aby je ostatní hráči viděli.

Pokyny

  • V hierarchii vyberte objekt HologramCollection .
  • V inspektoru klikněte na Přidat komponentu.
  • Do vyhledávacího pole zadejte Projectil Launcher. Vyberte výsledek hledání.

Nasazení a užijte si

  • Sestavte a nasaďte do zařízení HoloLens.
  • Když je aplikace spuštěná na všech zařízeních, poklepání vzduchem spusťte projektil na povrchu reálného světa.
  • Podívejte se, co se stane, když se váš projektil srazí s avatarem jiného hráče!

Kapitola 7 - Velké finále

V této kapitole odkryjeme portál, který je možné objevit pouze ve spolupráci.

Cíle

  • Spolupracujte, abyste spustili dostatek projektilů na kotvu, abyste odhalili tajný portál!

Pokyny

  • Na panelu Projekt přejděte do složky Hologramy .
  • Přetáhněte prostředek Underworld jako podřízený prvek HologramCollection.
  • S vybranou možností HologramCollection klikněte v inspektoru na tlačítko Přidat komponentu.
  • V nabídce zadejte do vyhledávacího pole ExplodeTarget. Vyberte výsledek hledání.
  • S vybranou možností HologramCollection přetáhněte z hierarchie objekt EnergyHub do pole Target v inspektoru.
  • Když je vybraná možnost HologramCollection , přetáhněte z hierarchie objekt Underworld do pole Underworld v inspektoru.

Nasazení a užijte si

  • Sestavte a nasaďte do zařízení HoloLens.
  • Po spuštění aplikace spolupracujte na spuštění projektilů v EnergyHubu.
  • Když se objeví podsvětí, vystřelte projektily na roboty podsvětí (třikrát udeřit robota pro další zábavu).