Udostępnianie urządzenia HoloLens (1. generacji) 240: wiele urządzeń HoloLens

Ważne

Samouczki dotyczące akademii Mixed Reality zostały zaprojektowane z myślą o urządzeniach HoloLens (1. generacji), Unity 2017 i Mixed Reality immersywnych zestawów słuchawkowych. W związku z tym uważamy, że ważne jest pozostawienie tych samouczków w miejscu dla deweloperów, którzy nadal szukają wskazówek dotyczących opracowywania tych urządzeń. Te samouczki nie zostaną zaktualizowane przy użyciu najnowszych zestawów narzędzi ani interakcji używanych do HoloLens 2 i mogą nie być zgodne z nowszymi wersjami aparatu Unity. Będą one utrzymywane w celu kontynuowania pracy na obsługiwanych urządzeniach. Opublikowano nową serię samouczków dla HoloLens 2.

Hologramy otrzymują obecność na naszym świecie, pozostając na miejscu, gdy poruszamy się w przestrzeni kosmicznej. Urządzenie HoloLens utrzymuje hologramy przy użyciu różnych układów współrzędnych w celu śledzenia lokalizacji i orientacji obiektów. Gdy udostępniamy te systemy współrzędnych między urządzeniami, możemy utworzyć wspólne środowisko, które pozwala nam wziąć udział w udostępnionym świecie holograficznym.

W tym samouczku zostaną wykonane następujące czynności:

  • Konfigurowanie sieci dla środowiska udostępnionego.
  • Udostępnianie hologramów na urządzeniach HoloLens.
  • Odkryj innych ludzi w naszym wspólnym świecie holograficznym.
  • Utwórz wspólne interaktywne środowisko, w którym możesz kierować innych graczy - i uruchamiać na nich pociski!

Obsługa urządzeń

Kurs HoloLens Immersyjne zestawy słuchawkowe
MR Sharing 240: Wiele urządzeń HoloLens ✔️

Przed rozpoczęciem

Wymagania wstępne

Pliki projektu

  • Pobierz pliki wymagane przez projekt. Wymaga aparatu Unity 2017.2 lub nowszego.
    • Jeśli nadal potrzebujesz obsługi środowiska Unity 5.6, użyj tej wersji.
    • Jeśli nadal potrzebujesz obsługi środowiska Unity 5.5, użyj tej wersji.
    • Jeśli nadal potrzebujesz obsługi środowiska Unity 5.4, użyj tej wersji.
  • Usuń zarchiwizuj pliki na pulpicie lub innym łatwym w osiągnięciu lokalizacji. Zachowaj nazwę folderu jako UdostępnioneHologramy.

Uwaga

Jeśli chcesz przejrzeć kod źródłowy przed pobraniem, jest on dostępny w witrynie GitHub.

Rozdział 1 — Holo World

W tym rozdziale skonfigurujemy nasz pierwszy projekt aparatu Unity i przejdziemy przez proces kompilacji i wdrażania.

Cele

  • Skonfiguruj środowisko Unity, aby tworzyć aplikacje holograficzne.
  • Zobacz swój hologram!

Instrukcje

  • Uruchom środowisko Unity.
  • Wybierz pozycję Otwórz.
  • Wprowadź lokalizację jako folder SharedHolograms , który został wcześniej niearchiwiony.
  • Wybierz pozycję Nazwa projektu i kliknij pozycję Wybierz folder.
  • W hierarchii kliknij prawym przyciskiem myszy aparat główny i wybierz polecenie Usuń.
  • W folderze HoloToolkit-Sharing-240/Prefabs/Camera znajdź prefab Main Camera .
  • Przeciągnij i upuść aparat główny w hierarchii.
  • W hierarchii kliknij pozycję Utwórz i Utwórz puste.
  • Kliknij prawym przyciskiem myszy nowy obiekt GameObject i wybierz polecenie Zmień nazwę.
  • Zmień nazwę obiektu GameObject na HologramCollection.
  • Wybierz obiekt HologramCollection w hierarchii.
  • W inspektorze ustaw pozycję przekształcenia na: X: 0, Y: -0,25, Z: 2.
  • W folderze Holograms w panelu Projekt znajdź zasób EnergyHub .
  • Przeciągnij i upuść obiekt EnergyHub z panelu Project do hierarchii jako element podrzędny hologramCollection.
  • Wybierz pozycję Zapisz scenę jako > ...
  • Nadaj scenie nazwę SharedHolograms i kliknij przycisk Zapisz.
  • Naciśnij przycisk Odtwórz w środowisku Unity, aby wyświetlić podgląd hologramów.
  • Naciśnij przycisk Odtwórz po raz drugi, aby zatrzymać tryb podglądu.

