ここでは、「スナップ回転」の処理について、XRIT の SnapTurnProviderRigidbody 版の RigidbodySnapTurnProvider を比較しつつ、Rigidbody 版の詳細を確認していく。

概要

項目SnapTurnProvider (XRIT)RigidbodySnapTurnProvider
基底クラスLocomotionProviderMonoBehaviour
回転の適用XRBodyYawRotationXRBodyTransformer が Transform 回転rb.MovePosition() + rb.MoveRotation()
ピボット点XRBodyYawRotation 内部で計算GetBodyGroundWorldPosition() でカメラ足元を明示計算
更新タイミングUpdate() で Transform 操作Update() で検出 → FixedUpdate() で物理操作
遅延機能(VR 酔い対策)あり(delayTimeなし
XRIT 統合あり(LocomotionMediator 経由)なし

アーキテクチャの違い

SnapTurnProvider(XRIT)

SnapTurnProvider
  └─ LocomotionProvider (XRIT)

フロー(Update ごと):
  1. 左右入力を合算(left + right)
  2. CardinalUtility.GetNearestCardinal() で4方向に分類
     ├─ East  → +turnAmount°
     ├─ West  → -turnAmount°
     ├─ South → 180°(TurnAroundActivated でなければ)
     └─ North → 0(無視)
  3. StartTurn(amount) → TryStartLocomotionImmediately()
  4. locomotionState == Moving なら:
     transformation.angleDelta = amount → TryQueueTransformation()
     → XRBodyTransformer が XRBodyYawRotation を適用(Transform 回転)
  5. m_TimeStarted を記録 → debounceTime 経過後リセット

RigidbodySnapTurnProvider(カスタム)

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

フロー:
  Update()
    ├─ debounceTimer を減算
    ├─ ReadInput() で左右入力を合算
    ├─ GetTurnAmount(): 閾値(0.5f)超えで角度を決定
    ├─ 立ち上がりエッジ(!m_WasActive)かつデバウンス満了で
    │   m_CurrentTurnAmount に角度を設定
    └─ m_WasActive を更新

  FixedUpdate()
    ├─ m_CurrentTurnAmount ≠ 0 なら TurnRig()(基底クラスのメソッド)
    └─ m_CurrentTurnAmount = 0 にリセット

入力検出の違い

SnapTurnProvider:CardinalUtility による4方向分類

var input = leftHandValue + rightHandValue;  // 左右合算
var cardinal = CardinalUtility.GetNearestCardinal(input);
// 入力ベクトルの方向を North/South/East/West に分類
// East が右スナップ、West が左スナップ、South が 180°

特徴:

  • 入力方向を角度で判定するため、斜め入力でも東西南北に分類される
  • 左右合算なので両手の入力が同時に有効

RigidbodySnapTurnProvider:閾値比較 + 立ち上がりエッジ

// 左右入力を合算(ReadInput() は基底クラスのメソッド)
var input = ReadInput();  // = leftHandValue + rightHandValue
 
float GetTurnAmount(Vector2 input)
{
    if (m_EnableTurnAround && input.y <= -k_Threshold)  // 後ろ倒しを優先
        return 180f;
    if (m_EnableTurnLeftRight && Mathf.Abs(input.x) >= k_Threshold)
        return Mathf.Sign(input.x) * m_TurnAmount;
    return 0f;
}
 
// 立ち上がりエッジ検出(押した瞬間の1回のみ)
if (isActive && !m_WasActive && m_DebounceTimer <= 0f)
    m_CurrentTurnAmount = snapAngle;

特徴:

  • XRIT と同様に左右入力を合算(基底クラス ReadInput() に委ねる)
  • input.y <= -threshold を先にチェックするため、後ろ倒しが左右より優先
  • 立ち上がりエッジで検出するため、押し続けても連続スナップしない(デバウンスとは別の制御)

回転の適用方法の違い

SnapTurnProvider:XRBodyTransformer 経由の Transform 操作

transformation.angleDelta = turnAmount;
TryQueueTransformation(transformation);
// → XRBodyYawRotation.Apply() が XROrigin.Origin を Y 軸回転
// → Transform.rotation を直接変更(物理エンジン外)
  • ピボット点の計算は XRBodyYawRotation 内部で行われる(カメラ位置基準)
  • LocomotionState の管理により、他プロバイダとの排他制御が効く
  • Rigidbody が存在する場合、Transform の直接変更で Rigidbody の位置ずれが起きる可能性

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

回転の適用ロジックは RigidbodyTurnProviderBase.TurnRig() に集約されている。

// RigidbodyTurnProviderBase.TurnRig()(両 Turn プロバイダが共用)
protected void TurnRig(float turnAmount)
{
    var origin = m_XROrigin.Origin.transform;
    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);
}
 
// ピボット計算:CameraInOriginSpacePos のローカル Y を 0 にして床面上の点を得る
protected Vector3 GetBodyGroundWorldPosition()
{
    var bodyLocalPos = m_XROrigin.CameraInOriginSpacePos;
    bodyLocalPos.y = 0f;
    return m_XROrigin.Origin.transform.TransformPoint(bodyLocalPos);
}
  • MovePosition/MoveRotation は FixedUpdate で物理エンジンと協調して動作
  • ピボット点をカメラ足元(床面上の頭部 XZ 位置)として明示的に計算
  • Rigidbody の衝突判定を維持したまま回転できる

delayTime(VR 酔い対策)

SnapTurnProvider のみ

[SerializeField]
float m_DelayTime;  // 最初のスナップ前の遅延(秒)
 
public override bool canStartMoving => m_DelayTime <= 0f || Time.time - m_DelayStartTime >= m_DelayTime;

スナップ前に意図的に待つことで、その間に Vignette(視界絞り)などのコンフォートエフェクトを再生する時間を確保できる。RigidbodySnapTurnProvider にはこの機能がない。


デバウンスの実装比較

SnapTurnProviderRigidbodySnapTurnProvider
タイマー管理m_TimeStarted(Time.time 基準)m_DebounceTimer(カウントダウン)
計測開始回転適用直後スナップ予約直後
継続押下の扱いm_TimeStarted > 0f 中は StartTurn をスキップm_WasActive で立ち上がりエッジのみ反応(継続押下は無視)

シナリオ別の選択目安

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

シナリオこのプロジェクトでの判断
標準 XRIT 構成(CharacterController)SnapTurnProvider が適している
Rigidbody 物理構成RigidbodySnapTurnProvider が適している
VR 酔い対策の delay/vignette 演出が必要SnapTurnProvider が適している
物理エンジンの衝突判定を維持した回転RigidbodySnapTurnProvider が適している

参考ファイル

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