Contrôleurs HP Reverb G2 dans Unity

les contrôleurs de mouvement HP sont un tout nouveau type de contrôleurs de Windows Mixed Reality : les mêmes technologies de suivi avec un ensemble légèrement différent d’entrées disponibles :

  • Le pavé tactile a été remplacé par deux boutons : A et B pour le contrôleur de droite, et X et Y pour le contrôleur de gauche.
  • Comprendre est maintenant un déclencheur qui publie un flux de valeurs entre 0,0 et 1,0 au lieu d’un bouton avec des États enfoncé et non enfoncé.

étant donné que les nouvelles entrées ne sont pas accessibles par le biais des api Windows et unity existantes, vous devez disposer du Package UPM Microsoft. MixedReality. Input dédié.

Important

les Classes de ce package ne remplacent pas les api Windows et unity existantes, mais les complètent. les fonctionnalités couramment disponibles pour les contrôleurs de Windows Mixed Reality classiques et les contrôleurs de mouvement HP sont accessibles via le même chemin de code à l’aide des api existantes. Seules les nouvelles entrées nécessitent l’utilisation du package Microsoft. MixedReality. Input supplémentaire.

Présentation du contrôleur HP motion

Microsoft. MixedReality. Input. MotionController représente un contrôleur de mouvement. Chaque instance MotionController a un XR. WSA. Entrée. InteractionSource , qui peut être corrélée à l’aide de la main, de l’ID de fournisseur, de l’ID de produit et de la version.

Vous pouvez récupérer des instances MotionController en créant un MotionControllerWatcher et en vous abonnant à ses événements, de la même manière qu’en utilisant des événements InteractionManager pour découvrir de nouvelles instances InteractionSource . Les méthodes et propriétés de MotionController décrivent les entrées prises en charge par le contrôleur, y compris ses boutons, déclencheurs, axe 2D et stick analogique. La classe MotionController expose également des méthodes permettant d’accéder aux États d’entrée par le biais de la classe MotionControllerReading . La classe MotionControllerReading représente un instantané de l’état du contrôleur à un moment donné.

Installation de Microsoft. MixedReality. Input avec l’outil de la fonctionnalité de réalité mixte

Installez le plug-in Microsoft. MixedReality. Input avec la nouvelle application outil de la fonctionnalité de réalité mixte. suivez les instructions d’installation et d’utilisation , puis sélectionnez le package d’entrée de réalité mixte dans la catégorie réalité mixte Shared Computer Toolkit :

Outil de fonctionnalité de réalité mixte, fenêtre packages avec entrée de réalité mixte mise en surbrillance

Utilisation de Microsoft. MixedReality. Input

Valeurs d’entrée

Un MotionController peut exposer deux types d’entrées :

  • Les boutons et les États de déclencheur sont exprimés par une valeur flottante unique comprise entre 0,0 et 1,0, qui indique combien ils sont appuyés.
    • Un bouton peut retourner uniquement 0,0 (lorsqu’il n’est pas enfoncé) ou 1,0 (lorsqu’il est enfoncé) alors qu’un déclencheur peut retourner des valeurs continues entre 0,0 (entièrement relâché) et 1,0 (entièrement enfoncé).
  • L’état du joystick est exprimé par un Vector2 dont les composants X et Y sont compris entre-1,0 et 1,0.

Vous pouvez utiliser MotionController. GetPressableInputs () pour retourner une liste d’entrées renvoyant une valeur appuyée (boutons et déclencheurs) ou la méthode MotionController. GetXYInputs () pour retourner une liste d’entrées retournant une valeur à 2 axes.

Une instance MotionControllerReading représente l’état du contrôleur à un moment donné :

  • GetPressedValue () récupère l’état d’un bouton ou d’un déclencheur.
  • GetXYValue () récupère l’état d’un stick analogique.

Création d’un cache pour tenir à jour une collection d’instances MotionController et leurs États

Commencez par instancier un MotionControllerWatcher et inscrire des gestionnaires pour ses événements MotionControllerAdded et MotionControllerRemoved afin de conserver un cache des instances MotionController disponibles. Ce cache doit être un monocomportement attaché à un GameObject, comme illustré dans le code suivant :

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

Lecture de nouvelles entrées par interrogation

Vous pouvez lire l’état actuel de chaque contrôleur connu par le biais de MotionController. TryGetReadingAtTime pendant la méthode Update de la classe monobehavior. Vous souhaitez passer DateTime. Now en tant que paramètre timestamp pour vous assurer que l’état le plus récent du contrôleur est Read.

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

Vous pouvez récupérer la valeur d’entrée actuelle des contrôleurs à l’aide de la droitier du contrôleur :

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

Par exemple, pour lire la valeur de préhension analogique d’un 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);
        … 
    }
} 

Génération d’événements à partir des nouvelles entrées

Au lieu d’interroger l’état d’un contrôleur une fois par Frame, vous avez la possibilité de gérer toutes les modifications d’État en tant qu’événements, ce qui vous permet de gérer même les actions les plus rapides qui sont moins qu’un cadre. Pour que cette approche fonctionne, le cache des contrôleurs motion doit traiter tous les États publiés par un contrôleur depuis la dernière trame, ce que vous pouvez faire en stockant l’horodateur du dernier MotionControllerReading récupéré à partir d’un MotionController et en appelant 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; 
    } 
} 

Maintenant que vous avez mis à jour les classes internes du cache, la classe monobehavior peut exposer deux événements, appuyés et libérés, et les déclencher à partir de sa méthode 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); 
            } 
        } 
    } 
} 

La structure dans les exemples de code ci-dessus rend les événements d’inscription plus lisibles :

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

Voir aussi