Table of Contents

Laborator VR 03. SteamVR. Advanced input

În acest laborator vom studia câteva aspecte mai avansate legate de input - cum îl putem citi în cod, cum ne putem defini acțiuni custom - veți reuni aceste aspecte în implementarea unui gravity gun!

Acest laborator presupune deja că aveți setup-ul de SteamVR funcțional pentru un anumit headset. Laboratorul precedent prezintă în detaliu acest setup, așdar urmați pașii descriși acolo dacă este cazul, aceste aspecte nu vor fi prezentate aici.

Notă referitoare la sistemele de input din Unity și SteamVR

Sistemul de input folosit în SteamVR se bazează pe acțiuni.

Înainte să discutăm despre el, vom prezenta pe scurt cele 2 paradigme de folosire a input-ului în Unity.

Următoarele subsecțiuni au scop informativ, nu sunt esențiale în rezolvarea laboratorului, dar vă introduc în modalitatea în care se prelucreză input-ul în SteamVR.

Sistemul de input legacy (Unity)

În sistem legacy de input din Unity testarea acțiunilor este destul de simplă în cod, dar are câteva limitări. În cel mai simplu mod, input-ul se poate testa prin polling-ul unor butoane de input specifice.

public class LegacyInputExample : MonoBehaviour
{
    // Update is needed to poll every frame.
    private void Update()
    {
        // I want 3 buttons to call the same logic: LMB (mouse), Space (key), LCtrl (key).
        // In the legacy system, I need to hardcode the keycodes.
        if (Input.GetMouseButtonDown(0) || 
            Input.GetKeyDown(KeyCode.Space) || 
            Input.GetKeyDown(KeyCode.LeftControl))
        {
            Debug.Log("[LegacyInputExample] Action performed!");
        }
    }
}

Sistemul de input baza pe acțiuni (Unity)

Noul sistem de input abstractizează partea de testare manuală a acestor input-uri prin mapping-uri și acțiuni.

Un mapping definește un set de acțiuni legate de anumite input-uri fizice (cum ar fi tastatura, mouse-ul, controller-ele, etc.). Practic, un mapping este o schemă de control care asociază diferite acțiuni cu diverse metode de input. Puteți să vă gândiți la un mapping ca la modul în care un player interacționează cu jocul în funcție de context. Câteva exemple de mapping-uri:

O acțiune reprezintă o interacțiune definită de utilizator care poate fi activată prin diverse tipuri de input-uri. Acțiunile se pot lega la una sau mai multe intrări fizice printr-un binding.

Pentru a folosi acest sistem de input:

În final, următorul cod prezintă modul de utilizare al acestui sistem. Comentariile din cod ar trebuie să fie explicative.

public class ActionsInputExample : MonoBehaviour
{
    private InputActionAssetExample playerInputActions;
    private InputAction fireAction;
 
    // Initialize input asset.
    private void Awake() => playerInputActions = new InputActionAssetExample();
 
    // Get the action `Fire` from the mapping `PlayerMapping`. Enable it & bind event.
    private void OnEnable()
    {
        fireAction = playerInputActions.PlayerMapping.Fire;
        fireAction.Enable();
        fireAction.performed += FirePerformed;
    }
 
    private void OnDisable()
    {
        fireAction.Disable();
        fireAction.performed -= FirePerformed;
    }
 
    // Called whenever any of the bindings defined for the `Fire` action are called.
    // No need for explicit checks or the `Update` method!
    private void FirePerformed(InputAction.CallbackContext context) => Debug.Log("Fired action!");
}

Sistemul de input SteamVR versus cel din Unity

Sistemul de input din SteamVR nu se bazează în mod expres pe sistemul de input din Unity; SteamVR are un sistem propriu bazat pe acțiuni. Cu toate acestea, conceptele utilizate (acțiuni, mapări, evenimente) sunt foarte similare. Înțelegerea sistemului de input bazat pe acțiuni din Unity vă va ajuta să înțelegeți mai bine modul în care funcționează cel din SteamVR.

Import schelet laborator

Această scenă conține prefab-ul de player, câteva mese pe care se află câteva obiecte, precum și un gravity gun.

