ここでは、「スナップ回転」の処理について、XRIT の SnapTurnProvider と Rigidbody 版の RigidbodySnapTurnProvider を比較しつつ、Rigidbody 版の詳細を確認していく。
概要
| 項目 | SnapTurnProvider (XRIT) | RigidbodySnapTurnProvider |
|---|---|---|
| 基底クラス | LocomotionProvider | MonoBehaviour |
| 回転の適用 | XRBodyYawRotation → XRBodyTransformer が 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 にはこの機能がない。
デバウンスの実装比較
| SnapTurnProvider | RigidbodySnapTurnProvider | |
|---|---|---|
| タイマー管理 | 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.csAssets/SimplePhysicsLocomotion/Scripts/RigidbodyTurnProviderBase.csLibrary/PackageCache/com.unity.xr.interaction.toolkit@.../Runtime/Locomotion/Turning/SnapTurnProvider.cs