Unity 中的 HP 回音 G2 控制器HP Reverb G2 Controllers in Unity

HP 运动控制器是全新类型的 Windows Mixed Reality 控制器:所有相同的跟踪技术都具有一组略有不同的可用输入:HP Motion controllers are a brand new type of Windows Mixed Reality controllers: all the same tracking technology with a slightly different set of available inputs:

  • 触摸板已替换为以下两个按钮: A 和 B (适用于右控制器)和 X 和 Y (适用于左侧控制器)。Touchpad has been replaced by two buttons: A and B for the right controller, and X and Y for the left controller.
  • 抓住现在是一个触发器,用于发布介于0.0 和1.0 之间的值流,而不是使用按下但未按下的按钮。Grasp is now a trigger that publishes a stream of values between 0.0 and 1.0 instead of a button with Pressed and Not Pressed states.

由于不能通过现有的 Windows 和 Unity Api 访问新的输入,因此需要专用的 MixedReality UPM 包。Since the new inputs aren't accessible through existing Windows and Unity APIs, you need the dedicated Microsoft.MixedReality.Input UPM Package.

重要

此包中的类不会替换现有的 Windows 和 Unity Api,但对它们进行了补充。Classes in this package do not replace existing Windows and Unity APIs but complement them. 通常可用于经典 Windows Mixed Reality 控制器和 HP 运动控制器的功能可通过使用现有 Api 的相同代码路径进行访问。Features commonly available to both classic Windows Mixed Reality controllers and HP Motion Controllers are accessible through the same code path using existing APIs. 只有新输入需要使用其他 MixedReality 包。Only the new inputs require the use of the additional Microsoft.MixedReality.Input package.

HP 运动控制器概述HP Motion Controller overview

MixedReality. MotionController 表示运动控制器。Microsoft.MixedReality.Input.MotionController represents a motion controller. 每个 MotionController 实例都有一个 XR。WSA.InteractionSource 对等互连,可使用左右手使用习惯、供应商 ID、产品 id 和版本进行关联。Each MotionController instance has an XR.WSA.Input.InteractionSource peer, which can be correlated using handedness, vendor ID, product ID, and version.

可以通过创建 MotionControllerWatcher 并订阅其事件来获取 MotionController 实例,类似于使用 InteractionManager 事件来发现新的 InteractionSource 实例。You can grab MotionController instances by creating a MotionControllerWatcher and subscribing to its events, similar to using InteractionManager events to discover new InteractionSource instances. MotionController 的方法和属性说明控制器支持的输入,包括其按钮、触发器、二维轴和操纵杆。The MotionController’s methods and properties describe the inputs supported by the controller, including its buttons, triggers, 2D axis, and thumbstick. MotionController 类还公开通过 MotionControllerReading 类访问输入状态的方法。The MotionController class also exposes methods for accessing input states through the MotionControllerReading class. MotionControllerReading 类表示在给定时间的控制器状态的快照。The MotionControllerReading class represents a snapshot of the controller’s state at a given time.

使用 Unity 包管理器安装 MixedRealityInstalling Microsoft.MixedReality.Input using the Unity Package Manager

Unity 包管理器使用 清单文件 () 上的 # B0 来确定要安装的包以及要安装的注册表 (服务器) 可以从安装这些包。The Unity Package Manager uses a manifest file (manifest.json) to determine which packages to install and the registries (servers) they can be installed from. 你需要注册混合现实组件服务器,然后才能使用 MixedReality 包。Before you can use the Microsoft.MixedReality.Input package, you'll need to register the Mixed Reality component server.

注册混合现实组件服务器Registering the Mixed Reality component server

对于将使用混合现实输入包的每个项目,"包" 文件夹中文件 (的 manifest.js) 需要添加混合现实范围内的注册表。For each project that will be using the Mixed Reality Input package, the manifest.json file (in the Packages folder) needs the Mixed Reality scoped registry added. 若要正确修改 manifest.js以支持混合现实:To properly modify manifest.json to support Mixed Reality: 1. 在文本编辑器中打开/Packages/manifest.js,如 Visual Studio Code。Open /Packages/manifest.json in a text editor, such as Visual Studio Code. 2. 在清单文件的顶部,将混合现实服务器添加到作用域注册表部分,并保存该文件。At the top of the manifest file, add the Mixed Reality server to the scoped registry section and save the file.