Sistemul de input din SteamVR

Precum a fost menționat anterior, sistemul de input din SteamVR se bazează pe acțiuni.

Deschideți fereastra de editor din Window → SteamVR Input. Aici o să vedeți definite:

In cod puteți face referire atât la Action sets cât și la Actions. Pentru a învăța modul de utilizare, urmăriți următorii pași:

Citire input prin polling

O metodă validă pentru a realiza acest task este prin polling. Urmăriți comentariile din snippet pentru detalii.

public class SteamVRInputActionsTesting : MonoBehaviour
{
    private void Update()
    {
        // Explanation:
        //  - From the `_default` mapping (action set), get the `GrabPinch` action's state via `GetState`.
        //  - `SteamVR_Input_Sources` can be used to set a specific device to read from. In this case any device which has this action.
        var grabPinchState = SteamVR_Actions._default.GrabPinch.GetState(SteamVR_Input_Sources.Any);
        Debug.Log($"[SteamVRInputActionsTesting] Polling: grabPinchState = {grabPinchState}");
    }
}

Pe lângă GetState puteți să folosiți și alte metode, care vă pot indica tranziții de la true la false (sau invers), etc.

Citire input folosind evenimente (metodă recomandată)

O altă metodă pentru a citi același input se bazează pe evenimente. Recomandăm această metodă întrucât face bypass polling-ului și considerăm că este un mod mai modular de a citi acest input.

public class SteamVRInputActionsTesting : MonoBehaviour
{
    // Subscribe to event `onChange` from the `GrabPinch` contained in the `_default` action set
    // and call `OnGrabPinchChanged` on invocations.
    private void OnEnable() => SteamVR_Actions._default.GrabPinch.onChange += OnGrabPinchChanged;
 
    // Unsubscribe from event.
    private void OnDisable() => SteamVR_Actions._default.GrabPinch.onChange -= OnGrabPinchChanged;
 
    // Method called when `onChange` from the `GrabPinch` is invoked.
    private void OnGrabPinchChanged(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource, bool grabPinchState)
    {
        Debug.Log($"[SteamVRInputActionsTesting] Events: grabPinchState = {grabPinchState}");
    }
}

Pe lângă onChange puteți să vă abonați și la alte evenimente, care vă pot indica tranziții de la true la false (sau invers), etc.

Task-ul vostru

Pentru a exersa aceste aspecte implementați una din cele două metode prezentate mai sus în script-ul SteamVRInputActionsTesting pentru acțiunea de GrabPinch și testați-o (similar GIF-urilor prezentate mai sus).

Binding-uri în SteamVR

Pentru a extinde funcționalitatea standard și a vă permite să ve definiți acțiuni noi, este necesar să folosiți sistemul de binding implementat în SteamVR.

Din mediul Window → SteamVR Input apăsați pe opțiunea Open binding UI. Ar trebui să vi să deschidă o nouă fereastră ce prezintă binding-ul curent pentru controller-ele pe care le folosiți.