Eksportowanie projektu z aparatu Unity do programu Visual Studio

  • W środowisku Unity wybierz pozycję Ustawienia kompilacji pliku>.
  • Kliknij pozycję Dodaj otwarte sceny , aby dodać scenę.
  • Wybierz pozycję platforma uniwersalna systemu Windows na liście Platforma, a następnie kliknij pozycję Przełącz platformę.
  • Ustaw zestaw SDK na wartość Universal 10.
  • Ustaw wartość Urządzenie docelowe na HoloLens i typ kompilacji platformy UWP na D3D.
  • Sprawdź projekty języka C# aparatu Unity.
  • Kliknij pozycję Skompiluj.
  • W wyświetlonym oknie Eksploratora plików utwórz nowy folder o nazwie "Aplikacja".
  • Jednym kliknięciem folderu Aplikacja .
  • Naciśnij pozycję Wybierz folder.
  • Po zakończeniu działania aparatu Unity zostanie wyświetlone okno Eksplorator plików.
  • Otwórz folder Aplikacja .
  • Otwórz plik SharedHolograms.sln , aby uruchomić program Visual Studio.
  • Za pomocą górnego paska narzędzi w programie Visual Studio zmień element docelowy z Debuguj na Wydanie i z usługi ARM na X86.
  • Kliknij strzałkę listy rozwijanej obok pozycji Maszyna lokalna, a następnie wybierz pozycję Urządzenie zdalne.
    • Ustaw wartość Address (Adres ) na nazwę lub adres IP urządzenia HoloLens. Jeśli nie znasz adresu IP urządzenia, zapoznaj się z tematem Ustawienia > Sieć & opcje zaawansowane internetu > lub zapytaj Cortanę "Hej Cortana, Jaki jest mój adres IP?"
    • Pozostaw opcję Tryb uwierzytelniania ustawiony na Wartość Uniwersalna.
    • Kliknij pozycję Wybierz
  • Kliknij przycisk Rozpocznij debugowanie > bez debugowania lub naciśnij klawisze Ctrl + F5. Jeśli jest to pierwsze wdrożenie na urządzeniu, należy go sparować z programem Visual Studio.
  • Umieść urządzenie HoloLens i znajdź hologram Usługi EnergyHub.

Rozdział 2 — Interakcja

W tym rozdziale będziemy korzystać z naszych hologramów. Najpierw dodamy kursor, aby zwizualizować nasze spojrzenie. Następnie dodamy gesty i użyjemy naszej ręki, aby umieścić nasze hologramy w przestrzeni.

Cele

  • Użyj danych wejściowych spojrzenia, aby kontrolować kursor.
  • Użyj danych wejściowych gestów, aby wchodzić w interakcje z hologramami.

Instrukcje

Spojrzenie

  • W panelu Hierarchia wybierz obiekt HologramCollection .
  • W panelu Inspektor kliknij przycisk Dodaj składnik .
  • W menu wpisz ciąg w polu wyszukiwania Gaze Manager. Wybierz wynik wyszukiwania.
  • W folderze HoloToolkit-Sharing-240\Prefabs\Input znajdź element zawartości Kursor .
  • Przeciągnij i upuść zasób Kursor na hierarchię.

Gest

  • Na panelu Hierarchy (Hierarchia ) wybierz obiekt HologramCollection .
  • Kliknij przycisk Dodaj składnik i wpisz Gest Manager w polu wyszukiwania. Wybierz wynik wyszukiwania.
  • W panelu Hierarchy (Hierarchia) rozwiń węzeł HologramCollection.
  • Wybierz podrzędny obiekt EnergyHub .
  • Na panelu Inspector (Inspektor ) kliknij przycisk Add Component (Dodaj składnik ).
  • W menu wpisz w polu wyszukiwania Hologram Placement . Wybierz wynik wyszukiwania.
  • Zapisz scenę, wybierając pozycję Plik > Zapisz scenę.

