HP Reverb G2-Controller in Unity

HP Motion-Controller sind ein völlig neuer Typ von Windows Mixed Reality Controllern: die gleiche Nachverfolgungstechnologie mit einem etwas anderen Satz verfügbarer Eingaben:

  • Touchpad wurde durch zwei Schaltflächen ersetzt: A und B für den rechten Controller und X und Y für den linken Controller.
  • "Greifen" ist jetzt ein Trigger, der einen Datenstrom mit Werten zwischen 0,0 und 1,0 anstatt einer Schaltfläche mit den Zuständen Gedrückt und Nicht gedrückt veröffentlicht.

Da auf die neuen Eingaben nicht über vorhandene Windows und Unity-APIs zugegriffen werden kann, benötigen Sie das dedizierte UPM-Paket Microsoft.MixedReality.Input.

Wichtig

Klassen in diesem Paket ersetzen vorhandene Windows und Unity-APIs nicht, sondern ergänzen sie. Features, die häufig sowohl für klassische Windows Mixed Reality-Controller als auch für HP Motion Controllers verfügbar sind, sind über denselben Codepfad über vorhandene APIs zugänglich. Nur die neuen Eingaben erfordern die Verwendung des zusätzlichen Microsoft.MixedReality.Input-Pakets.

Übersicht über HP Motion Controller

Microsoft.MixedReality.Input.MotionController stellt einen Motion Controller dar. Jede MotionController-Instanz verfügt über einen XR. WSA. Input.InteractionSource-Peer, der mithilfe von Handkraft, Anbieter-ID, Produkt-ID und Version korreliert werden kann.

Sie können MotionController-Instanzen greifen, indem Sie einen MotionControllerWatcher erstellen und dessen Ereignisse abonnieren, ähnlich wie bei der Verwendung von InteractionManager-Ereignissen, um neue InteractionSource-Instanzen zu ermitteln. Die Methoden und Eigenschaften des MotionController beschreiben die vom Controller unterstützten Eingaben, einschließlich der Schaltflächen, Trigger, 2D-Achse und des Fingerabdrucks. Die MotionController-Klasse macht auch Methoden für den Zugriff auf Eingabezustände über die MotionControllerReading-Klasse verfügbar. Die MotionControllerReading-Klasse stellt eine Momentaufnahme des Zustands des Controllers zu einem bestimmten Zeitpunkt dar.

Installieren von Microsoft.MixedReality.Input mit dem Mixed Reality FeatureTool

Installieren Sie das Microsoft.MixedReality.Input-Plug-In mit der neuen Anwendung Mixed Reality Feature Tool. Befolgen Sie die Installations- und Nutzungsanweisungen, und wählen Sie das paket Mixed Reality Input in der Kategorie Mixed Reality Toolkit aus:

Mixed Reality Fenster

Verwenden von Microsoft.MixedReality.Input

Eingabewerte

Ein MotionController kann zwei Arten von Eingaben verfügbar machen:

  • Schaltflächen und Triggerzustände werden durch einen eindeutigen float-Wert zwischen 0,0 und 1,0 ausgedrückt, der angibt, wie viel sie gedrückt werden.
    • Eine Schaltfläche kann nur 0,0 (wenn nicht gedrückt) oder 1,0 (wenn gedrückt) zurückgegeben werden, während ein Trigger kontinuierliche Werte zwischen 0,0 (vollständig freigegeben) und 1,0 (vollständig gedrückt) zurückgeben kann.
  • Der Fingerabdruckzustand wird durch einen Vector2 ausgedrückt, dessen X- und Y-Komponenten zwischen -1,0 und 1,0 liegen.

Sie können MotionController.GetPressableInputs() verwenden, um eine Liste von Eingaben zurückzugeben, die einen gedrückten Wert zurückgeben (Schaltflächen und Trigger) oder die MotionController.GetXYInputs()-Methode, um eine Liste von Eingaben zurückzugeben, die einen 2-Achsen-Wert zurückgeben.

Eine MotionControllerReading-Instanz stellt den Zustand des Controllers zu einem bestimmten Zeitpunkt dar:

  • GetPressedValue() ruft den Zustand einer Schaltfläche oder eines Triggers ab.
  • GetXYValue() ruft den Zustand eines Thumbsticks ab.

Erstellen eines Caches zum Verwalten einer Sammlung von MotionController-Instanzen und deren Zuständen

Instanziieren Sie zunächst einen MotionControllerWatcher, und registrieren Sie Handler für die MotionControllerAdded- und MotionControllerRemoved-Ereignisse, um einen Cache verfügbarer MotionController-Instanzen beizubehalten. Dieser Cache sollte ein MonoBehavior sein, der an ein GameObject angefügt ist, wie im folgenden Code gezeigt:

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); 
        } 
    } 
} 

Lesen neuer Eingaben durch Abfragen

Sie können den aktuellen Zustand jedes bekannten Controllers über MotionController.TryGetReadingAtTime während der Update-Methode der MonoBehavior-Klasse lesen. Sie möchten DateTime.Now als timestamp-Parameter übergeben, um sicherzustellen, dass der neueste Zustand des Controllers gelesen wird.

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); 
            } 
        } 
    } 
} 

Sie können den aktuellen Eingabewert des Controllers mithilfe der Handkraft des Controllers abrufen:

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; 
    } 
} 

Um z. B. den analogen Greifwert einer InteractionSource zu lesen:

/// 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);
        … 
    }
} 

Generieren von Ereignissen aus den neuen Eingaben

Anstatt den Zustand eines Controllers einmal pro Frame abzufragen, haben Sie die Möglichkeit, alle Zustandsänderungen als Ereignisse zu verarbeiten, wodurch Sie selbst die schnellsten Aktionen verarbeiten können, die weniger als einen Frame dauern. Damit dieser Ansatz funktioniert, muss der Cache von MotionControllern alle Zustände verarbeiten, die seit dem letzten Frame von einem Controller veröffentlicht wurden. Dazu können Sie den Zeitstempel des letzten MotionControllerReading speichern, das von einem MotionController abgerufen wurde, und MotionController.TryGetReadingAfterTime()aufrufen:

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; 
    } 
} 

Nachdem Sie die internen Cacheklassen aktualisiert haben, kann die MonoBehavior-Klasse zwei Ereignisse verfügbar machen – Pressed und Released – und sie über ihre Update()-Methode auslösen:

/// <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); 
            } 
        } 
    } 
} 

Die Struktur in den obigen Codebeispielen macht das Registrieren von Ereignissen viel besser lesbar:

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)) 
    { 
        … 
    } 
} 

Weitere Informationen