ここでは、「移動」の処理について、XRIT の DynamicMoveProviderRigidbody 版の RigidbodyMoveProvider を比較しつつ、Rigidbody 版の詳細を確認していく。

概要

項目DynamicMoveProviderRigidbodyMoveProvider
基底クラスContinuousMoveProviderLocomotionProviderMonoBehaviour
物理コンポーネントCharacterControllerRigidbody + CapsuleCollider
移動適用Update() 内で XROriginMovement (Transform 操作)FixedUpdate() 内で rb.linearVelocity 操作
重力管理GravityProvider または CharacterControllerRigidbody の物理演算に完全委託
XRIT Locomotion 統合あり(LocomotionMediator 経由)なし

アーキテクチャの違い

DynamicMoveProvider(CharacterController ベース)

DynamicMoveProvider
  └─ ContinuousMoveProvider (XRIT)
       └─ LocomotionProvider (XRIT)
            └─ MonoBehaviour

フロー:
  Update()
    ├─ DynamicMoveProvider.ComputeDesiredMove()
    │    └─ m_CombinedTransform の向きをブレンドして設定
    │         └─ base.ComputeDesiredMove() (ContinuousMoveProvider)
    │              └─ 移動ベクトル(world space)を計算
    └─ MoveRig(translationInWorldSpace)
         └─ TryQueueTransformation(XROriginMovement)
              └─ XRBodyTransformer が CharacterController.Move() を呼ぶ

LocomotionProvider を継承しているため、LocomotionMediator を通じた状態管理(Moving / Ended)と、他の Locomotion プロバイダ(Climb・Turn・Jump 等)との優先度管理が行われる。

RigidbodyMoveProvider(Rigidbody ベース)

RigidbodyMoveProvider
  └─ MonoBehaviour

フロー:
  Update()
    └─ ComputeHorizontalVelocity(leftValue, rightValue)
         └─ 水平速度ベクトルをキャッシュ(m_DesiredHorizontalVelocity)

  FixedUpdate()
    └─ rb.linearVelocity の XZ 成分を上書き
         (Y 成分は Rigidbody の物理演算に委ねる)

LocomotionMediator を一切使用しない独立した実装。PhysicsHand02 シーンでは他の Locomotion プロバイダもすべて物理ベースに置き換えるため、XRIT との統合は不要。


方向計算の比較

両者とも「左右スティックの入力量に応じて、基準方向(頭 or 手)をブレンドする」という同じロジックを持つ。実装アプローチが異なる。

DynamicMoveProvider のアプローチ:中間 GameObject を使ったブレンド

// Awake() で空の GameObject を作成し、forwardSource として登録
m_CombinedTransform = new GameObject("[Dynamic Move Provider] Combined Forward Source").transform;
forwardSource = m_CombinedTransform;
 
// ComputeDesiredMove() でブレンドした回転を m_CombinedTransform に適用
var combinedRotation = Quaternion.Slerp(m_RightMovementPose.rotation, m_LeftMovementPose.rotation, leftHandBlend);
m_CombinedTransform.SetPositionAndRotation(combinedPosition, combinedRotation);
 
// あとは base.ComputeDesiredMove() に委ねる
return base.ComputeDesiredMove(input);

ContinuousMoveProviderforwardSource という既存のフックを利用し、ブレンド結果を Transform として渡すことで、基底クラスの移動計算をそのまま再利用している。

RigidbodyMoveProvider のアプローチ:速度ベクトルを直接計算

Vector3 ComputeHorizontalVelocity(Vector2 leftValue, Vector2 rightValue)
{
    // 入力量の比率でブレンド係数を決定
    var leftHandBlend = totalSqrMagnitude > Mathf.Epsilon ? leftSqr / totalSqrMagnitude : 0.5f;
 
    // 左右の基準回転を Slerp でブレンド
    var leftRot  = GetSourceRotation(m_LeftHandMovementDirection,  m_LeftControllerTransform);
    var rightRot = GetSourceRotation(m_RightHandMovementDirection, m_RightControllerTransform);
    var blendedForward = Quaternion.Slerp(rightRot, leftRot, leftHandBlend) * Vector3.forward;
 
    // XROrigin の up 平面に投影して水平方向の forward を得る
    var projectedFwd = Vector3.ProjectOnPlane(blendedForward, up);
    var forwardRotation = Quaternion.FromToRotation(originTransform.forward, projectedFwd);
 
    // 入力ベクトルに回転を掛けて速度を得る
    var inputMove = Vector3.ClampMagnitude(new Vector3(strafe ? totalInput.x : 0f, 0f, totalInput.y), 1f);
    return originTransform.TransformDirection(forwardRotation * inputMove) * m_MoveSpeed;
}

