Erste Schritte mit einem Mixed Reality Platformer mit Microsoft HoloLens


holoheader
Das Plattformspielgenre hat sich konstant weiterentwickelt – von den ersten Varianten wie Donkey Kong und Pitfall bis hin zu neueren Spielen wie Flappy Bird. Super Mario Bros. von Shigeru Miyamoto wird allgemein als das beste Plattformspiel aller Zeiten anerkannt und hat für seine Nachfolger hohe Maßstäbe gesetzt. Die Lara-Croft-Serie baute auf Shigerus Innovationen auf, indem sie den „Side-scrolling Plattformer“ in eine 3D-Welt übersetzte. Durch Mixed Reality und HoloLens haben wir nun erneut die Möglichkeit, die Welt der Plattformspiele zu erweitern.

img-1

Die Standardkonventionen von Videospielen ändern sich grundlegend, wenn man einen Plattformer in eine Mixed-Reality-Umgebung überträgt. Erstens folgt man seinem Charakter physisch bei seinen Bewegungen in der Wirklichkeit, anstatt auf einem Stuhl zu sitzen und den Charakter auf dem Bildschirm hin- und her zu bewegen. Zweitens wird der Protagonist nicht nur mit digitalen Hindernissen konfrontiert, sondern auch mit physischen Objekten in der Wirklichkeit, wie z.B. Tischen und Stühlen und Stapeln von Büchern. Drittens wird jeder Raum, in dem Sie spielen, effektiv zu einem neuen Level. Das bedeutet, dass dem Mixed-Reality-Plattformspiel nie die Levels ausgehen und dass jedes Level eigene Herausforderungen birgt. Anstatt dass man Punkte pro Spielabschnitt vergleicht, vergleicht man, wie gut man im Wohnzimmer zurechtgekommen ist – oder in Janes Küche oder Shigerus Keller.

In diesem Post zeigen wir, wie man anfängt, ein Plattformspiel für die HoloLens zu programmieren, wobei ausschließlich kostenfreie Assets zum Einsatz kommen. Dabei erläutern wir die Grundlagen von Spatial Mapping, mit dem ein Raum eingelesen werden kann, damit der Spielercharakter mit ihm interagieren kann. Außerdem benutzen wir die etwas weiter fortgeschrittenen Merkmale von Spatial Understanding, um die Eigenschaften der Spielumgebung festzulegen. Das Ganze geschieht in der Unity IDE (aktuell 5.5.0f3) mit dem Open-Source-HoloToolkit.

Aufbau der Spielwelt mit Spatial Mapping

Wie ermöglicht HoloLens die Interaktion zwischen virtuellen und physischen Objekten? Das HoloLens-Gerät ist mit einer Tiefenbildkamera ausgestattet, ähnlich der Tiefenbildkamera bei Kinect v2. Die Kamera liest den Raum ein, um durch eine als „Spatial Mapping“ bezeichnete Technik eine räumliche Abbildung zu erstellen. Anhand dieser Daten über die Wirklichkeit erstellt HoloLens dann 3D-Oberflächen in der virtuellen Welt.

Anschließend benutzt es seine vier Umgebungskameras, um die 3D-Abbildung des Raums am Spieler auszurichten. Die Abbildung wird beim Start der HoloLens-Anwendung häufig als Gitternetz dargestellt, das über den Raum gelegt wird. Die Visualisierung lässt sich manchmal aufrufen, indem man einfach in die Luft vor sich tippt, während man die HoloLens trägt.

Um mit dem Spatial Mapping zu experimentieren, erstellen Sie ein neues 3D-Projekt in Unity. Sie können das Projekt „3D Platform Game“ nennen, wie in unserem Screenshot unten. Erstellen Sie für das Spiel eine neue Szene und nennen Sie sie „main“.

img-3Fügen Sie als Nächstes das HoloToolkit-Unity-Package zu Ihrer App hinzu. Sie können das Package vom GitHub-Repository des HoloToolkit-Projekts herunterladen. In dieser Anleitung wird HoloToolkit-Unity-v1.5.5.0.unitypackage benutzt. Wählen Sie in der Unity-IDE die Registerkarte Assets. Klicken Sie dann auf Import Package -> Custom Package und navigieren Sie dann zum Speicherort der heruntergeladenen HoloToolkit-Datei, um sie in die Szene zu importieren.