Editați binding-ul curent (click pe Edit de pe oculus_touch (Local changes).

  • Ca să legăm informația învățată: Acest Grab Pinch este o acțiune binded pe butonul de Trigger, conținută în action set-ul default
  • În spate, SteamVR va genera cod care ne permite să referențiem în script-urile noastre aceste elemente
  • Pe scurt, acesta este motivul pentru care putem referenția în cod SteamVR_Actions._default.GrabPinch

My first (new) SteamVR binding

În continuare veți învăța cum să vă creați propriile acțiuni. Pentru simplitate ne vom folosi de action set-ul default și de butonul fizic de Trigger.

Ca și exemplu, vom crea o nouă acțiune de atingere (touch) a trigger-ului

Controller-ele Meta Quest 2 și 3 oferă o formă de input prin atingerea unora dintre butoane. Pentru a declanșa acest tip de eveniment este suficient să țineți degetul pe buton (fără a-l apăsa). Ne vom folosi de acest feature în continuare.

Pentru început, este necesar să vă definiți întâi o nouă acțiune - în acest exemplu vom crea în fereastra SteamVR Input o nouă acțiune TouchTrigger în action set-ul default.

În continuare trebuie să realizăm binding-ul.

Așadar, acest sistem be binding vă permite astfel să configurați comportamentul logic al butonului fizic.

Următorul pas necesar este configurarea acțiunii:

Awesome 🎉! În acest moment toată logica necesară acestei acțiuni (și al binding-ului) este finalizată.

Task-ul vostru

Pentru a testa dacă această nouă acțiune a fost configurată corect, completați script-ul SteamVRInputActionsTesting și afișați mesaje în consolă atunci când interogați această nouă acțiune TouchTrigger.

Rendering stereo

În aplicațiile de VR dezvolatate cu Unity se poate seta parametrul Stereo Rendering Mode, care specifică modalitatea de desenare a scenei în realitate virtuală - acest aspect este controlabil întrucât în general există două ecrane (unul pentru fiecare ochi) care trebuie actualizate și există câteva moduri de bază:

Metodele de tip single pass sunt cele mai eficiente din punct de vedere computațional, dar pot suferi de incompatibilități cu diverse shadere. Un exemplu folosit în acest laborator este componenta de tip LineRenderer, care nu este desenată corect în cazul desenării single pass.

Pentru acest laborator va trebui să schimbați din Edit → Project Settings → XR Plug-In Management → OpenVR Stereo Rendering Mode în Multipass.

Este recomandat să dezvoltați aplicațiile în modurile cele mai eficiente (single pass) și să rețineți această setare pentru situații similare în viitor. În cadrul acestui laborator compromisul folosind Multipass este preferat pentru a ne ușura munca.

Gravity gun

Pentru a aplica cunoștințele pe care le-ați învățat pe parcursul acest laborator, va trebuie să finalizați implementarea unui gravity gun.

Logica de funcționare a acestui gravity gun este deja implimentată, voi va trebui să configurați și să legați input-ului necesar folosind acțiuni și binding-uri.

Obiectul [GravityGun] din scena suport este de tip Throwable, așadar îi puteți face grab.

Vă recomandăm (pentru acest obiect în mod particulat) să faceți grab folosind doar grip-ul (butonul lateral), întrucât trigger-ul va fi folosit pentru logica de funcționare.

Funcționare gravity gun

După ce gravity gun-ul a fost grabbed, va trebui să implementați următorul comportament:

Detalii implementare

Logica de funcționare este implementată în GravityGunController, iar obiectele interactibile care interacționează cu gravity gun-ul implementează GravityGunObject.

Task-uri

  1. Implementați în SteamVRInputActionsTesting logica de citire a input-ului pentru acțiunea GrabPinch folosind oricare dintre metodele prezentate (polling, evenimente). Afișați rezultatul în consolă pentru a confirma funcționarea
    • Faceți referire la secțiunile Citire input prin polling și Citire input folosind evenimente (metodă recomandată) pentru snippet-uri din cod și explicații suplimentare
  2. Creați-vă o nouă acțiune TouchTrigger, realizați-i binding-ul pe logica de Touch și implementați în SteamVRInputActionsTesting logica de citire a input-ului. Afișați rezultatul în consolă pentru a confirma funcționarea
    • Faceți referire la secțiunea My first (new) SteamVR binding pentru explicații suplimentare
  3. Finalizați logica pentru gravity gun
    • Definiți-vă noile acțiuni și binding-uri
    • Legați input-ul în script-ul GravityGunController și apelați metodele SnapObject, TossObject și ThrowObject pe baza acestuia
    • Faceți referire la capitolul Gravity gun (și subcapitolele acestuia) pentru explicații suplimentare
  4. [✨Bonus✨] Implementați o mecanică de rotire a obiectului snapped de către gravity gun folosind stick-ul direcțional (joystick) al controller-ului
    • Modalitatea de transformare a input-ului oferit de joystick în rotația efectivă este la latitudinea voastră (mai precis trebuie să vedeți față de ce axe (X, Y, Z, locale, globale) rotați bazat pe valoarea input-ului
    • Modulați viteza de rotație în funcție de valoarea analogică (0-1) oferită de joystick