Kontrolery HP Reverb G2 w środowisku Unity

Kontrolery HP Motion to zupełnie nowy typ kontrolerów Windows Mixed Reality: wszystkie te same technologie śledzenia z nieco innym zestawem dostępnych danych wejściowych:

  • Touchpad został zastąpiony dwoma przyciskami: A i B dla prawego kontrolera oraz X i Y dla lewego kontrolera.
  • Funkcja Grasp jest teraz wyzwalaczem, który publikuje strumień wartości z zakresu od 0,0 do 1,0 zamiast przycisku ze stanami naciśnięcia i braku naciśnięcia.

Ponieważ nowe dane wejściowe nie są dostępne za pośrednictwem istniejących interfejsów API systemu Windows i aparatu Unity, potrzebujesz dedykowanego pakietu Microsoft.MixedReality.Input UPM.

Ważne

Klasy w tym pakiecie nie zastępują istniejących interfejsów API systemu Windows i aparatu Unity, ale je uzupełniają. Funkcje powszechnie dostępne zarówno dla klasycznych kontrolerów Windows Mixed Reality, jak i kontrolerów ruchu HP są dostępne za pośrednictwem tej samej ścieżki kodu przy użyciu istniejących interfejsów API. Tylko nowe dane wejściowe wymagają użycia dodatkowego pakietu Microsoft.MixedReality.Input.

Omówienie kontrolera ruchu HP

Microsoft.MixedReality.Input.MotionController reprezentuje kontroler ruchu. Każde wystąpienie MotionController ma XR. WSA. Element równorzędny Input.InteractionSource , który może być skorelowany przy użyciu funkcji przekazania, identyfikatora dostawcy, identyfikatora produktu i wersji.

Wystąpienia motionController można chwycić, tworząc element MotionControllerWatcher i subskrybując jego zdarzenia, podobnie jak przy użyciu zdarzeń InteractionManager , aby odnaleźć nowe wystąpienia elementu InteractionSource . Metody i właściwości MotionController opisują dane wejściowe obsługiwane przez kontroler, w tym przyciski, wyzwalacze, oś 2D i szminkę. Klasa MotionController uwidacznia również metody uzyskiwania dostępu do stanów wejściowych za pośrednictwem klasy MotionControllerReading . Klasa MotionControllerReading reprezentuje migawkę stanu kontrolera w danym momencie.

Instalowanie pliku Microsoft.MixedReality.Input za pomocą narzędzia funkcji Mixed Reality

Zainstaluj wtyczkę Microsoft.MixedReality.Input przy użyciu nowej aplikacji narzędzia Mixed Reality Feature Tool. Postępuj zgodnie z instrukcjami dotyczącymi instalacji i użycia, a następnie wybierz pakiet Mixed Reality Input w kategorii Mixed Reality Toolkit:

okno pakietów narzędzi funkcji Mixed Reality z wyróżnionymi danymi wejściowymi rzeczywistości mieszanej

Korzystanie z pliku Microsoft.MixedReality.Input

Wartości wejściowe

Element MotionController może uwidocznić dwa rodzaje danych wejściowych:

  • Przyciski i stany wyzwalacza są wyrażane przez unikatową wartość zmiennoprzecinkową z zakresu od 0,0 do 1,0, która wskazuje, ile są one naciskane.
    • Przycisk może zwracać tylko 0,0 (jeśli nie jest naciśnięty) lub 1.0 (po naciśnięciu), podczas gdy wyzwalacz może zwracać wartości ciągłe z zakresu od 0,0 (w pełni zwolnione) do 1.0 (w pełni naciśnięcie).
  • Stan szminki jest wyrażony przez wektor2, którego składniki X i Y mają od -1.0 do 1.0.

Możesz użyć metody MotionController.GetPressableInputs(), aby zwrócić listę danych wejściowych zwracających wartość naciśniętą (przyciski i wyzwalacze) lub metodę MotionController.GetXYInputs(), aby zwrócić listę danych wejściowych zwracających wartość 2 osi.

Wystąpienie MotionControllerReading reprezentuje stan kontrolera w danym momencie:

  • Funkcja GetPressedValue() pobiera stan przycisku lub wyzwalacza.
  • Polecenie GetXYValue() pobiera stan szminki.

Tworzenie pamięci podręcznej w celu zachowania kolekcji wystąpień MotionController i ich stanów