img-4

Das HoloToolkit bietet zahlreiche nützliche Hilfsfunktionen und Abkürzungen bei der Entwicklung von HoloLens-Anwendungen. Im HoloToolkit-Menü gibt es eine Option „Configure“, mit der Sie Ihr Spiel für die HoloLens ausrichten können. Nachdem Sie Ihre Szene und das Projekt gespeichert haben, klicken Sie auf jede dieser Optionen, um Ihre Szene, Ihr Projekt und Ihre Einstellungen unter „Capabilities“ zu konfigurieren. Bei den Capabilities müssen Sie SpatialPerception deaktivieren – anderenfalls funktioniert das Spatial Mapping nicht. Vergessen Sie nicht, nach jeder Änderung Ihr Projekt zu speichern. Falls Sie diesen Schritt aus irgendeinem Grund lieber manuell durchführen möchten, gibt es eine Dokumentation, die den Vorgang detailliert erklärt.

img-5

Um Ihr Spiel um Spatial Mapping zu ergänzen, verschieben Sie einfach per Drag&Drop das SpatialMapping-Prefab in Ihre Szene, und zwar über HoloToolkit > SpatialMapping > Prefabs. Wenn Sie Ihr Spiel jetzt erstellen und auf Ihrer HoloLens bzw. in Ihrem HoloLens Emulator bereitstellen, werden Sie das überlagernde Gitternetz sehen können, während die Oberflächenrekonstruktion erstellt wird.

Und damit haben Sie auch schon Ihr erstes Level erstellt.

Einen Protagonisten und einen Xbox-Controller hinzufügen

Im nächsten Schritt erstellen Sie Ihren Protagonisten. Wenn Sie das Glück haben, ein ausgerichtetes Mario- oder Luigi-Modell zu haben, sollten Sie es auf jeden Fall benutzen. Wie zuvor erwähnt, wollen wir uns in dieser Anleitung jedoch ausschließlich auf kostenfreie Assets verlassen und benutzen daher das kostenlose Ethan-Asset.

img-6

Wählen Sie im Unity-Menü Assets -> Import Package -> Characters. Kopieren Sie das gesamte Paket in Ihr Spiel, indem Sie auf Import klicken. Verschieben Sie anschließend per Drag&Drop das ThirdPersonController-Prefab unter Assets -> Standard Assets -> Characters -> ThirdPersonCharacter -> Prefabs in Ihre Szene.

Als Nächstes brauchen Sie einen Bluetooth-Controller, um Ihren Charakter zu steuern. Neuere Xbox One-Controller unterstützen Bluetooth. Um den Controller für das Zusammenspiel mit der HoloLens einzurichten, befolgen Sie genau die folgenden Hinweise, um die Firmware auf Ihrem Controller zu aktualisieren. Koppeln Sie anschließend den Controller mit der HoloLens, indem Sie den entsprechenden Eintrag im Menü Settings -> Devices wählen.

img-7

Um den Xbox One-Controller in Ihrem Spiel zu unterstützen, sollten Sie ein weiteres kostenfreies Asset hinzufügen. Öffnen Sie den Asset Store, indem Sie auf Window > Asset Store klicken und dann Xbox Controller Input for HoloLens suchen. Importieren Sie dieses Paket in Ihr Projekt.

Mit ein klein wenig angepasstem Skript, können Sie dieses Ihrem Charakter hinzufügen. Wählen Sie dafür in Ihrer Szene das ThirdPersonController-Prefab aus. Suchen Sie im Prüffenster das Third Person User Control-Skript und löschen Sie es. Sie werden selbst Ihr eigenes Steuerelement schreiben, das von dem soeben von Ihnen importierten Xbox Controller-Paket abhängt.

Blättern Sie im Prüffenster nach unten und klicken Sie dann auf Add Component -> New Script. Nennen Sie Ihr Skript ThirdPersonHoloLensControl und kopieren Sie per Copy-Paste den folgenden Code in das Skript:

