Unityの HP リバーブ G2 コントローラー
HPモーションコントローラは、Windows Mixed Realityコントローラのまったく新しいタイプです:利用可能な入力のセットが少し異なるすべての同じ追跡技術:
- タッチパッドは、右側のコントローラーの場合は A と B、左側のコントローラーの場合は X と Y の 2 つのボタンに置き換えられました。
- Grasp は、押された状態と未押し状態のボタンではなく、0.0 から 1.0 の間の値のストリームを公開するトリガーになりました。
新しい入力には既存の Windows API と Unity API を介してアクセスできないため、専用の Microsoft.MixedReality.Input UPM パッケージが必要です。
重要
このパッケージ内のクラスは、既存の Windows API と Unity API を置き換えるのではなく、それらを補完します。 従来の Windows Mixed Reality コントローラーと HP Motion Controller の両方で一般的に使用できる機能には、既存の API を使用して同じコード パスを使用してアクセスできます。 追加の Microsoft.MixedReality.Input パッケージを使用する必要があるのは、新しい入力だけです。
HP モーション コントローラーの概要
Microsoft.MixedReality.Input.MotionController は、モーション コントローラーを表します。 各 MotionController インスタンスには XR があります。WSA。Input.InteractionSource ピア。ハンドドネス、ベンダー ID、製品 ID、バージョンを使用して関連付けることができます。
MotionController インスタンスを取得するには、InteractionManager イベントを使用して新しい InteractionSource インスタンスを検出するのと同様に、MotionControllerWatcher を作成し、そのイベントをサブスクライブします。 MotionController のメソッドとプロパティは、ボタン、トリガー、2D 軸、サムスティックなど、コントローラーでサポートされる入力を記述します。 MotionController クラスは、 MotionControllerReading クラスを介して入力状態にアクセスするためのメソッドも公開します。 MotionControllerReading クラスは、特定の時点でのコントローラーの状態のスナップショットを表します。
Mixed Reality機能ツールを使用した Microsoft.MixedReality.Input のインストール
新しいMixed Reality機能ツール アプリケーションを使用して、Microsoft.MixedReality.Input プラグインをインストールします。 インストールと使用方法の手順に従い、[Mixed Reality ツールキット] カテゴリで [Mixed Reality入力] パッケージを選択します。
Microsoft.MixedReality.Input の使用
入力値
MotionController では、次の 2 種類の入力を公開できます。
- ボタンとトリガーの状態は、押された量を示す 0.0 から 1.0 の間の一意の float 値で表されます。
- ボタンは 0.0 (押されていない場合) または 1.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);
…
}
}
新しい入力からのイベントの生成
フレームごとに 1 回コントローラーの状態をポーリングする代わりに、すべての状態変更をイベントとして処理できます。これにより、フレーム未満の最も迅速なアクションも処理できます。 この方法を機能させるには、モーション コントローラーのキャッシュで、最後のフレーム以降にコントローラーによって発行されたすべての状態を処理する必要があります。これを行うには、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 の 2 つのイベントを公開し、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))
{
…
}
}