BI-PGA Programování grafických aplikací
Jdi na navigaci předmětu

Unity XR Interaction Toolkit - základní metody pohybu a orientace

  • Ukážeme si přesný postup i s vysvětlením jak vytvořit/zprovoznit několik standartních způsobů pohybu a orientace v Unity aplikacích virtuální reality. Využijeme k tomu hlavně komponenty a logiku Unity Open XR Toolkitu.

Požadavky:

  • Unity 2020.2+
  • Projekt s nainstalovaným a nakonfigurovaným OpenXR Pluginem
  • Základní znalost prostředí Unity a syntaxe jazyku C#

Jdeme na to!

  • Nejdříve přidáme do scény XR Rig - tedy objekt, který se stará o zpracování gyroskopických a pozičních dat připojeného headsetu tak, že se promítnou na pozicích herních objektů, které reprezentují pozice a rotace hlavy (kamery) a ovladačů. Dále jako zdroj "kamery", která se použije při renderu na display headsetu.
    • V hierarchii scény: Kontextové menu > XR > XR Rig (action-based)

01

(Pozn.) V momentální verzi Unity (2020.3.2) a XR Toolkitu (1.0.0) se XR Rig vytvoří s již předpřipravenou a nakonfigurovanou strukturou Kamery, levého a pravého Ovladače. Objekty ovladačů již mají komponenty XR Controller a XR Ray Interactor, což je něco co později využijeme, ale vysvětlení jejich funkčnosti není obsahem tohoto příkladu.

  • Předtím, než můžeme začít s jednotlivými metodami pohybu, potřebujeme do kořenového objektu našeho Rigu (XR Rig) přidat dvě komponenty: Input Action Manager a Locomotion System

02

  • Input Action Manager - Defaultně náš Rig nereaguje nic (na žádná tlačítka ani pozice joysticků na ovladačích atd.). Tato komponenta umožnůje určit, na kterou sadu "vstupů" bude tento objekt reagovat a zpracovávat jejich callbacky.
    • Aby jsme mohli zpracovávat/příjmat vstupy, musíme do pole přidat nový element a přiřadit do něj nějaký již nakonfiguravaný seznam akcí (novéhu Unity Input systému) - V tomto případě přiřadíme Defaultní XR Input (Importovaný sample z XR Interaction Toolkit package)

03

  • Locomotion System - Objekt, který zajištuje, že Locomotion providery získávají přístup k provedení změně lokace/rotace Rigu jen po jednom. Tedy spravuje exkluzivní přístup k našeho rigu.

Locomotion Providery

  • Komponenty, které implementují různé druhy pohybu pro rig
  • Samotná třída Locomotion Provider je abstraktním základem pro takovéto implementace

Action-based vs Device-based

  • V této fázi by stálo za to vysvětlit, co vlastně to Action-based a Device-based označení znamená. Víceméně to označuje jen to, jak komponenta získává vstup.
  • Novější Action-based komponenty využívají způsobu získávání vstupu pomocí nového Unity Action Input systému. Zatímco starší Device-based používají "primitivnější" způsob přímého čtení vstupních hodnot.
  • V tomto příkladu budu používat primárně Action-based komponenty, jelikož se jedná o novější a doporučovaný způsob.
  • Pro účely prototypování není problémem využívat komponent obou druhů, ale ve finálním produktu by to mohlo způsobit těžký guláš.

Turn Providery

  • Nejprve si přidáme prvky, které pomohou s orientací v našem virtuálním prostředí - s otáčením. Ne každý uživatel virtuální reality má prostor na to se ve svém prostředí pohybovat nebo otáčet (např. při sezení). Proto skoro v každé aplikaci pro virtuální realitu nalezneme funkčnost pro otáčení naší virtuální postavou bez nutnosti otáčení v reálném světě. OpenXR Toolkit nám nabízí dvě předpřipravené komponenty: Snap Turn Provider a Continuous Turn Provider.
    • Snap Turn - instantní rotace. Continuous Turn - plynulá rotace při nepřetřitém Inputu
    • Obě komponenty mají skoro stejné atributy:
      • System - Reference na Locomotion System. Pokuď není vyplněná, pokusí se při startu získat validní komponentu na stejném objektu
      • Turn Amount - Úhel ve stupních, o kolik se rig "otočí"
      • Debounce Time - Čas v sekundách, který musíme počkat, než se můžeme znovu otočit
      • Enable Turn Left/Right - Povolit otáčení o +-Turn Amount "ve směru vstupu"
      • Enable Turn Around - Povolit otočení o 180 stupnů (pro zvláštní vstupní hodnotu)
      • Reference - Který z ovladačů budeme používat pro input a jaká z akcí Input systému provede "otočení"
  • Pro tento příklad přidáme Snap Turn Provider(action-based) s referencí vstupu na pravý ovladač (standartní layout - pohyb na levém ovladači a rotace na pravém) - při použití Defaultních Input akcí nás bude pravý joystick otáčet o Turn Amount v horizontálí poloze a o 180 stupnů při dolní poloze joysticku.