using UnityEngine;
using HoloLensXboxController;
using UnityStandardAssets.Characters.ThirdPerson;
 
public class ThirdPersonHoloLensControl : MonoBehaviour
{
 
    private ControllerInput controllerInput;
    private ThirdPersonCharacter m_Character;
    private Transform m_Cam;                
    private Vector3 m_CamForward;            
    private Vector3 m_Move;
    private bool m_Jump;                      
 
    public float RotateAroundYSpeed = 2.0f;
    public float RotateAroundXSpeed = 2.0f;
    public float RotateAroundZSpeed = 2.0f;
 
    public float MoveHorizontalSpeed = 1f;
    public float MoveVerticalSpeed = 1f;
 
    public float ScaleSpeed = 1f;
 
 
    void Start()
    {
        controllerInput = new ControllerInput(0, 0.19f);
        // get the transform of the main camera
        if (Camera.main != null)
        {
            m_Cam = Camera.main.transform;
        }
 
        m_Character = GetComponent<ThirdPersonCharacter>();
    }
 
    // Update is called once per frame
    void Update()
    {
        controllerInput.Update();
        if (!m_Jump)
        {
            m_Jump = controllerInput.GetButton(ControllerButton.A);
        }
    }
 
 
    private void FixedUpdate()
    {
        // read inputs
        float h = MoveHorizontalSpeed * controllerInput.GetAxisLeftThumbstickX();
        float v = MoveVerticalSpeed * controllerInput.GetAxisLeftThumbstickY();
        bool crouch = controllerInput.GetButton(ControllerButton.B);
 
        // calculate move direction to pass to character
        if (m_Cam != null)
        {
            // calculate camera relative direction to move:
            m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
            m_Move = v * m_CamForward + h * m_Cam.right;
        }
 
 
        // pass all parameters to the character control script
        m_Character.Move(m_Move, crouch, m_Jump);
        m_Jump = false;
    }
}

Bei diesem Code handelt es sich um eine Variante des Standard-Controller-Codes. Nachdem er angefügt ist, können Sie einen Bluetooth-fähigen Xbox One-Controller benutzen, um Ihren Charakter zu steuern. Benutzen Sie die A-Taste, um zu springen. Benutzen Sie die B-Taste, um in die Hocke zu gehen.

Sie haben jetzt ein erstes Level und einen Spielercharakter, den Sie mit einem Controller steuern können – also so ziemlich alles, was man für ein Plattformspiel benötigt. Wenn Sie das Projekt jedoch in diesem Zustand bereitstellen, werden Sie feststellen, dass es ein kleines Problem gibt: Ihr Charakter fällt durch den Boden.

Dies liegt daran, dass der Charakter zwar sofort erscheint, wenn die Szene gestartet wird, das Scannen des Raums und Erstellen der Gitternetze für den Boden jedoch etwas Zeit in Anspruch nimmt. Wenn der Charakter erscheint, bevor die Gitternetze in der Szene platziert wurden, fällt er einfach durch den Boden – und fällt immer weiter, weil es kein Gitter gibt, das ihn aufhalten könnte.

Wie wäre es mit ein wenig Raumverständnis?

Um dies zu vermeiden, benötigt die Anwendung bessere Raumregeln. Sie muss warten, bis die Gitternetze größtenteils vollständig sind, bevor sie den Charakter in die Szene einfügt. Die Anwendung sollte auch den Raum scannen und den Boden finden, damit der Charakter sachte eingefügt werden kann, anstatt einfach in den Raum zu fallen. Das Spatial Understanding-Prefab wird Ihnen helfen, diese beiden Anforderungen zu erfüllen.

img-8

Fügen Sie das Spatial Understanding-Prefab für räumliches Verständnis in Ihre Szene ein. Sie finden es unter Assets > HoloToolkit > SpatialUnderstanding > Prefabs.

Da das SpatialUnderstanding-Spielobjekt beim Scanvorgang auch ein Drahtmodell zeichnet, sollten Sie das vom SpatialMapping-Spielobjekt benutzte visuelle Gitternetz deaktivieren, indem Sie die Auswahl von Draw Visual Mesh im Spatial Mapping-Managerskript aufheben. Hierzu wählen Sie das SpatialMapping-Spielobjekt aus, suchen im Prüffenster den Spatial Mapping-Manager und entfernen das Häkchen bei Draw Visual Mesh.