Wdrażanie i ciesz się

  • Skompiluj i wdróż na urządzeniu HoloLens, korzystając z instrukcji z poprzedniego rozdziału.
  • Gdy aplikacja zostanie uruchomiona na urządzeniu HoloLens, posuń głowę i zwróć uwagę na to, jak usługa EnergyHub podąża za spojrzeniem.
  • Zwróć uwagę, że kursor pojawia się podczas patrzenia na hologram i zmienia się na światło punktu, gdy nie patrzy na hologram.
  • Wykonaj naciśnięcie powietrza, aby umieścić hologram. W tym czasie w naszym projekcie można umieścić hologram tylko raz (ponownie wdrożyć, aby spróbować ponownie).

Rozdział 3 — współdzielone współrzędne

Dobrze jest zobaczyć hologramy i wchodzić z nimi w interakcje, ale przejdźmy dalej. Skonfigurujemy nasze pierwsze wspólne środowisko — hologram, który wszyscy będą mogli zobaczyć razem.

Cele

  • Skonfiguruj sieć na potrzeby środowiska udostępnionego.
  • Ustanów wspólny punkt odniesienia.
  • Współużytkuj systemy współrzędnych między urządzeniami.
  • Każdy widzi ten sam hologram!

Uwaga

Możliwości InternetClientServer i PrivateNetworkClientServer muszą być zadeklarowane, aby aplikacja łączyła się z serwerem udostępniania. Jest to wykonywane już w hologramach 240, ale pamiętaj o tym we własnych projektach.

  1. W edytorze aparatu Unity przejdź do ustawień odtwarzacza, przechodząc do pozycji "Edytuj > odtwarzacz ustawień > projektu"
  2. Kliknij kartę "Sklep Windows"
  3. W sekcji "Możliwości ustawień > publikowania" sprawdź możliwości InternetClientServer i możliwości PrivateNetworkClientServer

Instrukcje

  • W panelu Projekt przejdź do folderu HoloToolkit-Sharing-240\Prefabs\Sharing .
  • Przeciągnij i upuść prefabryk udostępniania na panelu Hierarchia.

Następnie musimy uruchomić usługę udostępniania. Ten krok wymaga tylko jednego komputera w środowisku udostępnionym.

  • W amencie Unity — w menu u góry wybierz menu HoloToolkit-Sharing-240.
  • Wybierz element Launch Sharing Service (Uruchom usługę udostępniania ) z listy rozwijanej.
  • Zaznacz opcję Sieć prywatna i kliknij pozycję Zezwalaj na dostęp po wyświetleniu monitu o zaporę.
  • Zanotuj adres IPv4 wyświetlany w oknie konsoli usługi udostępniania. Jest to ten sam adres IP co maszyna, na których jest uruchamiana usługa.

Postępuj zgodnie z pozostałymi instrukcjami na wszystkich komputerach , które dołączą do środowiska udostępnionego.

  • W obszarze Hierarchy (Hierarchia) wybierz obiekt Sharing (Udostępnianie ).
  • W inspektorze w składniku Etap udostępniania zmień adres serwera z "localhost" na adres IPv4 maszyny z systemem SharingService.exe.
  • W obszarze Hierarchy wybierz obiekt HologramCollection .
  • W inspektorze kliknij przycisk Dodaj składnik .
  • W polu wyszukiwania wpisz Import Export Anchor Manager. Wybierz wynik wyszukiwania.
  • W panelu Project (Projekt ) przejdź do folderu Scripts (Skrypty ).
  • Kliknij dwukrotnie skrypt HologramPlacement , aby otworzyć go w programie Visual Studio.
  • Zastąp zawartość poniższym kodem.
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.
    }
}
  • Po powrocie do aparatu Unity wybierz element HologramCollection w panelu Hierarchy (Hierarchia).
  • Na panelu Inspector (Inspektor ) kliknij przycisk Add Component (Dodaj składnik ).
  • W menu wpisz ciąg w polu wyszukiwania App State Manager. Wybierz wynik wyszukiwania.