GameObject を介さず、回転のブレンド→水平投影→速度計算を一気通貫で行う。


移動の適用方法の違い

このプロジェクトにおいて、両者の最も大きな差異のひとつがここにある。

CharacterController.Move()(DynamicMoveProvider)

// XROriginMovement.Apply() の内部(Unity XRIT パッケージ内)
characterController.Move(motion);
// または
origin.transform.position += motion;
  • Update() タイミングで呼ぶ(物理エンジンのサイクルと無関係)
  • CharacterController 自体が衝突判定・スライディングを処理する
  • Rigidbody は不要(CharacterController と Rigidbody は通常共存させない)
  • isGrounded プロパティで接地判定可能

Rigidbody.linearVelocity の XZ 上書き(RigidbodyMoveProvider)

void FixedUpdate()
{
    var v = m_Rigidbody.linearVelocity;
    v.x = m_DesiredHorizontalVelocity.x;
    v.z = m_DesiredHorizontalVelocity.z;
    // v.y は上書きしない → 重力・ジャンプの Y 速度を保持
    m_Rigidbody.linearVelocity = v;
}
  • FixedUpdate() タイミングで適用(物理エンジンと同期)
  • Rigidbody + CapsuleCollider が衝突・スライディングを処理
  • Y 速度を保持することで重力とジャンプを物理エンジンに委ねられる
  • Update()FixedUpdate() が別タイミングのため、m_DesiredHorizontalVelocity を介してバッファリングが必要

重力の扱いの違い

DynamicMoveProvider(GravityProvider 連携)

GravityProvider(独立コンポーネント)
  ├─ isGrounded の監視
  ├─ m_GravityDrivenVelocity の蓄積
  └─ CharacterController.Move() に重力分を含めて渡す
  • OnLocomotionStateChanging()GravityProvider に重力のロック/アンロックを通知
  • 空中では inAirControlModifier で水平入力をスムージング(急な方向転換を抑制)
  • enableFly フラグで重力を無効化できる

RigidbodyMoveProvider(物理エンジン委託)

Rigidbody
  └─ useGravity = true(Inspector で設定)
       └─ 物理エンジンが Y 方向に重力加速度を自動適用
  • 重力の計算コードが一切不要
  • ただし衝突時の挙動(バウンド・摩擦)は Rigidbody の Physics Material に依存
  • 物理的に正確だが、ゲーム的な調整(空中制御の減衰など)は自前実装が必要

XR Origin スケール対応

DynamicMoveProvider(対応あり)

// ContinuousMoveProvider.ComputeDesiredMove() 内
var speedFactor = m_MoveSpeed * deltaTime * originTransform.localScale.x;

ユーザーのスケール(身長補正など)に応じて移動速度が自動調整される。

RigidbodyMoveProvider(対応なし)

return originTransform.TransformDirection(velocityInRigSpace) * m_MoveSpeed;
// localScale の補正なし

XR Origin のスケールを変更した場合、速度が体感的にずれる可能性がある。


LocomotionMediator との関係

DynamicMoveProvider(統合あり)

  • LocomotionProvider 継承により LocomotionState(Idle / Starting / Moving / Ended)を管理
  • TryQueueTransformation() で他プロバイダと排他制御(例:テレポート中は移動しない)
  • locomotionStarted / locomotionEnded イベントで UI(Vignette など)と連携

RigidbodyMoveProvider(統合なし)

  • LocomotionMediator を一切使わない
  • 他プロバイダとの排他制御は行わない
  • PhysicsHand02 シーンでは全 Locomotion を物理ベースで統一するため、これで問題なし

シナリオ別の選択目安

以下はあくまで私のプロジェクトでの判断基準であり、要件によって異なる選択が適切になる場合もある。

シナリオこのプロジェクトでの判断
標準的な VR 移動(XRIT 標準構成)DynamicMoveProvider が適している
Rigidbody による物理インタラクションが必要RigidbodyMoveProvider が適している
物理ハンドと環境の押し合いが必要RigidbodyMoveProvider が適している
Climb / Grab との物理的な統合が必要RigidbodyMoveProvider が適している
テレポートなど XRIT 標準 Locomotion との共存DynamicMoveProvider が適している

参考ファイル

  • Assets/Samples/XR Interaction Toolkit/3.3.0/Starter Assets/Scripts/DynamicMoveProvider.cs
  • Assets/SimplePhysicsLocomotion/Scripts/RigidbodyMoveProvider.cs
  • Library/PackageCache/com.unity.xr.interaction.toolkit@.../Runtime/Locomotion/Movement/ContinuousMoveProvider.cs