Dispozitivele dezvoltate de către Meta (în trecut, cunoscute ca și Oculus) sunt soluții `all-in-one` ce permit dezvoltarea aplicațiilor de realitate virtuală și oferă suport pentru cele mai recente funcționalități din acest domeniu, precum hand tracking, realitate mixtă (MR), audio spațializat, eye și body tracking.
În mod curent, în seria Quest, regăsim 4 astfel de dispozitive ce oferă suport activ pentru dezvoltarea aplicațiilor VR, anume:
Pentru dezvoltarea aplicațiilor de VR, există suport pentru OpenXR (aplicații native), sau SDK-uri ce oferă integrare cu game engine-urile standard, precum Unity și Unreal Engine.
Integrarea SDK-ului vine cu suport pentru diverse feature-uri pe care le putem utiliza, în funcție de necesitățile aplicației pe care dorim s-o dezvoltăm:
În imaginea de mai jos puteți studia un flow generalizat al dezvoltării aplicațiilor de VR folosind dispozitivele celor de la Meta.
Aplicația vă oferă setări adiționale pentru un dispozitiv Quest conectat la calculator. Folosind MQDH puteți să:
IRVA_ProjectSetup_XRPluginProviderController, care se găsește în folder-ul UnityPackages din folder-ul root al proiectului, după care restartați Unity.
Începând cu versiunea 59, pachetul de integrare se folosește de UPM (Unity Package Manager), oferind astfel o modalitate mai ușoară de integrare, realizarea rapidă a upgrade-urilor, iar probabil cel mai important, un management ușor în alegerea pachetelor esențiale de folosit (Core, Integration, Audio, etc.). Versiunea up-to-date la momentul redactării acestui text este 81.0.0.
Din File → Build Profiles vă recomandăm să vă creați un nou profil de build derivat din platforma Meta Quest.
O să primiți un prompt pentru instalarea backend-ului OpenXR, acceptați-l.
com.unity.xr.openxr.
În Unity Editor ar trebui să vă apară un nou buton Meta VR Tools (în stânga-sus, sub toolbar).
De aici puteți deschide Project Setup Tool, care vă permite să vă configurați proiectul de Unity pentru a fi compatibil cu cerințele SDK-ului.
Puteți realiza build-uri în cel mai simplu mod folosiți opțiunea de Build And Run din meniul profilului de build selectat (Meta Quest). La finalul build-ului, aplicația este instalată pe headset și pornită în mod automat. Aplicația se poate apoi regăsi în submeniul Unknown Sources.
Pentru alte variante de build, în urma creării APK-ului din Unity (așadar Build simplu, nu Build And Run), se pot folosi Meta Quest Developer Hub sau SideQuest pentru instalarea acestor aplicații pe headset (proces cunsocut ca și sideloading).
GravityGunController din laboratorul 3. Pe scurt, build-ul din laboratorul curent încearcă să compileze acest script, care conține componente specifice SteamVR, iar cum SteamVR nu este inclus în build, acele componente nu sunt găsite.
Unity package-ul pentru L3 a fost actualizat pentru a rezolva această problemă, dar pentru a rezolva pe loc eroarea cuprindeți întreg script-ul GravityGunController într-o directivă de preprocesare corespunzătoare build-urilor Standalone, astfel:
#if UNITY_STANDALONE // ... script GravityGunController ... #endif
IRVA_L4_VR_MetaXR_Skeleton care se găsește în folder-ul UnityPackages din folder-ul root al proiectuluiVeți învăța în continuare cum se pot integra diverse componente ale SDK-ului aplicația voastră: headset-ul virtual, controller-ele, hand tracking-ul, funcționalitatea de passthrough, etc.
SDK-ul a introdus recent o nouă modalitate de setup a acestor elemente printr-un meniu de Building Blocks.
În primele iterații ale sistemului de building blocks componentele player-ului trebuiau alcătuite din building block-uri separate. De exemplu, dacă doream un player care să poată folosi atât controllerele cât și mâinile pentru a interacționa cu un obiect trebuiau inserate:
Fiecare dintre acestea adăuga câte o componentă și făcea setup în mod automat în ierarhie. Spre exemplu:
În noile versiuni ale SDK-ului a fost introdus un building block numit Interactions Rig care este util întrucât unifică toate componentele menționate mai sus, nefiind necesar setup-ul individual. Introduceți-l pe acesta în scenă via drag-and-drop.
Selectați obiectul [BuildingBlock] Camera Rig. În cadrul acestui laborator nu va trebui să-l modificați dar rețineți că acesta conține configurări esențiale atunci când o să lucrați la proiectele voastre.
Nagivagați în inspector la script-ul OVRManager. Acesta conține numeroase setări ce țin de sesiunea VR, precum:
Este locul central în care se poate configura sesiunea VR în cadrul acestui SDK.
Obiectele OVRHands și OVRControllers conțin atașate script-uri ce implementază structurile de date ce reprezintă în esență controllerele sau mâinile virtuale și pot fi utilizate pentru a citi date legate de input.
Obiectele OVRLeftHandVisual, OVRRightHandVisual, OVRLeftControllerVisual și OVRRightControllerVisual oferă logica și asset-urile pentru reprezentarea vizuală a mâinilor, respectiv controllerelor.
Ce este esențial de reținut este că orice interacțiune necesită pe de-o parte de un interactor, de exemplu, un HandGrabInteractor (vedeți în ierarhie [BuildingBlock] OVRInteractionComprehensive → LeftInteractions → Hand and No Controller → HandGrabInteractor) care poate interacționa doar cu un obiect care are atașat un script de tip interactibil, în cazul acesta, un HandGrabInteractable
Rețineți, de asemenea, că interactorul poate interacționa cu un interactibil de același tip - de exemplu, un interactor de tip Poke nu poate comunica cu un interactibil de tip Distance Grab.
Atenție la faptul că se face distincție între interactori/interactibile compatibili pentru controller sau mâini. De exemplu:
Același principiu se aplică și pentru interactibili.
Adăugați în scenă building block-ul Grab Interaction.
Uitați vă în inspector la obiectul [BuildingBlock] Cube → [BuildingBlock] HandGrabInstallationRoutine. Observați că există preconfigurate script-urile necesare de tip interactibil:
Rețineți existența acestora în cazul în care aveți nevoie de interacțiuni de tip grab pe obiecte custom.
În cadrul task-urilor din laborator veți avea de completat implementarea unei arme pe care o veți folosi pentru a trage în niște obiecte (sticle) regăsite în scena suport.
Funcționarea armei este descrisă în mod modular printr-o serie de script-uri, unele dintre acestea le veți avea de completat:
Dacă ați testat scena sample ComprehensiveRigExample poate ați observat faptul modelul mâinii se conformează unor pose-uri prestabilite atunci obiectele din scenă (torța și cana).
De asemenea, SDK-ul oferă suport pentru diferite dimenisuni ale mâinii (în funcție de utilizator).
Acest feature oferă un grad sporit de imersivitate, întrucât dezvoltatorii se pot asigura de faptul că interacțiunea mână-obiect virtual este desenată fără probleme cum ar fi clipping-ul.
Meta SDK oferă un utilitar pentru a crea pose-uri custom pentru obiecte/modele artbitrare printr-un proces de înregistrare a unui snapshot al pose-ului.
În acest laborator, obiectul [Gun] are atașat doar un script Grabbable, dar momentan nu are un HandGrabInteractable. În loc să atașăm manual această componentă (care nu definește un pose al mâinii), vom crea cu acest utilitar un nou pose, care va crea la rândul său componenta HandGrabInteractable necesară.
Deschideți utilitarul din toolbar, Meta → Interaction → Hand Grab Pose Recorder.
Completați câmpurile de la secțiunea 1.
Pentru a înregistra un pose custom, urmați următorii pași (aveți și un GIF de ajutor după lista de mai jos)
În continuare, puteți rafina pose-ul prin manipularea încheieturilor de pe obiectul HandGrabInteractable → HandGrabPose. Ajustați poziția degetelor după caz.
Va trebui să modificați pose-ul astfel încât acesta indică apăsarea completă a trăgaciului armei. Workflow recomandat (aveți GIF mai jos):
Ultimele ajustări pe care trebuie să le faceți țin de regulile care determină grab-ul obiectului, precum și specificarea constrângerilor degetelor în momentul în care arma este ținută în mână.
Pentru a realiza aceste setări, modicați de pe HandGrabInteractable atât Pinch Grab Rules cât și Palm Grab Rules astfel:
În final, ajustați pose-ul degetelor astfel (de pe HandGrabPose, Fingers Freedom):
Pentru a valida setup-ul pentru pose-ul custom al mâinii:
Pentru a realiza acest feature, va trebui în esență să detectați schimarea de stare a interactibilului atașat de armă. Rețineți că HandGrabInteractable a fost adăugat atunci când ați creat pose-ul custom.
Adăugați pentru script-ul GunHandGrabController elementele lipsă din inspector.
În script-ul GunHandGrabController va trebui să interogați starea variabilei handGrabInteractable - Pentru a realiza acest lucru, abonați-vă evenimentului numit WhenStateChanged de pe handGrabInteractable.
În event handler-ul creat, va trebui să vă folosiți de proprietățile NewState și PreviousState ale argumentului InteractableStateChangeArgs pentru a testa starea de grab / release.
// Object has been grabbed. if (state.NewState == InteractableState.Select) { ... }
// Object has been released. if (state.PreviousState == InteractableState.Select && state.NewState != InteractableState.Select) { ... }
Pentru a accesa interactorul care a declanșat interacțiunea și în esență obiectul tip IHand aferent, puteți interoga structura SelectingInteractors - aceasta descrie intractorii care interacționează în mod curent cu interactibilul (arma) astfel:
var handInteractor = handGrabInteractable.SelectingInteractors.FirstOrDefault();
Pentru a extrage componenta tip IHand:
if (handInteractor != null) { var hand = handInteractor.Hand; }
Urmăriți comentariile din script pentru a finaliza acest task.
În continuare va trebui să citiți nivelul de curl (sau gradul de îndoire a degetului) arătător, valoare cu ajutorul căreia vom determina atât rotația trăgaciului armei cât și momentul în care aceasta declașează un eveniment de tragere.
SDK-ul ne permite să citim varii proprietați ale mâinilor și degetelor, precum:
Accesați script-ul GunFingerCurlController.
Pentru a citi aceste informații ne vom folosi de datele furnizate de FingerFeatureStateProvider (deja atașate pe GunHandGrabController) – în metoda Update a script-ului curent observați că este deja implementată extragerea provider-ului curent în variabila provider.
Pentru a citi gradul de îndoire (curl) a degetului arătăror (index) putem face astfel:
var curlValue = provider.GetFeatureValue(HandFinger.Index, FingerFeature.Curl); var curlFloat = curlValue ?? 0f;
Afișați în consolă valoarea pe care o obțineți. În continuare va trebui să setați valorile min și max (din inspector) pentru usableFingerCurlRange folosind valoarea afișată în consolă:
Normalizați valoarea curl-ului între valorile de minim și maxim obținute. De exemplu, dacă min/max slider sunt [200;250], remapați în range-ul [0;1]. În script-ul Utils aveți metoda ajutătoare Remap.
Pentru a roti trăgaciul armei, apelați metoda SetCurl de pe script-ul GunTriggerCurlController.
Restul de script se ocupă de detecția evenimentului de tragere pe baza unui prag, anume triggerThreshold. Logica deja dată apelează evenimentele de trăgaci apăsat (OnTriggerPressed) sau trăgaci lăsat liber (OnTriggerReleased).
Partea aceasta este în mare parte implementată [moment de pauză 🧘♂️!]. În scop informativ:
OnTriggerPressed și OnTriggerRelease din GunFingerCurlController sunt legate în GunFireController, unde se determină evenimentul de tragere (arma funcționează în modul semi-automat, fiecare apăsare de trăgaci declanșează o tragere)OnGunFired este declanșat de aici și ascultat în:Pentru a finaliza implementarea armei, va trebui să completați logica de raycast astfel încât să puteți, pe baza tragerii armei, să spargeți sticlele din scenă.
Accesați script-ul GunRaycastController.
În metoda PerformRaycast va trebui să implementați logica de raycast.
Definim un Ray din poziția raycastOrigin pe direcția forward a acestuia.
var ray = new Ray(raycastOrigin.position, raycastOrigin.forward);
Efectuăm testul tip raycast:
if (Physics.Raycast(ray, out var hitInfo, raycastDistance)) { ... }
Testați dacă hitInfo.collider are atașat un script de tip Bottle - dacă testul trece, apelați metoda Shatter de pe Bottle.
Pentru a reseta sticlele de pe cea de-a doua masă din scenă, ne vom folosi de un intractibil de tip Poke - pe scurt, această funcționează ca un buton virtal și primește input de la interactorul de tip HandPokeInteractor.
Îl aveți deja definit în scenă.
Legarea se poate realiza și în cod, dar pentru acest task, folosiți-vă de evenimentele expuse în inspector de pe script-ul InteractableUnityEventWrapper atașat obiectului BottleReset_PokeInteractable. Evenimentul de interes este Select.
Legați la acesta metoda SpawnBottles din script-ul BottlesManager - îl găsiți atașat de obiectul BottlesTable în scenă.
HandGrabInteractable și HandGrabPoseGunHandGrabController pentru a detecta momentul în care arma este grabbedGunFingerCurlController pentru a citi și aplica inputul corespuzător valorii de curl a degetului arătătorGunRaycastController pentru a realiza raycast-ul în urma evenimentului de tragere și adăugați logica de spargere a sticlelor în cazul în care raza se intersectează cu un obiect de tip BottleBottlesManager pentru a reseta sticleleGunFireController pentru logica de tragere tip automat (sau creați-vă propriile script-uri, după plac/necesitate)