04

  • V této chvíli by mělo smysl dosavadní pokrok vyzkoušet a případně vyladit vzniklé chyby.

Metody pohybu

  • Existuje spoustu metod, jakými se může uživatel ve virtuálním prostředí pohybovat (pomocí ovladačů). Těmi nejzákladnějšími a nejvíce používanými, na které se podíváme, jsou tkzv. "umělé"-tedy nahrazují fyzický pohyb uživatele joystickem. To jsou: Teleportace a Plynulý pohyb.
  • Mezi další měně typické patří fyzikální způsoby jako jsou např.: Arm Swinger, "Drag"( např. v aplikaci/hře Gorilla Tag - doporučuji vyzkoušet ;) )
  • OpenXR nabízí již hotové implementace pro Teleportaci a plynulý pohyb. A na ty se teď podíváme.

Teleportation Provider

  • Asi nejjednodušší princip na pochopení: "Zmáčknu tlačítko a přesune mě to na místo, kam právě ukazuju".
  • Pro použití přidáme do našeho XR Rigu komponentu Teleportation Provider. Možná se zklamáním zjistíme, že nemá skoro žádné atributy…​

05

  • Pokuď se podíváme do kódu, tak zjistíme, že víceméně jediné co komponenta dělá, je samotná logika přesunu na nějakou danou pozici.
  • Další věcí, kterou potřebujeme je tkzv. "Teleportační oblast" - což je XR Interactible objekt, který "zdeleguje" našemu Teleportation provideru údaje k jeho "teleportačnímu eventu" (jakousi další spojnicí v tomto případě je komponenta XR Ray Interactor, která je součástí objektů ovladačů, kterou jsem zmínil výše a která zprostředkovává dálkovou interakci s Interaktable objekty)
  • Tu můžeme do scény přidat buď přes kontextovou nabídku > XR > Teleportation Area nebo jako komponentu již existujícímu objektu. Samotnou plochu, kde je možné teleportaci provést určují Collidery z atributu Colliders(pokuď nejsou žádné zvolené, skript se pokusí najít všechny dostupné na stejném objektu)
  • V tomto případě mám ve scéně plochu, která má Teleportation Area komponentu.

06

  • Dalším zajímavým atributem je Custom Reticle, což je objekt který se zobrazí na lokaci, kde koliduje XR Ray Interaktor s Teleportation Area Colliderem. Tedy ukazatel toho, kam se můžeme teleportovat.
  • Takže pokuď máme v XR Rigu přidaný Teleportation Provider, nějaký objekt s Teleportation Area a Colliderem a zkusíme aplika spustit, můžeme se teď na daném objektu teleportovat (Teleportace se defaultně aktivuje pomocí Activate akce, která je v Defaultním XR Inputu pro mě nastavená na gripButton)

teleport viz

V tomto případě jsem jako Custom Reticle použil dva do sebe vložené cylindry s průhledným materiálem a ještě jsem pozměnil vlastnosti XR Ray Interactoru.

Continuous Move Provider

  • Druhý nejčastěji používaný způsob pohybu. Typicky pomocí joysticku - velmi intuitivní. Říká se, že oproti teleportaci je "více imerzivní". Ve skutečnosti má větší vliv na imerzivní zážitek spíše prostředí a mechaniky samotné aplikace/hry, než způsob pohybu. Je to spíše na osobní preferenci.
  • Do XR Rigu přáme Continuous Move Provider(action-based) komponentu
    • Je tu hodně paralel s naším Snap Turn Providerem, co se atributů týče
      • Move Speed - Rychlost pohybu. Tady bych doporučil opatrnost. Plynulý pohyb ve virtuální realitě je něco co může některým lidem způsobit "závratě". Čím větší rychlost, tím spíše se někomu udělá špatně.
      • Enable Strafe - Povolujeme i pohyb bokem?
      • Forward Source - Směr pohybu - implicitně se nastaví na hlavní kameru Rigu.
  • Jak jsem se zmiňoval u Snap Turn Provideru - typicky se levý joystick/ovladač stará o pohyb a pravý o otáčení.

07

  • Když teď spustíme aplikaci, tak se můžeme pomocí levého joysticku hýbat. jupí.
  • Má to ale jednu vadu. Naše postava vůbec nerespektuje ani schody ani zdi a už vůbec né gravitaci.

clip2 clip

  • Naštěstí to můžeme docela snadno vyřešit přidáním Character Controller komponenty.

08

  • Character Controller - je nativní Unity komponenta
    • Umožňuje nám realizovat kolize s collidery a "simuluje" gravitaci bez nutnosti mít z objektu dynamický fyzikální objekt.
      • Což je většinou ve virtální realitě žádoucí. Není nic horšího, než když vám kamera udělá deset salt vzad kvůli nějaké nečekané fyzikální interakci
  • Když teď aplikaci spustíme, tak zjistíme, že už nás zdi nepustí skrz a naše "postava" konečně respektuje výšku. Ale pozor na okraj, teď už nám nic nebrání v pádu.