Als Nächstes gilt es, das Spiel um etwas Orchestrierung zu ergänzen, um zu verhindern, dass der Third-Person-Charakter zu früh eingefügt wird. Wählen Sie in Ihrer Szene ThirdPersonController. Wechseln Sie dann zum Prüfbereich und klicken Sie dort auf Add Component -> New Script. Nennen Sie Ihr Skript OrchestrateGame. Obwohl dieses Skript an jedem beliebigen Ort abgelegt werden könnte, ist es sinnvoll, es an den ThirdPersonController anzuhängen. Damit wird es für Sie leichter, die Eigenschaften Ihres Charakters zu ändern.

Beginnen Sie, indem Sie HideCharacter- und ShowCharacter-Methoden zur Klasse OrchestrateGame hinzufügen. Damit können Sie Ihren Charakter unsichtbar werden lassen, bis Sie bereit sind, ihn zum Spiel-Level (dem Raum) hinzuzufügen.

private void ShowCharacter(Vector3 placement)
{
    var ethanBody = GameObject.Find("EthanBody");
    ethanBody.GetComponent<SkinnedMeshRenderer>().enabled = true;
    m_Character.transform.position = placement;
    var rigidBody = GetComponent<Rigidbody>();
    rigidBody.angularVelocity = Vector3.zero;
    rigidBody.velocity = Vector3.zero;        
}
 
private void HideCharacter()
{
    var ethanBody = GameObject.Find("EthanBody");
    ethanBody.GetComponent<SkinnedMeshRenderer>().enabled = false;
}

Wenn das Spiel startet, blenden Sie den Charakter vorerst aus. Wichtiger noch, Sie haken sich beim SpatialUnderstanding-Singleton ein und behandeln sein ScanStateChanged-Ereignis. Nachdem der Scanvorgang abgeschlossen ist, benutzen Sie das räumliche Verständnis, um den Charakter korrekt zu platzieren.

private ThirdPersonCharacter m_Character;
 