{ 
  "scopedRegistries": [ 
    { 
      "name": "Microsoft Mixed Reality", 
      "url": "https://pkgs.dev.azure.com/aipmr/MixedReality-Unity-Packages/_packaging/Unity-packages/npm/registry/", 
      "scopes": [ 
        "com.microsoft.mixedreality" 
      ] 
    } 
  ], 

添加 MixedReality 包Adding the Microsoft.MixedReality.Input package

在文本编辑器中修改/Packages/manifest.json file 的依赖项部分,以添加 mixedreality 包并保存该文件。Modify the dependencies section of the /Packages/manifest.json file in the text editor to add com.microsoft.mixedreality.input package and save the file.

  "dependencies": { 
    "com.microsoft.mixedreality.input": "0.9.2006", 
  }

使用 MixedRealityUsing Microsoft.MixedReality.Input

输入值Input values

MotionController 可以公开两种输入:A MotionController can expose two kinds of inputs:

  • 按钮和触发器状态由介于0.0 和1.0 之间的唯一浮点值表示,用于指示按下的量。Buttons and trigger states are expressed by a unique float value between 0.0 and 1.0 that indicates how much they're pressed.
    • 按下时,按钮只能返回 0.0 ( (按) 下时未按) 或1.0 时,触发器可以返回 0.0 (完全释放) 到 1.0 (完全按下) 的连续值。A button can only return 0.0 (when not pressed) or 1.0 (when pressed) while a trigger can return continuous values between 0.0 (fully released) to 1.0 (fully pressed).
  • 操纵杆状态由其 X 和 Y 分量介于-1.0 和1.0 之间的 Vector2.y 表示。Thumbstick state is expressed by a Vector2 whose X and Y components are between -1.0 and 1.0.

可以使用 MotionController. GetPressableInputs ( # B1 返回一个输入列表,该列表返回按 (按钮和触发器) 或 ( MotionController # B5 方法返回的输入,以返回返回2轴值的输入列表。You can use MotionController.GetPressableInputs() to return a list of inputs returning a pressed value (buttons and triggers) or the MotionController.GetXYInputs() method to return a list of inputs returning a 2-axis value.

MotionControllerReading 实例表示在给定时间的控制器状态:A MotionControllerReading instance represents the state of the controller at a given time:

  • GetPressedValue ( # B1 检索按钮或触发器的状态。GetPressedValue() retrieves the state of a button or a trigger.
  • GetXYValue ( # B1 检索操纵杆的状态。GetXYValue() retrieves the state of a thumbstick.

创建缓存以维护 MotionController 实例及其状态的集合Creating a cache to maintain a collection of MotionController instances and their states

首先实例化 MotionControllerWatcher 并为其 MotionControllerAddedMotionControllerRemoved 事件注册处理程序,以保留可用 MotionController 实例的缓存。Start by instantiating a MotionControllerWatcher and registering handlers for its MotionControllerAdded and MotionControllerRemoved events to keep a cache of available MotionController instances. 此缓存应是附加到 GameObject 的 MonoBehavior,如以下代码所示:This cache should be a MonoBehavior attached to a GameObject as demonstrated in the following code:

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

通过轮询读取新输入Reading new inputs by polling

可以在 MonoBehavior 类的 Update 方法中通过 MotionController 读取每个已知控制器的当前状态。You can read the current state of each known controller through MotionController.TryGetReadingAtTime during the Update method of the MonoBehavior class. 要传递 DateTime。现在 作为时间戳参数,以确保读取控制器的最新状态。You want to pass DateTime.Now as the timestamp parameter to ensure that the latest state of the controller is 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); 
            } 
        } 
    } 
} 

可以使用控制器的左右手使用习惯获取控制器的当前输入值:You can grab the controllers current input value using the Handedness of the controller:

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

例如,若要读取 InteractionSource 的模拟抓住值:For example, to read the analog grasp value of an 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);
        … 
    }
} 

从新输入生成事件Generating events from the new inputs

您可以选择将所有状态变化作为事件来处理,而不是每个帧都轮询控制器的状态,这样您就可以更快地处理持久小于帧的操作。Instead of polling for a controller's state once per frame, you have the option of handling all state changes as events, which lets you handle even the quickest actions lasting less than a frame. 为了使此方法生效,运动控制器的缓存需要处理控制器自最后一帧后发布的所有状态,可以通过存储从 MotionController 检索到的最后一个 MotionControllerReading 的时间戳并调用 MotionController ( # B1In order for this approach to work, the cache of motion controllers needs to process all states published by a controller since the last frame, which you can do by storing the timestamp of the last MotionControllerReading retrieved from a MotionController and calling 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; 
    } 
} 

更新缓存内部类后,MonoBehavior 类可以公开两个事件(按下并释放),并从更新 ( # A1 方法中引发它们:Now that you've updated the cache internal classes, the MonoBehavior class can expose two events – Pressed and Released – and raise them from its Update() method:

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

上述代码示例中的结构使注册事件的可读性更高:The structure in the above code examples makes registering events much more readable:

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

另请参阅See also