ramp

  • Je tu ale další, trochu jiná vada. Tentokrát nás náš Character Controller blokuje v případě, že se potřebujeme přikrčit do nízkého průchodu. A navíc je vždy vycentrovaný na pozici Rigu a ne na pozici naší kamery.

stuck stuck2

  • Jedním z řešení je přepočítávat výšku a pozici Character Controlleru podle hlavy/kamery našeho Rigu.
  • Na to již existuje OpenXR komponenta Character Controller Driver.
    • Její atributy Min/Max Height nám umožnují omezit velikosti, kterých může Character Controller dosáhnout.

09

  • A teď po spuštění už můžeme při skrčení projít. Dokonce se můžeme i pod něčím podplazit.

crouch

  • Bohužel nám tu pořád zůstal jeden problém. Character Controller Driver přepočítává Character Controller jen když se provádí locomotion event, tedy jen když se zrovna hýbeme pomocí (v našem případě) joysticku. Problém je v tom, že se pořád můžeme hýbat s headsetem v reálném prostředí (Objekt Rigu se nehýbe, když chodíme po místnosti v headsetu) a ve virtuálním prostředí tak můžeme např. projít zdí.
  • Nastal tedy čas napsat něco vlastního - vytvoříme si vlastní Character Controller Driver.
    • Chceme tedy aby se Character Controller pořád přepočítával na pozici kamery.
  • Budeme využívat funkčnosti XR Interactions Toolkitu a pro zjednodušení ladení vyžadujeme existenci Character Controller komponenty:

    using UnityEngine.XR.Interaction.Toolkit;
    
    [RequireComponent(typeof(CharacterController))]
    public class MyCharacterControllerDriver : MonoBehaviour
    {
  • Dále bude potřebovat atributy Rigu(pro získání informací o poloze kamery) a Character Controlleru(aby jsme ho mohli upravit), dále atributy pro omezení výšky

    public XRRig xrRig;
    public CharacterController characterController;
    
    public float minHeight = 0f;
    public float maxHeight = float.PositiveInfinity;
  • Samotný kód pro přepočítání polohy a výšky:

    void UpdateCharacterController()
    {
        if (xrRig is null || characterController is null)   // nemáme žádnou záruku, že atributy má přiřazené validní hodnoty
            return;
    
        // XRRig naštěstí obsahuje přesně to co potřebujeme - Character Controller má atribut _center_,
        // který označuje "offset" v prostoru, o který je posunutý oproti lokální pozici jeho "rodičovské" tranform komponenty.
        // Což je přesně i to, co nám _cameraInRigSpacePos_ říká, pozici kamery v prostoru rigu - tedy kameru v "lokální souřadnicové síti".
        Vector3 center = xrRig.cameraInRigSpacePos;
    
        // Stejně tak získáme i samostatně údaj o výšce, který akorát omezíme atributy min/max výšky.
        float height = Mathf.Clamp(xrRig.cameraInRigSpaceHeight, minHeight, maxHeight);
    
        // Ještě musíme snížit střed CharControlleru, aby kamera byla na vrcholu charControlleru.
        center.y = height / 2f;
    
        // A jen přiřadit získané hodnoty atributům CharacterControlleru.
        characterController.center = center;
        characterController.height = height;
    }
  • Ještě nám zbývá volat náš kód každý Update() a script je hotový:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    using UnityEngine.XR.Interaction.Toolkit;
    
    [RequireComponent(typeof(CharacterController))]
    public class MyCharacterControllerDriver : MonoBehaviour
    {
        public XRRig xrRig;
        public CharacterController characterController;
    
        public float minHeight = 0f;
        public float maxHeight = float.PositiveInfinity;
    
        void Update()
        {
            UpdateCharacterController();
        }
    
        void UpdateCharacterController()
        {
            if (xrRig is null || characterController is null)
                return;
    
            Vector3 center = xrRig.cameraInRigSpacePos;
            float height = Mathf.Clamp(xrRig.cameraInRigSpaceHeight, minHeight, maxHeight);
            center.y = height / 2f;
    
            characterController.center = center;
            characterController.height = height;
        }
    }
  • Tak a máme Rig, se kterým se umíme teleportovat, plynule chodit a přitom víceméně respektovat fyzikální zákony a v případě nouze se můžeme i manuálně otáčet.
    • Určitě toto není ideální řešení, ale na prototypování nebo pokuď pohyb není jednou z hlavních mechnik naší aplikace, tak je více než dostačující.
  • Náš objekt (XR Rig) tedy na konci vypadá nějak takto:

10

  • V této fázi můžeme z našeho Rigu udělat Prefab a třeba ho exportovat a jednoduše použít i v dalších projektech.