ここでは、「連続回転」の処理について、XRIT の ContinuousTurnProviderRigidbody 版の RigidbodyContinuousTurnProvider を比較しつつ、Rigidbody 版の詳細を確認していく。

概要

項目ContinuousTurnProvider (XRIT)RigidbodyContinuousTurnProvider
基底クラスLocomotionProviderRigidbodyTurnProviderBase(abstract MonoBehaviour)
回転の適用XRBodyYawRotationXRBodyTransformer が Transform 回転rb.MovePosition() + rb.MoveRotation()
ピボット点XRBodyYawRotation 内部で計算GetBodyGroundWorldPosition() でカメラ足元を明示計算
更新タイミングUpdate() で Transform 操作Update() で入力読み取り → FixedUpdate() で物理操作
アナログ入力対応あり(input.magnitude でスケール)あり(input.magnitude でスケール)
180° ターンアラウンドあり(South 方向入力)なし
XRIT 統合あり(LocomotionMediator 経由)なし

アーキテクチャの違い

ContinuousTurnProvider(XRIT)

ContinuousTurnProvider
  └─ LocomotionProvider (XRIT)

フロー(Update ごと):
  1. 左右入力を合算(left + right)
  2. CardinalUtility.GetNearestCardinal() で4方向に分類
  3. GetTurnAmount():
     ├─ East/West → input.magnitude × sign × turnSpeed × deltaTime
     ├─ South     → 180°(TurnAroundActivated でなければ)
     └─ North     → 0
  4. TurnRig(turnAmount) → TryStartLocomotionImmediately()
  5. locomotionState == Moving なら:
     transformation.angleDelta = turnAmount → TryQueueTransformation()
     → XRBodyTransformer が XRBodyYawRotation を適用
  6. 入力ゼロで m_TurnAroundActivated をリセット

RigidbodyContinuousTurnProvider(カスタム)

RigidbodyContinuousTurnProvider
  └─ RigidbodyTurnProviderBase (abstract MonoBehaviour)
       共通フィールド: m_Rigidbody, m_XROrigin, m_EnableTurnLeftRight, 左右入力
       共通メソッド: ReadInput()(左右合算), TurnRig(), GetBodyGroundWorldPosition()

フロー:
  Update()
    ├─ ReadInput() で左右入力を合算
    └─ m_CurrentInput にキャッシュ

  FixedUpdate()
    ├─ angle = input.magnitude × Sign(input.x) × turnSpeed × fixedDeltaTime
    └─ angle ≠ 0 なら TurnRig(angle)(基底クラスのメソッド)
         ├─ GetBodyGroundWorldPosition() でカメラ足元ピボットを計算
         ├─ rb.MovePosition() で位置を補正
         └─ rb.MoveRotation() で回転を適用

入力スケーリングの違い

連続回転では、スティックをどれだけ傾けたかに比例して回転速度を変えるアナログ対応が重要になる。

ContinuousTurnProvider:magnitude でスケール

// GetTurnAmount()
// East/West の cardinal 方向になった場合に限り、入力全体の magnitude で回転量を調整
return input.magnitude * (Mathf.Sign(input.x) * m_TurnSpeed * Time.deltaTime);

input.magnitude は斜め入力(x と y 両方に値がある場合)も考慮した大きさになる。また CardinalUtility.GetNearestCardinal() を経由するため、Y 軸入力が強い場合は East/West に分類されず回転しないことがある。

RigidbodyContinuousTurnProvider:magnitude(XRIT と同じ方式)

// GetTurnAmount()
return input.magnitude * (Mathf.Sign(input.x) * m_TurnSpeed * Time.fixedDeltaTime);

XRIT と同様に input.magnitude を使うため、斜め入力でも大きさに応じてスケールされる。Time.fixedDeltaTime を使う点のみ XRIT と異なる。Y 成分を含む magnitude を使うため、CardinalUtility なしでもアナログ的な速度制御が可能。


回転の適用方法の違い

SnapTurnProvider vs RigidbodySnapTurnProvider と同じ原理。