Rozpocznij od utworzenia wystąpienia elementu MotionControllerWatcher i zarejestrowania programów obsługi dla zdarzeń MotionControllerAdded i MotionControllerRemoved , aby zachować pamięć podręczną dostępnych wystąpień MotionController. Ta pamięć podręczna powinna być elementem MonoBehavior dołączonym do obiektu GameObject, jak pokazano w poniższym kodzie:

public class MotionControllerStateCache : MonoBehaviour 
{ 
    /// <summary> 
    /// Internal helper class which associates a Motion Controller 
    /// and its known state 
    /// </summary> 
    private class MotionControllerState 
    { 
        /// <summary> 
        /// Construction 
        /// </summary> 
        /// <param name="mc">motion controller</param>` 
        public MotionControllerState(MotionController mc) 
        { 
            this.MotionController = mc; 
        } 

        /// <summary> 
        /// Motion Controller that the state represents 
        /// </summary> 
        public MotionController MotionController { get; private set; } 
        … 
    } 

    private MotionControllerWatcher _watcher; 
    private Dictionary<Handedness, MotionControllerState> 
        _controllers = new Dictionary<Handedness, MotionControllerState>(); 

    /// <summary> 
    /// Starts monitoring controller's connections and disconnections 
    /// </summary> 
    public void Start() 
    { 
        _watcher = new MotionControllerWatcher(); 
        _watcher.MotionControllerAdded += _watcher_MotionControllerAdded; 
        _watcher.MotionControllerRemoved += _watcher_MotionControllerRemoved; 
        var nowait = _watcher.StartAsync(); 
    } 

    /// <summary> 
    /// Stops monitoring controller's connections and disconnections 
    /// </summary> 
    public void Stop() 
    { 
        if (_watcher != null) 
        { 
            _watcher.MotionControllerAdded -= _watcher_MotionControllerAdded; 
            _watcher.MotionControllerRemoved -= _watcher_MotionControllerRemoved; 
            _watcher.Stop(); 
        } 
    }

    /// <summary> 
    /// called when a motion controller has been removed from the system: 
    /// Remove a motion controller from the cache 
    /// </summary> 
    /// <param name="sender">motion controller watcher</param> 
    /// <param name="e">motion controller </param> 
    private void _watcher_MotionControllerRemoved(object sender, MotionController e) 
    { 
        lock (_controllers) 
        { 
            _controllers.Remove(e.Handedness); 
        } 
    }

    /// <summary> 
    /// called when a motion controller has been added to the system: 
    /// Remove a motion controller from the cache 
    /// </summary> 
    /// <param name="sender">motion controller watcher</param> 
    /// <param name="e">motion controller </param> 
    private void _watcher_MotionControllerAdded(object sender, MotionController e) 
    { 
        lock (_controllers) 
        { 
            _controllers[e.Handedness] = new MotionControllerState(e); 
        } 
    } 
} 

Odczytywanie nowych danych wejściowych przez sondowanie

Bieżący stan każdego znanego kontrolera można odczytać za pomocą elementu MotionController.TryGetReadingAtTime podczas metody Update klasy MonoBehavior. Chcesz przekazać parametr DateTime.Now jako parametr znacznika czasu, aby upewnić się, że jest odczytywany najnowszy stan kontrolera.

public class MotionControllerStateCache : MonoBehaviour 
{ 
    … 

    private class MotionControllerState 
    {
        … 

        /// <summary> 
        /// Update the current state of the motion controller 
        /// </summary> 
        /// <param name="when">time of the reading</param> 
        public void Update(DateTime when) 
        { 
            this.CurrentReading = this.MotionController.TryGetReadingAtTime(when); 
        } 

        /// <summary> 
        /// Last reading from the controller 
        /// </summary> 
        public MotionControllerReading CurrentReading { get; private set; } 
    } 

    /// <summary> 
    /// Updates the input states of the known motion controllers 
    /// </summary> 
    public void Update() 
    { 
        var now = DateTime.Now; 

        lock (_controllers) 
        { 
            foreach (var controller in _controllers) 
            { 
                controller.Value.Update(now); 
            } 
        } 
    } 
} 

Bieżącą wartość wejściową kontrolerów można pobrać za pomocą polecenia Wręczenie kontrolera:

public class MotionControllerStateCache : MonoBehaviour 
{ 
    … 
    /// <summary> 
    /// Returns the current value of a controller input such as button or trigger 
    /// </summary> 
    /// <param name="handedness">Handedness of the controller</param> 
    /// <param name="input">Button or Trigger to query</param> 
    /// <returns>float value between 0.0 (not pressed) and 1.0 
    /// (fully pressed)</returns> 
    public float GetValue(Handedness handedness, ControllerInput input) 
    { 
        MotionControllerReading currentReading = null; 

        lock (_controllers) 
        { 
            if (_controllers.TryGetValue(handedness, out MotionControllerState mc)) 
            { 
                currentReading = mc.CurrentReading; 
            } 
        } 

        return (currentReading == null) ? 0.0f : currentReading.GetPressedValue(input); 
    } 

