Unity 中的 HP Reverb G2 控制器
HP 動作控制器是一種全新的Windows Mixed Reality控制器:所有相同的追蹤技術都有一組稍微不同的可用輸入:
- 觸控板已由兩個按鈕取代:A 和 B 代表右控制器,以及左側控制器的 X 和 Y。
- 「掌握」現在是一個觸發程式,其會發佈介於 0.0 到 1.0 之間的值資料流程,而不是具有 [已按下] 和 [未按下] 狀態的按鈕。
由於無法透過現有的 Windows 和 Unity API 存取新的輸入,因此您需要專用 的 Microsoft.MixedReality.Input UPM 套件。
重要
此套件中的類別不會取代現有的 Windows 和 Unity API,而是加以補充。 傳統Windows Mixed Reality控制器和 HP 動作控制器通常都可以使用現有的 API,透過相同的程式碼路徑來存取功能。 只有新的輸入需要使用額外的 Microsoft.MixedReality.Input 套件。
HP 動作控制器概觀
Microsoft.MixedReality.Input.MotionController 代表動作控制器。 每個 MotionController 實例都有 一個 XR。Wsa。Input.InteractionSource 對等,可使用手部、廠商識別碼、產品識別碼和版本相互關聯。
您可以藉由建立 MotionControllerWatcher 並訂閱其事件來擷取 MotionController 實例,類似于使用 InteractionManager 事件來探索新的 InteractionSource 實例。 MotionController 的方法和屬性描述控制器支援的輸入,包括其按鈕、觸發程式、2D 軸和搖桿。 MotionController 類別也會公開透過 MotionControllerReading 類別存取輸入狀態的方法。 MotionControllerReading 類別代表控制器狀態在特定時間的快照集。
使用 Mixed Reality 功能工具安裝 Microsoft.MixedReality.Input
使用新的 Mixed Reality Feature Tool 應用程式安裝 Microsoft.MixedReality.Input 外掛程式。 遵循安裝和使用指示,然後在 [Mixed Reality工具組] 類別中選取[Mixed Reality輸入套件]:
使用 Microsoft.MixedReality.Input
輸入值
MotionController 可以公開兩種輸入:
- 按鈕和觸發程式狀態會以 0.0 和 1.0 之間的唯一浮點值表示,指出按下多少。
- 當未按下) 或按下 1.0) 時,按鈕只能傳回 0. (0 (,而觸發程式可以傳回介於 0.0 (完全釋放) 到 1.0 (完全按下) 之間的連續值。
- 搖桿狀態是由 Vector2 表示,其 X 和 Y 元件介於 -1.0 和 1.0 之間。
您可以使用 MotionController.GetPressableInputs () 傳回輸入清單,以傳回按下的值 (按鈕和觸發程式) 或 MotionController.GetXYInputs () 方法傳回傳回 2 軸值的輸入清單。
MotionControllerReading 實例代表指定時間的控制器狀態:
- GetPressedValue () 會擷取按鈕或觸發程式的狀態。
- GetXYValue () 會擷取搖桿的狀態。
建立快取以維護 MotionController 實例及其狀態的集合
首先,具現化 MotionControllerWatcher 並註冊 其 MotionControllerAdded 和 MotionControllerRemoved 事件的處理常式,以保留可用 MotionController 實例的快取。 此快取應該是附加至 GameObject 的 MonoBehavior,如下列程式碼所示:
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);
}
}
}
輪詢來讀取新的輸入
您可以在 MonoBehavior 類別的Update方法期間,透過MotionController.TryGetReadingAtTime讀取每個已知控制器的目前狀態。 您想要傳遞 DateTime.Now 做為時間戳記參數,以確保讀取控制器的最新狀態。
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);
}
}
}
}
您可以使用控制器的 Handedness 來擷取控制器目前的輸入值:
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 的類比擷取值:
/// 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);
…
}
}
從新的輸入產生事件
您可以選擇處理所有狀態變更為事件,而不要針對每個畫面輪詢控制器的狀態一次,這可讓您處理比框架還短的最快速動作。 為了讓此方法能夠運作,動作控制器的快取必須處理自最後一個畫面之後由控制器發佈的所有狀態,您可以儲存從 MotionController 擷取的最後一個 MotionControllerReading 時間戳記,並呼叫 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 類別可以公開兩個事件 : Pressed 和 Released – 並從其 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);
}
}
}
}
上述程式碼範例中的 結構可讓註冊事件更容易閱讀:
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))
{
…
}
}