Wdrażanie i ciesz się

  • Skompiluj projekt dla urządzeń HoloLens.
  • Wyznaczanie jednego urządzenia HoloLens do wdrożenia w pierwszej kolejności. Musisz poczekać na przekazanie kotwicy do usługi, zanim będzie można umieścić usługę EnergyHub (może to potrwać ok. 30–60 sekund). Dopóki przekazywanie nie zostanie ukończone, gesty naciśnięcia zostaną zignorowane.
  • Po umieszczeniu usługi EnergyHub jego lokalizacja zostanie przekazana do usługi, a następnie będzie można wdrożyć je na wszystkich innych urządzeniach HoloLens.
  • Gdy nowy urządzenie HoloLens po raz pierwszy dołączy do sesji, lokalizacja usługi EnergyHub może nie być poprawna na tym urządzeniu. Jednak gdy tylko lokalizacje kotwicy i energyhub zostały pobrane z usługi, usługa EnergyHub powinna przejść do nowej, udostępnionej lokalizacji. Jeśli tak się nie stanie w ciągu około 30–60 sekund, przejdź do miejsca, w którym oryginalne urządzenie HoloLens było podczas ustawiania kotwicy w celu zebrania większej liczby wskazówek dotyczących środowiska. Jeśli lokalizacja nadal nie jest włączona, wdróż je ponownie na urządzeniu.
  • Gdy wszystkie urządzenia są gotowe i działają w aplikacji, poszukaj usługi EnergyHub. Czy wszyscy mogą wyrazić zgodę na lokalizację hologramu i kierunek kierowania tekstem?

Rozdział 4 — Odnajdywanie

Każdy może teraz zobaczyć ten sam hologram! Teraz zobaczmy, jak wszyscy inni są połączeni z naszym wspólnym światem holograficznym. W tym rozdziale zajmiemy się lokalizacją głowy i rotacją wszystkich innych urządzeń HoloLens w tej samej sesji udostępniania.

Cele

  • Odkryj się nawzajem w naszym wspólnym środowisku.
  • Wybierz i udostępnij awatar gracza.
  • Dołącz awatar gracza obok głowy wszystkich.

Instrukcje

  • W panelu Project (Projekt ) przejdź do folderu Holograms .
  • Przeciągnij i upuść element PlayerAvatarStore do hierarchii.
  • W panelu Project (Projekt ) przejdź do folderu Scripts (Skrypty ).
  • Kliknij dwukrotnie skrypt AvatarSelector , aby otworzyć go w programie Visual Studio.
  • Zastąp zawartość poniższym kodem.
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;
    }
}
  • W obszarze Hierarchy wybierz obiekt HologramCollection .
  • W inspektorze kliknij pozycję Dodaj składnik.
  • W polu wyszukiwania wpisz Local Player Manager. Wybierz wynik wyszukiwania.
  • W obszarze Hierarchy wybierz obiekt HologramCollection .
  • W inspektorze kliknij pozycję Dodaj składnik.
  • W polu wyszukiwania wpisz Remote Player Manager. Wybierz wynik wyszukiwania.
  • Otwórz skrypt HologramPlacement w programie Visual Studio.
  • Zastąp zawartość poniższym kodem.
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.
    }
}
  • Otwórz skrypt AppStateManager w programie Visual Studio.
  • Zastąp zawartość poniższym kodem.
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;
        }
    }
}

Wdrażanie i ciesz się

  • Skompiluj i wdróż projekt na urządzeniach HoloLens.
  • Gdy usłyszysz dźwięk pingingu, znajdź menu wyboru awatara i wybierz awatar z gestem naciśnięcia powietrza.
  • Jeśli nie patrzysz na żadne hologramy, punkt światła wokół kursora zmieni inny kolor, gdy urządzenie HoloLens komunikuje się z usługą: inicjowanie (ciemno fioletowy), pobieranie kotwicy (zielony), importowanie/eksportowanie danych lokalizacji (żółty), przekazanie kotwicy (niebieski). Jeśli światło punktu wokół kursora jest domyślnym kolorem (jasno fioletowy), możesz rozpocząć interakcję z innymi graczami w sesji!
  • Spójrz na innych ludzi połączonych z twoją przestrzenią - nie będzie robot holograficzny unoszący się nad ramieniem i naśladując swoje ruchy głowy!

