【UE5】VRテンプレートのWidget Interaction Componentの使われ方

プログラマの尾関です。

UE5のVRテンプレートには、3D Widget とのインタラクションするために「Widget Interaction Component」というコンポーネントが使われています。

具体的には VRPawn アセットのコンポーネント階層にモーションコントローラーの下に "WidgetInteractionRight" と "WidgetInteractionLeft" という名前でアタッチされています。

この "Widget Interaction Component" が VRテンプレートでどのように使われているのかを解説したいと思います。

3D Widgetの呼び出し部分

3D Widget である "Menu" アセットは VRPawn の ToggleMenu から呼び出されています。

Spawn Actorで "Menu" をスポーンしています。

Menuのコンポーネント階層は以下のようになっています。

  • Menu (Self): メニューアクターそのもの
    • DefaultSceneRoot: シーンルート
      • MenuLaser: Niagaraで作成されたレーザーポインタ
      • Widget: User Widgetを 3D UI として表示するためのコンポーネント
        • Cursor: 丸いカーソルポインタ

Widget (3D UI) の設定項目

Widgetコンポーネントが User Widget を3D空間に表示するためのものですが、「ユーザーインターフェース > ウィジェットクラス」に "WidgetMenu" が指定されており、これが3D UIのもととなる User Widgetです。

WidgetMenuアセットは以下のような User Widget となっています。

Cursor (3Dカーソル)

VRテンプレートでは、レーザーポインタが当たった部分に (またはサムスティック移動で) 丸いカーソルが出るのですが、それについてはスタティックメッシュを使って表現しています。

サイズが小さい Sphereメッシュで、UI用に unlit (ライトが当たらない) マテリアルを使っています。

MenuのBeginPlayイベントは大まかに以下の処理を行っています。

  1. IMCの追加
  2. コンポーネント参照の準備 (Set Widget Interaction References, Set Motion Controller Reference)
  3. メニューを開くアニメーションを再生
  4. アニメーション再生後 (Open Menu Animation Timeline の完了後)、カーソルがWidget外に移動しないように設定

Widget Interaction Component の参照を保持するところ

個人的に引っかかったところとして、Widget Interaction Component の左右判定として "Pointer Index" というパラメータを使用しています。

Widget Interaction Componentには "Pointer Index" があります。

VRテンプレートでは、この値を使って Widget Interaction Component の左右判定を行っています。

モーションコントローラーの参照を保持するところ

もう1つ、個人的に引っかかったところとして、左手・右手判定に "Motion Source" というパラメータを使っています。

このパラメータは Widget Interaction Component ではなく、Motion Controller Component (モーションコントローラー) のパラメータですね。

Information

このあたり混乱しないように Widget Interaction Component (WIC) を扱っているのか、それとも Motion Controller Component (MCC) を扱っているのかを意識して使う必要がありそうです。

Event Tickはやや大きめのBlueprint となっていますが、おおよそ以下の動きとなっていました。

  1. Widget (メニュー) の位置をカメラの向いている方向の少し下あたりに移動させる
  2. Widget (メニュー) の向きをカメラの方向に向く (ビルボード) ようにする
  3. Widget Interaction Componentのヒットテストを行う
    1. ヒットテストがTrueの場合は、レーザーポインタを有効 (初回1回目のみ) にして、レーザーポインタ (Niagara) の開始点と終端を設定する
    2. ヒットテストがFalseの場合は、レーザーポインタを非表示にして、 サムスティックでのカーソル操作を有効にする

1. Widgetを見やすい場所に移動

  1. Widget (メニュー) から見たカメラへの “Find Look at Rotation” で向きを求める
  2. 正面ベクトル (Get Forward Vector) と Upベクトル を足し込んで斜め上に移動
  3. Widget (メニュー) の位置をカメラに近づくように動かす

2. Widgetをビルボード処理する

Find Look at Rotation” で Widget をカメラの向きに回転する

3. Widget Interaction ComponentがWidgetとヒットしているかどうかを判定

Widget Interaction Component の “Is Over Hit Test Visible Widget” で WIC (レーザーポインタ) が Widget と交差しているかどうかをチェックする

  1. Widgetと交差している場合: レーザーポインタの処理
  2. 交差していない場合: サムスティックの操作

4. レーザーポインタの入力の有効化と入力チェック

  1. レーザーポインタ (Niagara) を表示 (可視化)
  2. Widget Interaction Compontから “Attach Component To Component” を呼び出し、Motion Controller Commonent (モーションコントローラー) にアタッチする
    1. WIC を MCC の子としてアタッチ (親:MCC, 子:WIC)
    2. ソケットの指定はなし
    3. 位置と回転は MCC に依存。スケールのみ影響を受けない
    4. 物理シミュレーションは結合 (Weld Simulated Bodies: ON)。WICはMCCに物理的に一体化し、独立した衝突判定を失う (親の衝突の一部として扱われる)
  3. GetLastHitResult_ImpactPointノードで、WICの Impact Point (レーザーポインタの衝突地点) を取得
    1. 内部的には Widget Interaction Component の “Get Last Hit Result” の呼び出しにより取得した値を使っている
  4. カーソルの位置には Impact Point を使う
  5. レーザーポインタの開始地点には WICの位置を使い、終端には Impact Point を使う

