ここでは、「ジャンプ」の処理について、XRIT の JumpProviderRigidbody 版の RigidbodyJumpProvider を比較しつつ、Rigidbody 版の詳細を確認していく。

概要

項目JumpProvider (XRIT)RigidbodyJumpProvider
基底クラスLocomotionProvider + IGravityControllerMonoBehaviour
依存コンポーネントGravityProvider(必須)Rigidbody + CapsuleCollider + XRInputButtonReader
ジャンプ物理モデル毎フレーム Transform に加算(疑似速度)AddForce(Impulse) 一発
接地判定GravityProvider.isGrounded(頭部から SphereCast)カプセル底部から CheckSphere
更新タイミングUpdate()XROriginMovement キューUpdate() で検出 → FixedUpdate() で適用
XRIT 統合あり(LocomotionMediator 経由)なし

アーキテクチャの違い

JumpProvider(XRIT)

JumpProvider
  ├─ LocomotionProvider (XRIT)
  └─ IGravityController (GravityProvider と連携するインターフェース)

依存関係:
  JumpProvider ─→ GravityProvider(Awake でシーン検索。なければ自身を Disable)
  GravityProvider ─→ JumpProvider(接地状態変化を OnGroundedChanged で通知)

フロー(Update ごと):
  1. JumpInput.ReadIsPerformed() でボタン検出
  2. CanJump() = 接地 or コヨーテタイム or 空中ジャンプ残り
  3. Jump() → m_IsJumping = true、GravityProvider に重力ロック要求
  4. UpdateJump() → CalculateJumpForceForFrame() で減衰する力を計算
  5. transformation.motion = m_JumpVector → TryQueueTransformation()
  6. XRBodyTransformer が transform.position += motion を適用

RigidbodyJumpProvider(カスタム)

RigidbodyJumpProvider
  └─ MonoBehaviour

フロー:
  Update()
    ├─ m_JumpInput.ReadWasPerformedThisFrame() で押した瞬間を検出
    │  (Input System がエッジ検出を内部処理するため m_WasJumpPressed フラグ不要)
    └─ m_JumpRequested = true をキャッシュ

  FixedUpdate()
    ├─ IsGrounded() で接地確認(CapsuleCollider 底部球から CheckSphere)
    ├─ m_JumpRequested && IsGrounded() なら AddForce(up * jumpForce, Impulse)
    └─ m_JumpRequested = false(空中で押し直しは Update で再検出)

ジャンプの物理モデル比較

両者の最も大きな差異のひとつがここにある。

JumpProvider:時間軸で減衰する疑似速度モデル

// CalculateJumpForceForFrame: 正規化時間 0→1 で力が減衰する
float force = (1 - normalizedJumpTime) * m_JumpHeight * approximateForceToMeters;
// ≈ jumpHeight 分の高さに到達するよう経験的に近似
 
// 毎フレーム Transform に加算(CharacterController ベース)
transformation.motion = force * deltaTime * upDirection;
TryQueueTransformation(transformation);  // → transform.position += motion
  • 「ジャンプ高さ(メートル)」を直接指定できる直感的な UX
  • 重力は GravityProvider が別途適用(ジャンプ中は重力を ForcedOff にできる)
  • variableHeightJump: ボタンを離すと早期終了→低いジャンプが可能
  • Transform の直接操作なので Rigidbody の物理ループとは無関係

RigidbodyJumpProvider:AddForce(Impulse) ワンショット

// FixedUpdate() で一度だけ瞬間的な速度変化を与える
m_Rigidbody.AddForce(m_XROrigin.Origin.transform.up * m_JumpForce, ForceMode.Impulse);
  • 力の単位は mass × velocity(Rigidbody の Mass 依存)
  • 以降は Rigidbody の物理演算(重力加速・空気抵抗など)に完全委任
  • ボタンを押し続けても高さは変わらない(ワンショットのみ)
  • シンプルだが「何メートル飛ぶか」の直感的な調整はしにくい