Rozdział 5 — Umieszczanie

W tym rozdziale sprawimy, że kotwica będzie mogła być umieszczona na rzeczywistych powierzchniach. Użyjemy współużytkowanych współrzędnych, aby umieścić kotwicę w środku punktu między wszystkimi połączonymi ze wspólnym środowiskiem.

Cele

  • Umieść hologramy w siatce mapowania przestrzennego na podstawie pozycji głowy graczy.

Instrukcje

  • W panelu Project (Projekt ) przejdź do folderu Holograms .
  • Przeciągnij i upuść prefabryk CustomSpatialMapping na hierarchię.
  • W panelu Project (Projekt ) przejdź do folderu Scripts (Skrypty ).
  • Kliknij dwukrotnie skrypt AppStateManager , aby otworzyć go w programie Visual Studio.
  • Zastąp zawartość poniższym kodem.
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;
        }
    }
}
  • W panelu Project (Projekt ) przejdź do folderu Scripts (Skrypty ).
  • Kliknij dwukrotnie skrypt HologramPlacement , aby otworzyć go w programie Visual Studio.
  • Zastąp zawartość poniższym kodem.
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();
    }
}

Wdrażanie i ciesz się

  • Skompiluj i wdróż projekt na urządzeniach HoloLens.
  • Gdy aplikacja jest gotowa, stań w kręgu i zwróć uwagę na to, jak usługa EnergyHub pojawia się w centrum wszystkich.
  • Naciśnij, aby umieścić usługę EnergyHub.
  • Spróbuj wykonać polecenie głosowe "Resetuj element docelowy", aby wybrać kopię zapasową usługi EnergyHub i pracować razem jako grupa, aby przenieść hologram do nowej lokalizacji.

Rozdział 6 — fizyka Real-World

W tym rozdziale dodamy hologramy, które odbijają się od rzeczywistych powierzchni. Oglądaj, jak twoja przestrzeń wypełnia się projektami uruchomionymi zarówno przez Ciebie, jak i Twoich przyjaciół!

Cele

  • Wystrzelij pociski, które odbijają się od rzeczywistych powierzchni.
  • Udostępnij pociski, aby inni gracze mogli je zobaczyć.

Instrukcje

  • W obszarze Hierarchy wybierz obiekt HologramCollection .
  • W inspektorze kliknij pozycję Dodaj składnik.
  • W polu wyszukiwania wpisz Projectile Launcher. Wybierz wynik wyszukiwania.

Wdrażanie i ciesz się

  • Kompilowanie i wdrażanie na urządzeniach HoloLens.
  • Gdy aplikacja jest uruchomiona na wszystkich urządzeniach, wykonaj naciśnięcie powietrza, aby uruchomić pocisk na rzeczywistych powierzchniach.
  • Zobacz, co się stanie, gdy pocisk zderza się z awatarem innego gracza!

Rozdział 7 — Wielki finał

W tym rozdziale odkryjemy portal, który można odnaleźć tylko we współpracy.

Cele

  • Współpracuj, aby uruchomić wystarczająco dużo pocisków na kotwicy, aby odkryć tajny portal!

Instrukcje

  • W panelu Project (Projekt ) przejdź do folderu Holograms .
  • Przeciągnij i upuść element zawartości Underworld jako element podrzędny hologramCollection.
  • Po wybraniu elementu HologramCollection kliknij przycisk Dodaj składnik w inspektorze.
  • W menu wpisz w polu wyszukiwania ExplodeTarget. Wybierz wynik wyszukiwania.
  • Po wybraniu elementu HologramCollection przeciągnij obiekt EnergyHub z hierarchii do pola Target w inspektorze.
  • Po wybraniu elementu HologramCollection przeciągnij obiekt Underworld z hierarchii do pola Underworld w inspektorze.

Wdrażanie i ciesz się

  • Kompilowanie i wdrażanie na urządzeniach HoloLens.
  • Po uruchomieniu aplikacji współpracują ze sobą, aby uruchamiać pociski w usłudze EnergyHub.
  • Kiedy pojawi się półświatka, wystrzelij pociski w robotach underworld (uderzył robota trzy razy w dodatkową zabawę).