ハイライト
visionOS ゲーム入力には 3 つの階層があります。システムジェスチャー(デフォルト選択)、組み合わせジェスチャー(より精密な制御)、カスタムジェスチャー(ARKit hand tracking、Full Space のみ)。物理外設は補完として使用。
主要内容
visionOS ゲームを作るとき、入力方式の選択がプレイヤーが「すぐに遊べるか」を直接決定します。Apple Vision Pro では目と手ですべてを操作しますが、「手」の使い方には 2 種類あります。direct(手を伸ばして仮想物体に直接触れる)と indirect(ターゲットを見て、指を捏合して遠距離操作)。Direct は高エネルギーゲームに適しますが、長時間手を上げると疲れます。Indirect は手を体側に置いたまま、小さな動きを大きな操作に増幅でき、長時間プレイに適しています。Session の Loona パズルゲームが indirect の典型——パズルピースを見て捏合で掴み、ドラッグ、二本指タップで位置合わせ。立っても座ってもプレイ可能。Super Fruit Ninja は direct 路線で、飛んでくるフルーツを直接斬り、稲妻の軌跡と果汁音で haptics 不在を補います。
Apple の助言は明確です。システムジェスチャーがデフォルトであるべき。プレイヤーはプラットフォーム全体で既に使い方を知っており、教える必要がない。追加デバイス不要で開封即使用。Shared Space から Full Space まですべてサポート。システムジェスチャーが足りなければ simultaneously(with:) で複数を組み合わせ。システムジェスチャーと組み合わせジェスチャーの両方で要件を満たせない場合のみ、ARKit hand skeleton tracking でカスタムジェスチャーを検討——ただしカスタムジェスチャーは Full Space のみ使用可能。Shared Space では ARKit 不可。物理外設(Xbox、PlayStation コントローラー、Bluetooth キーボード/トラックパッド)は 2 つ以上の同時入力が必要なゲームや他プラットフォームからの移植に適しています。GameController フレームワークは iOS、iPadOS、macOS、visionOS で完全に同一です。
詳細
システムジェスチャー:最もシンプルな出発点
visionOS が提供するシステムジェスチャー:シングル tap、ダブル tap、pinch and hold、pinch and drag、両手でのスケールと回転。RealityView 内の entity が tap に応答させるには 2 ステップ。entity に InputTargetComponent と CollisionComponent を追加し、RealityView に tap ジェスチャーを付ける。(05:16)
struct ContentView: View {
var body: some View {
RealityView { content in
// For entity targeting to work, entities must have an InputTargetComponent
// and a CollisionComponent!
}
.gesture(TapGesture().targetedToAnyEntity().onEnded { value in
print("Tapped entity \(value.entity)!")
})
}
}
キーポイント:
InputTargetComponentで entity をインタラクティブターゲットにCollisionComponentで entity に衝突検出範囲を提供targetedToAnyEntity()は RealityView 内の任意の entity がトリガー可能onEndedクロージャは tap 終了時に実行。value.entityでタップされた entity を取得
組み合わせジェスチャー:ドラッグ + スケール + 回転を同時実行
simultaneously(with:) で複数ジェスチャーを同時に有効化。Session では衛星操作シーンをデモ:ドラッグで移動、捏合でスケール、手首回転——各ジェスチャー発生時に即座に認識。(07:08)
var manipulationGesture: some Gesture<AffineTransform3D> {
DragGesture()
.simultaneously(with: MagnifyGesture())
.simultaneously(with: RotateGesture3D())
.map { gesture in
let (translation, scale, rotation) = gesture.components()
return AffineTransform3D(
scale: scale,
rotation: rotation,
translation: translation
)
}
}
キーポイント:
simultaneously(with:)で複数ジェスチャーを並行検出。前のジェスチャー未終了でも新しいジェスチャーを認識gesture.components()で平行移動、スケール、回転の 3 成分を分解AffineTransform3Dで 3 つの変換を 1 つの統一 3D 仿射変換に合成SpatialEventGestureはより低レベルのアクセスを提供。Tap Begins/Moves/Ends を追跡。両手で 2 つの異なるターゲットを同時操作する場合に適する
カスタムジェスチャー:ARKit hand skeleton tracking
カスタムジェスチャーには Full Space が必要。Shared Space では ARKit 不可——これはハード制限。Blackbox ゲームはシステムジェスチャーとカスタムジェスチャーを混在使用する例。カスタムジェスチャーで新しいバブルを作成(ステージ出入り)、システムジェスチャーでバブルをタップ。Session では「両手の指先で円を描く」カスタムジェスチャーをデモ。(09:33)
let leftHandIndexFingerTip = leftHandAnchor.skeleton.joint(named: .handIndexFingerTip)
// ...
let leftHandIndexFingerTipWorldPosition = matrix_multiply(leftHandAnchor.originFromAnchorTransform,
leftHandIndexFingerTip.anchorFromJointTransform).columns.3.xyz
// ...
let isCircleShapeGesture = indexFingersDistance < 0.04 && thumbsDistance < 0.04
if isCircleShapeGesture {
// respond to gesture
}
キーポイント:
skeleton.joint(named:)で指定関節のアンカーを取得originFromAnchorTransformとanchorFromJointTransformを乗算し、関節のワールド座標系位置を取得.columns.3.xyzで 4x4 行列から平行移動成分(ワールド xyz)を抽出- 両手の人差し指先距離と親指先距離がともに 4 cm 未満のとき circle ジェスチャーと認識
- カスタムジェスチャーには視覚とサウンドのフィードバックが必須。プレイヤーが正しく実行していることを知るため
ゲームコントローラー:GameController フレームワーク
visionOS は iOS/macOS と同じゲームコントローラーをサポート。Xbox Series X と PlayStation DualSense を含む。接続検出は GCControllerDidConnect 通知。(14:00)
NotificationCenter.default.addObserver(
forName: NSNotification.Name.GCControllerDidConnect,
object: nil, queue: nil) { note in
guard let _controller = note.object as? GCController else { return }
_controller.physicalInputProfile[GCInputButtonA]?.valueChangedHandler = {
//...
}
}
キーポイント:
GCControllerDidConnectはゲーム起動時にも既接続デバイスで発火。取りこぼしなしphysicalInputProfile[GCInputButtonA]でキーパス経由の特定ボタンアクセスvalueChangedHandlerコールバックを設定するか、ボタン状態を能動的にpollGCControllerDidDisconnectも監視し、コントローラー切断時にジェスチャー入力にフォールバック
visionOS 2 では RealityView に .handlesGameControllerEvents(matching:) 修飾子も追加し、このビューがコントローラー入力を受信することを示す。(14:24)
struct ContentView: View {
var body: some View {
RealityView { content in
// Tag your RealityView to respond to controller input events.
}
.handlesGameControllerEvents(matching .gamepad)
}
}
キーポイント:
.handlesGameControllerEvents(matching:)で RealityView が指定タイプのコントローラーイベントを受信.gamepadは標準コントローラー入力にマッチ。.extendedGamepadなども選択可能- visionOS 2 の新しい必須ステップ。この修飾子なしではコントローラー入力がビューにルーティングされない
入力方式と空間タイプの対応関係
| 入力方式 | Shared Space | Full Space |
|---|---|---|
| システムジェスチャー | サポート | サポート |
| ゲームコントローラー/キーボード/トラックパッド | サポート | サポート |
| カスタムジェスチャー(ARKit) | 利用不可 | サポート |
アクセシビリティ
どの入力方式を使っても、異なる能力のプレイヤーに代替手段を提供。Synth Riders は片手モードオプションを提供。RealityKit はネイティブアクセシビリティフレームワークを使用可能。Unity では Apple Accessibility プラグインが使える。
重要ポイント
-
やること:新プロジェクトでは indirect input + システムジェスチャーを優先。価値がある理由:indirect input で手を体側に置いたまま操作でき、長時間プレイでも疲れにくい。システムジェスチャーは学習コストゼロでプレイヤーがすぐ使える。始め方:entity に
InputTargetComponentとCollisionComponentを追加、TapGesture().targetedToAnyEntity()を付け、Shared Space と Full Space の両方で動作することを確認。 -
やること:
simultaneously(with:)で Drag + Magnify + Rotate を組み合わせ。価値がある理由:3D 空間での物体操作は移動、スケール、回転を同時に必要とするのが自然。組み合わせジェスチャーで操作がシームレス。始め方:Session のmanipulationGestureパターンに従い、.mapで 3 成分をAffineTransform3Dに合成。 -
やること:コントローラー入力の自動検出と切り替えを実装。設定画面を探させない。価値がある理由:Wylde Flowers の方式——コントローラー接続で自動切り替え。これがプレイヤーの期待。始め方:
GCControllerDidConnect/GCControllerDidDisconnectを監視、接続時にコントローラー、切断時にジェスチャーへフォールバック、RealityView に.handlesGameControllerEvents(matching .gamepad)を追加。 -
やること:すべての入力方式に視覚フィードバックとサウンドを追加。価値がある理由:Vision Pro には haptics がない。視覚とサウンドがプレイヤーに「何かに触れた」と伝える唯一の手段。始め方:ジェスチャー発火時にサウンド(AVFoundation)+ entity 上の視覚アニメーション。コントローラー入力は CoreHaptics で振動。
関連セッション
- Create custom hover effects in visionOS — 視線ホバーで視覚フィードバックをトリガー。ジェスチャー入力と組み合わせて体験向上
- Create custom visual effects with SwiftUI — SwiftUI 視覚エフェクト実装。ジェスチャートリガーの動的フィードバックに使用
- Elevate your tab and sidebar experience in iPadOS — iPadOS ナビゲーション更新。クロスプラットフォーム入力設計の参考
- Add personality to your app through UX writing — インタラクションコピー設計。カスタムジェスチャーのチュートリアル誘導に参考
コメント
GitHub Issues · utterances