void Start()
{
    m_Character = GetComponent<ThirdPersonCharacter>();
    SpatialUnderstanding.Instance.ScanStateChanged += Instance_ScanStateChanged;
    HideCharacter();
}
private void Instance_ScanStateChanged()
{
    if ((SpatialUnderstanding.Instance.ScanState == SpatialUnderstanding.ScanStates.Done) &&
SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
     {
        PlaceCharacterInGame();
    }
}

Wie entscheiden Sie, wann der Scan abgeschlossen ist? Sie könnten einen Zeitgeber einrichten und abwarten, bis eine vorgegebene Zeitspanne abgelaufen ist. Aber dies könnte zu inkonsistenten Ergebnissen führen. Besser ist es, Sie benutzen die Raumverständnis-Funktionalität des HoloToolkits.

Das Raumverständnis prüft kontinuierlich Oberflächen, die von der Spatial Mapping-Komponente erfasst werden. Sie legen einen Grenzwert fest, ab dem genügend Rauminformationen gesammelt wurden. Bei jedem Aufruf der Update-Methode lassen Sie prüfen, ob der vom Raumverständnis-Modul bestimmte Grenzwert erreicht wurde. Wenn ja, können Sie die RequestFinishScan-Methode für SpatialUnderstanding aufrufen, um den Scanvorgang zu beenden und den ScanState auf Abgeschlossen zu setzen.

private bool m_isInitialized;
    public float kMinAreaForComplete = 50.0f;
    public float kMinHorizAreaForComplete = 25.0f;
    public float kMinWallAreaForComplete = 10.0f;
    // Update is called once per frame
    void Update()
    {
        // check if enough of the room is scanned
        if (!m_isInitialized && DoesScanMeetMinBarForCompletion)
        {
            // let service know we're done scanning
            SpatialUnderstanding.Instance.RequestFinishScan();
            m_isInitialized = true;
        }
    }
 
    public bool DoesScanMeetMinBarForCompletion
    {
        get
        {
            // Only allow this when we are actually scanning
            if ((SpatialUnderstanding.Instance.ScanState != SpatialUnderstanding.ScanStates.Scanning) ||
                (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding))
            {
                return false;
            }
 
            // Query the current playspace stats
            IntPtr statsPtr = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceStatsPtr();
            if (SpatialUnderstandingDll.Imports.QueryPlayspaceStats(statsPtr) == 0)
            {
                return false;
            }
            SpatialUnderstandingDll.Imports.PlayspaceStats stats = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceStats();
 
            // Check our preset requirements
            if ((stats.TotalSurfaceArea > kMinAreaForComplete) ||
                (stats.HorizSurfaceArea > kMinHorizAreaForComplete) ||
                (stats.WallSurfaceArea > kMinWallAreaForComplete))
            {
                return true;
            }
            return false;
        }
    }

Nachdem anhand des Raumverständnisses etabliert wurde, dass genügend von dem Raum eingelesen wurde, um den Level zu starten, können Sie das Raumverständnis ein weiteres Mal einsetzen, um zu bestimmen, wo Ihr Protagonist platziert werden soll. Als erstes versucht die unten dargestellte PlaceCharacterInGame-Methode die Y-Koordinate des Raumbodens zu bestimmen. Als Nächstes wird das Hauptkameraobjekt benutzt, um zu bestimmen, in welche Richtung die HoloLens ausgerichtet ist und um dadurch eine Koordinatenposition zwei Meter vor der HoloLens zu bestimmen. Die Position wird mit der Y-Koordinate des Bodens kombiniert, um den Charakter sachte auf dem Boden vor dem Spieler zu platzieren.

private void PlaceCharacterInGame()
{
// use spatial understanding to find floor
SpatialUnderstandingDll.Imports.QueryPlayspaceAlignment(SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignmentPtr());
SpatialUnderstandingDll.Imports.PlayspaceAlignment alignment = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignment();
 
// find 2 meters in front of camera position
var inFrontOfCamera = Camera.main.transform.position + Camera.main.transform.forward * 2.0f;
 
// place character on floor 2 meters ahead
ShowCharacter(new Vector3(inFrontOfCamera.x, alignment.FloorYValue, 2.69f));
 
// hide mesh
var customMesh = SpatialUnderstanding.Instance.GetComponent<SpatialUnderstandingCustomMesh>();
customMesh.DrawProcessedMesh = false;
}

Sie schließen die PlaceCharacterInGame-Methode ab, indem Sie die Gitternetze für den Spieler unsichtbar machen. Dies verstärkt den Eindruck, dass Ihr Protagonist gegen Objekte in der reellen Welt rennt bzw. diese überspringt. Das letzte Element, das benötigt wird, um das Spiel abzuschließen, ist Level Design. Leider ist dies zu komplex, als dass wir es auf dieser Plattform behandeln könnten.

image-10

Da dieses Plattformspiel in Mixed Reality entwickelt wurde, haben Sie jedoch beim Entwurf des Levels eine interessante Entscheidung zu treffen: Sie können entweder anhand von 3D-Modellen auf die herkömmliche Weise Levels entwerfen, oder aber dafür Objekte in der reellen Welt benutzen, zwischen denen der Charakter hindurchlaufen bzw. über die er springen muss. Der beste Ansatz besteht möglicherweise darin, die beiden zu kombinieren.

Fazit

Frei nach Shakespeare: Die ganze Welt ist Bühne und jeder Raum ein Level. Mit Mixed Reality können wir neue Welten erschaffen. Sie erlaubt es uns darüber hinaus auch, die bereits existierenden kulturellen Artefakte und Konventionen, wie etwa traditionelle Plattformspiele, aus einem völlig neuen Blickwinkel zu betrachten. Wo es bei Virtual Reality vor allem um Eskapismus geht, liegt das Geheimnis von Mixed Reality wohl darin, dass es uns dazu bringt, das zu schätzen, was wir bereits haben – indem es uns neue Augen gibt, um es zu betrachten.

Comments (0)

Skip to main content