    /// <summary> 
    /// Returns the current value of a controller input such as button or trigger 
    /// </summary> 
    /// <param name="handedness">Handedness of the controller</param> 
    /// <param name="input">Button or Trigger to query</param> 
    /// <returns>float value between 0.0 (not pressed) and 1.0 
    /// (fully pressed)</returns> 
    public float GetValue(UnityEngine.XR.WSA.Input.InteractionSourceHandedness handedness, ControllerInput input) 
    { 
        return GetValue(Convert(handedness), input); 
    } 

    /// <summary> 
    /// Returns a boolean indicating whether a controller input such as button or trigger is pressed 
    /// </summary> 
    /// <param name="handedness">Handedness of the controller</param> 
    /// <param name="input">Button or Trigger to query</param> 
    /// <returns>true if pressed, false if not pressed</returns> 
    public bool IsPressed(Handedness handedness, ControllerInput input) 
    { 
        return GetValue(handedness, input) >= PressedThreshold; 
    } 
} 

Aby na przykład odczytać wartość uchwycenia analogiczną elementu InteractionSource:

/// Read the analog grasp value of all connected interaction sources 
void Update() 
{ 
    … 
    var stateCache = gameObject.GetComponent<MotionControllerStateCache>(); 
    foreach (var sourceState in InteractionManager.GetCurrentReading()) 
    { 
        float graspValue = stateCache.GetValue(sourceState.source.handedness, 
            Microsoft.MixedReality.Input.ControllerInput.Grasp);
        … 
    }
} 

Generowanie zdarzeń z nowych danych wejściowych

Zamiast sondowania stanu kontrolera raz na ramkę, można obsługiwać wszystkie zmiany stanu jako zdarzenia, co umożliwia obsługę nawet najszybszych akcji trwających mniej niż ramka. Aby to podejście działało, pamięć podręczna kontrolerów ruchu musi przetwarzać wszystkie stany opublikowane przez kontroler od ostatniej ramki, co można zrobić, przechowując znacznik czasu ostatniego elementu MotionControllerReading pobranego z elementu MotionController i wywołując element MotionController.TryGetReadingAfterTime():

private class MotionControllerState 
{ 
    … 
    /// <summary> 
    /// Returns an array representng buttons which are pressed 
    /// </summary> 
    /// <param name="reading">motion controller reading</param> 
    /// <returns>array of booleans</returns> 
    private bool[] GetPressed(MotionControllerReading reading) 
    { 
        if (reading == null) 
        { 
            return null; 
        } 
        else 
        { 
            bool[] ret = new bool[this.pressableInputs.Length]; 
            for (int i = 0; i < pressableInputs.Length; ++i) 
            { 
                ret[i] = reading.GetPressedValue(pressableInputs[i]) >= PressedThreshold; 
            } 

            return ret; 
        } 
    } 

    /// <summary> 
    /// Get the next available state of the motion controller 
    /// </summary> 
    /// <param name="lastReading">previous reading</param> 
    /// <param name="newReading">new reading</param> 
    /// <returns>true is a new reading was available</returns> 
    private bool GetNextReading(MotionControllerReading lastReading, out MotionControllerReading newReading) 
    { 
        if (lastReading == null) 
        { 
            // Get the first state published by the controller 
            newReading = this.MotionController.TryGetReadingAfterSystemRelativeTime(TimeSpan.FromSeconds(0.0)); 
        } 
        else 
        { 
            // Get the next state published by the controller 
            newReading = this.MotionController.TryGetReadingAfterTime(lastReading.InputTime); 
        } 

        return newReading != null; 
    } 