ContinuousTurnProvider:XRBodyTransformer 経由(Transform 直接)

transformation.angleDelta = turnAmount;
TryQueueTransformation(transformation);
// XRBodyYawRotation.Apply() が XROrigin.Origin の Transform.rotation を変更

RigidbodyContinuousTurnProvider:MovePosition + MoveRotation(基底クラス TurnRig())

RigidbodyTurnProviderBase.TurnRig() を呼ぶだけ。SnapTurnProvider と共通実装。

// RigidbodyTurnProviderBase.TurnRig()(共通)
protected void TurnRig(float turnAmount)
{
    var pivot = GetBodyGroundWorldPosition();     // カメラ足元ピボット
    var rot = Quaternion.AngleAxis(turnAmount, origin.up);
    m_Rigidbody.MovePosition(pivot + rot * (m_Rigidbody.position - pivot));
    m_Rigidbody.MoveRotation(rot * m_Rigidbody.rotation);
}

MovePosition/MoveRotation は FixedUpdate の物理ステップ内で衝突判定を維持しながら移動するため、Rigidbody 環境に適している。


180° ターンアラウンドの扱い

ContinuousTurnProvider にあり

case Cardinal.South:
    if (m_EnableTurnAround && !m_TurnAroundActivated)
        return 180f;
    break;
// 入力がゼロに戻るとフラグリセット → 再び南に倒せば再発動

スティックを後ろに倒すと 180° 即時スナップ回転。連続回転プロバイダがスナップ回転も兼ねている形。

RigidbodyContinuousTurnProvider にはなし

X 軸のみ見るため後ろ倒し(Y < 0)は無視される。180° ターンアラウンドが必要な場合は RigidbodySnapTurnProvider と組み合わせる。


LocomotionState 管理の有無

ContinuousTurnProvider(状態管理あり)

protected void Update()
{
    m_IsTurningXROrigin = false;
    var turnAmount = GetTurnAmount(ReadInput());
    TurnRig(turnAmount);
 
    if (!m_IsTurningXROrigin)
        TryEndLocomotion();   // 入力ゼロで Locomotion を終了通知
}
  • LocomotionState の Moving / Ended 遷移を管理
  • 他の Locomotion プロバイダとの排他制御に参加
  • locomotionStarted/Ended イベントで Vignette 等の UX コンポーネントと連携

RigidbodyContinuousTurnProvider(状態管理なし)

入力がゼロなら angle ≈ 0ApplyPivotYawRotation が呼ばれないだけ。LocomotionState の概念がなく、他プロバイダとの調整も行わない。


フレームタイミングの違いと影響

ContinuousTurnProviderRigidbodyContinuousTurnProvider
入力読み取りUpdate()Update()
回転適用Update() 内で Transform 操作FixedUpdate() 内で Rigidbody 操作
フレームレート依存Time.deltaTime でスケールTime.fixedDeltaTime でスケール
Update と FixedUpdate のずれなしm_CurrentInput バッファで吸収

FixedUpdate は物理演算と同期するため回転が物理エンジンに対して正確に適用される。一方で UpdateFixedUpdate の実行タイミングがずれるため、m_CurrentInput を介したバッファリングが必要。


シナリオ別の選択目安

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

シナリオこのプロジェクトでの判断
標準 XRIT 構成(CharacterController)ContinuousTurnProvider が適している
Rigidbody 物理構成RigidbodyContinuousTurnProvider が適している
後ろ倒し 180° ターンアラウンドが必要ContinuousTurnProvider が適している
物理衝突を維持した滑らかな回転RigidbodyContinuousTurnProvider が適している
精密なアナログ入力スケーリングが必要ContinuousTurnProvider が適している

参考ファイル

  • Assets/SimplePhysicsLocomotion/Scripts/RigidbodyContinuousTurnProvider.cs
  • Assets/SimplePhysicsLocomotion/Scripts/RigidbodyTurnProviderBase.cs
  • Library/PackageCache/com.unity.xr.interaction.toolkit@.../Runtime/Locomotion/Turning/ContinuousTurnProvider.cs