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
- Komputer Windows 10 skonfigurowany z odpowiednimi narzędziami zainstalowanymi z dostępem do Internetu.
- Co najmniej dwa urządzenia HoloLens skonfigurowane do programowania.
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.
- W edytorze aparatu Unity przejdź do ustawień odtwarzacza, przechodząc do pozycji "Edytuj > odtwarzacz ustawień > projektu"
- Kliknij kartę "Sklep Windows"
- 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ę).