ここでは、「連続回転」の処理について、XRIT の ContinuousTurnProvider と Rigidbody 版の RigidbodyContinuousTurnProvider を比較しつつ、Rigidbody 版の詳細を確認していく。
概要
| 項目 | ContinuousTurnProvider (XRIT) | RigidbodyContinuousTurnProvider |
|---|---|---|
| 基底クラス | LocomotionProvider | RigidbodyTurnProviderBase(abstract MonoBehaviour) |
| 回転の適用 | XRBodyYawRotation → XRBodyTransformer が 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 ≈ 0 → ApplyPivotYawRotation が呼ばれないだけ。LocomotionState の概念がなく、他プロバイダとの調整も行わない。
フレームタイミングの違いと影響
| ContinuousTurnProvider | RigidbodyContinuousTurnProvider | |
|---|---|---|
| 入力読み取り | Update() | Update() |
| 回転適用 | Update() 内で Transform 操作 | FixedUpdate() 内で Rigidbody 操作 |
| フレームレート依存 | Time.deltaTime でスケール | Time.fixedDeltaTime でスケール |
| Update と FixedUpdate のずれ | なし | m_CurrentInput バッファで吸収 |
FixedUpdate は物理演算と同期するため回転が物理エンジンに対して正確に適用される。一方で Update と FixedUpdate の実行タイミングがずれるため、m_CurrentInput を介したバッファリングが必要。
シナリオ別の選択目安
以下はあくまでこのプロジェクトでの判断基準であり、要件によって異なる選択が適切になる場合もある。
| シナリオ | このプロジェクトでの判断 |
|---|---|
| 標準 XRIT 構成(CharacterController) | ContinuousTurnProvider が適している |
| Rigidbody 物理構成 | RigidbodyContinuousTurnProvider が適している |
| 後ろ倒し 180° ターンアラウンドが必要 | ContinuousTurnProvider が適している |
| 物理衝突を維持した滑らかな回転 | RigidbodyContinuousTurnProvider が適している |
| 精密なアナログ入力スケーリングが必要 | ContinuousTurnProvider が適している |
参考ファイル
Assets/SimplePhysicsLocomotion/Scripts/RigidbodyContinuousTurnProvider.csAssets/SimplePhysicsLocomotion/Scripts/RigidbodyTurnProviderBase.csLibrary/PackageCache/com.unity.xr.interaction.toolkit@.../Runtime/Locomotion/Turning/ContinuousTurnProvider.cs