    /// <summary> 
    /// Processes all the new states published by the controller since the last call 
    /// </summary> 
    public IEnumerable<MotionControllerEventArgs> GetNextEvents() 
    {
        MotionControllerReading lastReading = this.CurrentReading; 
        bool[] lastPressed = GetPressed(lastReading); 
        MotionControllerReading newReading; 
        bool[] newPressed; 

        while (GetNextReading(lastReading, out newReading)) 
        { 
            newPressed = GetPressed(newReading); 

            // If we have two readings, compare and generate events 
            if (lastPressed != null) 
            { 
                for (int i = 0; i < pressableInputs.Length; ++i) 
                { 
                    if (newPressed[i] != lastPressed[i]) 
                    { 
                        yield return new MotionControllerEventArgs(this.MotionController.Handedness, newPressed[i], this.pressableInputs[i], newReading.InputTime); 
                    } 
                } 
            } 

            lastPressed = newPressed; 
            lastReading = newReading; 
        } 

        // No more reading 
        this.CurrentReading = lastReading; 
    } 
} 

Po zaktualizowaniu klas wewnętrznych pamięci podręcznej klasa MonoBehavior może uwidocznić dwa zdarzenia — naciśnięcie i wydanie — i podnieść je z metody Update():

/// <summary> 
/// Event argument class for InputPressed and InputReleased events 
/// </summary> 
public class MotionControllerEventArgs : EventArgs 
{ 
    public MotionControllerEventArgs(Handedness handedness, bool isPressed, rollerInput input, DateTime inputTime) 
    { 
        this.Handedness = handedness; 
        this.Input = input; 
        this.InputTime = inputTime; 
        this.IsPressed = isPressed; 
    } 

    /// <summary> 
    /// Handedness of the controller raising the event 
    /// </summary> 
    public Handedness Handedness { get; private set; } 

    /// <summary> 
    /// Button pressed or released 
    /// </summary> 
    public ControllerInput Input { get; private set; } 

    /// <summary> 
    /// Time of the event 
    /// </summary> 
    public DateTime InputTime { get; private set; } 

    /// <summary> 
    /// true if button is pressed, false otherwise 
    /// </summary> 
    public bool IsPressed { get; private set; } 
} 

/// <summary> 
/// Event raised when a button is pressed 
/// </summary> 
public event EventHandler<MotionControllerEventArgs> InputPressed; 

/// <summary> 
/// Event raised when a button is released 
/// </summary> 
public event EventHandler<MotionControllerEventArgs> InputReleased; 

/// <summary> 
/// Updates the input states of the known motion controllers 
/// </summary> 
public void Update() 
{ 
    // If some event handler has been registered, we need to process all states  
    // since the last update, to avoid missing a quick press / release 
    if ((InputPressed != null) || (InputReleased != null)) 
    { 
        List<MotionControllerEventArgs> events = new <MotionControllerEventArgs>(); 

        lock (_controllers) 
        { 
            foreach (var controller in _controllers) 
            { 
                events.AddRange(controller.Value.GetNextEvents()); 
            } 
        } 
 
        // Sort the events by time 
        events.Sort((e1, e2) => DateTime.Compare(e1.InputTime, e2.InputTime)); 

        foreach (MotionControllerEventArgs evt in events) 
        { 
            if (evt.IsPressed && (InputPressed != null)) 
            { 
                InputPressed(this, evt); 
            } 
            else if (!evt.IsPressed && (InputReleased != null)) 
            { 
                InputReleased(this, evt); 
            } 
        } 
    } 
    else 
    { 
        // As we do not predict button presses and the timestamp of the next e is in the future 
        // DateTime.Now is correct in this context as it will return the latest e of controllers 
        // which is the best we have at the moment for the frame. 
        var now = DateTime.Now; 
        lock (_controllers) 
        { 
            foreach (var controller in _controllers) 
            { 
                controller.Value.Update(now); 
            } 
        } 
    } 
} 

Struktura w powyższych przykładach kodu sprawia, że rejestrowanie zdarzeń jest znacznie bardziej czytelne:

public InteractionSourceHandedness handedness; 
public Microsoft.MixedReality.Input.ControllerInput redButton;

// Start of the Mono Behavior: register handlers for events from cache 
void Start() 
{ 
    var stateCache = gameObject.GetComponent<MotionControllerStateCache>(); 
    stateCache.InputPressed += stateCache_InputPressed; 
    stateCache.InputReleased += stateCache_InputReleased; 
    … 
} 

// Called when a button is released 
private void stateCache_InputReleased(object sender, MotionControllerStateCache.MotionControllerEventArgs e) 
{ 
    if ((e.SourceHandedness == handedness) && (e.Input == redButton)) 
    { 
        … 
    } 
} 

// Called when a button is pressed 
private void stateCache_InputPressed(object sender, MotionControllerStateCache.MotionControllerEventArgs e) 
{ 
    if ((e.SourceHandedness == handedness) && (e.Input == redButton)) 
    { 
        … 
    } 
} 

Zobacz też