ここでは、「ジャンプ」の処理について、XRIT の JumpProvider と Rigidbody 版の RigidbodyJumpProvider を比較しつつ、Rigidbody 版の詳細を確認していく。
概要
| 項目 | JumpProvider (XRIT) | RigidbodyJumpProvider |
|---|---|---|
| 基底クラス | LocomotionProvider + IGravityController | MonoBehaviour |
| 依存コンポーネント | 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 の衝突判定と整合性が高い。
高度なジャンプ機能の比較
| 機能 | JumpProvider | RigidbodyJumpProvider |
|---|---|---|
| 可変高さジャンプ(長押しで高く) | あり(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.csLibrary/PackageCache/com.unity.xr.interaction.toolkit@.../Runtime/Locomotion/Jump/JumpProvider.csLibrary/PackageCache/com.unity.xr.interaction.toolkit@.../Runtime/Locomotion/Gravity/GravityProvider.cs