5. サムスティックの有効化とカーソルの移動

  1. レーザーポインタ (Niagara) を非表示
  2. Cursor の “Attach Component To Component” を呼び出し、WICにアタッチ
  3. WICの “Set Relative Location” を呼び出し、位置の調整とコリジョン無効、ワープ有効にする
    1. New Location (x, y, z) = (-10, 0, 0) によって X軸方向に-10移動
    2. Sweep無効:移動中に衝突があってもコリジョンを無視する
    3. Teleport有効:WICは指定された位置に瞬時に移動。速度や加速度の計算は行いません
  4. Get IA_Menu_Cursor_[Right/Left] でサムスティックの移動量を取得
  5. 移動可能な範囲に移動楼を丸める
  6. カーソルの移動後の位置を設定する

WICと User Widgetのインタラクション (交差判定) はどこで行っているのか?

WIC (Widget Interaction Component) は基本的に複雑な設定なしで、User Widget との交差判定ができます。

WICには「インタラクション」のカテゴリがあり、そこでレイキャスト (レーザーポインタ) の距離やチャンネルを指定できます。

ここで引っかかったのが「Trace Channel」の項目。

"ECC Game Trace Channel 1" という謎のパラメータとなっています。

調べたところ「Trace Channel」は "ECollisionChannel" という列挙型で以下のものが一般的な値のようです。

  1. ECC_WorldStatic: 動かない静的なオブジェクト(建物、地形など)に使用されます
  2. ECC_WorldDynamic: 動く可能性のある動的オブジェクトに使用されます
  3. ECC_Pawn: プレイヤーやAIが制御するキャラクターに使用されます
  4. ECC_PhysicsBody: 物理シミュレーションに基づいて動くオブジェクトに使用されます
  5. ECC_Vehicle: 乗り物に対するコリジョンに使用されます
  6. ECC_Destructible: 破壊可能なオブジェクトに使用されます

では "ECC Game Trace Channel 1" とは一体…? とソースコードを見てみてところ“Hidden” 項目となっていました。

UENUM(BlueprintType)
enum ECollisionChannel : int
{

  ECC_WorldStatic UMETA(DisplayName="WorldStatic"),
  ECC_WorldDynamic UMETA(DisplayName="WorldDynamic"),
  ECC_Pawn UMETA(DisplayName="Pawn"),
  ECC_Visibility UMETA(DisplayName="Visibility" , TraceQuery="1"),
  ECC_Camera UMETA(DisplayName="Camera" , TraceQuery="1"),
  ECC_PhysicsBody UMETA(DisplayName="PhysicsBody"),
  ECC_Vehicle UMETA(DisplayName="Vehicle"),
  ECC_Destructible UMETA(DisplayName="Destructible"),

  /** Reserved for gizmo collision */
  ECC_EngineTraceChannel1 UMETA(Hidden), // ←ここに "ECC Game Trace Channel 1" がある

  ECC_EngineTraceChannel2 UMETA(Hidden),
  ...

色々試してわかったのですが、VRテンプレートでは、UE5.5では未定義となったトレースオブジェクトを使って 3D Widget とのレイトレースを行うために、このような設定になっているのではないかと思われます。

というのも、トレースオブジェクトの設定は「プロジェクト設定 > エンジン > コリジョン」から Trace Channels > 新規のトレースチャンネルで作成すると、カスタムのトレースオブジェクトを作成することができます。

これを使って、例えば "3DWidget" というチャンネルを新しく作ります。

すると "Menu" アセットの Widgetコンポーネントのコリジョンプリセットに "3DWidget" のみオーバーラップとなっていることがわかりました。

なおチャンネル追加前だと、3DWidgetの項目が存在しません。そのためコリジョンレスポンスが「すべて無視する」という設定だと勘違いして、半日ほど調べたり色々試したりしました。

ということで、3DのUIを作るときは、Trace Channel 周りの設定は注意した方が良いかもしれませんね…。

Information

Widget Interaction Componentの交差判定を有効にする設定について、まとめると以下のとおりです。

  1. Widget Interaction ComponentTrace Channel で、衝突を行うチャンネルを設定します
  2. Widgetコンポーネントコリジョンレスポンスには、WICのTrace Channelで指定したチャンネルと同じものを「オーバーラップ」として指定します

3D Widget の入力判定はどこで行っているのか?

これは Menu アセット側で、WIC の "Press Pointer Key" という関数でマウス操作をシミュレートしているようです。

  1. IA_Menu_Interact_Left_Pressed イベントを受取り
  2. Press Pointer Key で「マウス左クリック」をシミュレート
  3. Release Pointer Key で「マウス左ボタンを離した」をシミュレート
Information

Widget Interaction Componentの「Press Pointer Key」は、Widgetに対してのデバイス操作をシミュレーションします。

Information

ボタンを離したときの「Release Pointer Key」も忘れずに呼ぶようにします。

これを呼び出さないと「Press状態」が継続してボタンが押せなくなってしまうためです。

User Widgetのクリック判定 (Click Method) は "Mouse Down" にする

VRテンプレート実装されている User Widget の Click Method は "Mouse Down" となっています。

これは、モーションコントローラーのポインタは、トリガーボタン押下ですぐに決定と判定した方が操作性がしやすいためですね。

Widget Interaction Componentと3D Widgetとの交差判定のデバッグ

WICで交差判定が正常に行われない場合に調査する方法として、Show Debug の項目にチェックを入れると、指から赤い線が表示されてレイキャストが可視化されます。

おしまい

VRテンプレートの Widget Interaction Component の使われ方を調べた情報でした。

もし UEで VRゲームを作ってみたいときの参考になれば何よりです。

Follow me!