接地判定の比較

JumpProvider(GravityProvider 委任)

// GravityProvider.CheckGrounded() が毎フレーム実行
// 頭の位置から下方向へ SphereCast
m_CastOrigin = GetBodyHeadPosition();         // カメラ高さ
m_CastDirection = -GetCurrentUp();             // 下方向
m_CastDistance = CameraInOriginSpaceHeight;    // 頭の高さ分
Physics.SphereCast(origin, radius, dir, hits, distance, layerMask);
 
// JumpProvider は GravityProvider.isGrounded を参照するだけ
bool CanJump() => m_GravityProvider.isGrounded || コヨーテタイム内;

特徴: CharacterController の高さ中心(頭部)から床まで届く長い SphereCast。XR Origin スケールに追従。

RigidbodyJumpProvider(カプセル底部 CheckSphere)

// CapsuleCollider の底部球の中心を計算
var center = origin.TransformPoint(m_CapsuleCollider.center);
var bottomCenter = center - origin.up * (m_CapsuleCollider.height / 2f - m_CapsuleCollider.radius);
 
// 底部球に少しマージンを足して CheckSphere
Physics.CheckSphere(bottomCenter, m_CapsuleCollider.radius + m_GroundCheckDistance, layerMask);

特徴: CapsuleCollider と同形状の底面球で判定するため、Rigidbody の衝突判定と整合性が高い。


高度なジャンプ機能の比較

機能JumpProviderRigidbodyJumpProvider
可変高さジャンプ(長押しで高く)あり(variableHeightJumpなし
コヨーテタイム(崖端ジャンプ猶予)あり(jumpForgivenessWindowなし
空中ジャンプ(多段ジャンプ)あり(inAirJumpCountなし
ジャンプ中の重力無効化あり(disableGravityDuringJump不要(物理が管理)
ジャンプ高さの直感的な指定あり(メートル単位で近似)なし(Impulse 値を調整)

コヨーテタイムや可変高さジャンプを重視するゲーム品質の UX には JumpProvider が適している。一方 RigidbodyJumpProvider は、物理シミュレーションとの整合性を優先したシンプルな実装で、このプロジェクトの要件には十分だった。どちらが優れているかではなく、必要な機能と実装コストのバランスで選択するとよい。


GravityProvider との連携構造

JumpProvider(密結合)

GravityProvider.Update()
  └─ CheckGrounded() → OnGroundedChanged(isGrounded) を全 IGravityController に通知

JumpProvider.OnGroundedChanged(isGrounded)
  ├─ 接地時: StopJump()、m_CurrentInAirJumpCount リセット
  └─ 離地時(かつ非ジャンプ中): StartCoyoteTimer()

JumpProvider.Jump()
  └─ TryLockGravity(ForcedOff) → GravityProvider が重力を停止

GravityProvider と JumpProvider は双方向に通信する設計。GravityProvider なしでは動作しない。

RigidbodyJumpProvider(独立)

Rigidbody の useGravity = true による物理エンジンの重力だけを使用。他コンポーネントとの通信なし。ジャンプ後の落下は Rigidbody が自動で処理する。


シナリオ別の選択目安

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

シナリオこのプロジェクトでの判断
CharacterController ベースの標準 XRIT 構成JumpProvider が適している
Rigidbody による物理インタラクション重視RigidbodyJumpProvider が適している
コヨーテタイム・多段ジャンプが必要JumpProvider が適している
物理的に正確なジャンプ(質量・摩擦との統合)RigidbodyJumpProvider が適している

参考ファイル

  • Assets/SimplePhysicsLocomotion/Scripts/RigidbodyJumpProvider.cs
  • Library/PackageCache/com.unity.xr.interaction.toolkit@.../Runtime/Locomotion/Jump/JumpProvider.cs
  • Library/PackageCache/com.unity.xr.interaction.toolkit@.../Runtime/Locomotion/Gravity/GravityProvider.cs