WWDC Quick Look 💓 By SwiftGGTeam
Bring keyboard and mouse gaming to iPad

Bring keyboard and mouse gaming to iPad

元の動画を見る

ハイライト

Apple がゲーム コントローラー フレームワークに追加GCKeyboardそしてGCMouseこれにより、iPad ゲームは、マージされたキーボード キーの状態、マウス デルタ イベントを読み取り、UIKit ポインター ロックを使用して、Dock やエッジ ジェスチャーが全画面ゲームプレイを中断するのを防ぐことができます。

主要内容

多くの iPad ゲームは元々タッチを中心に設計されました。タップ、スワイプ、ジェスチャはモバイル デバイスに最適で、仮想ジョイスティックと画面上のボタンは飛行、運転、アクション ゲームをサポートします。この問題は、MOBA、一人称視点、および PC のようなゲームプレイで発生します。プレイヤーは、WASD を使用して動きを制御し、マウスを使用して視点を正確に制御し、キーボードの修飾キーを使用してアクションを変更する必要があります。これらの操作をタッチ領域に強制すると、画面スペースが占有され、触覚フィードバックと精度が失われます。

Apple は、この入力パスを iPadOS 14 のゲームに追加しました。新しいゲーム コントローラー フレームワークGCKeyboardそしてGCMouse。キーボード イベントは、コールバックとして処理することも、ゲーム更新ループでポーリングすることもできます。マウス イベントはデルタ値を提供し、ゲームは視点、十字線、または自己描画カーソルがどのように動くかを決定します。全画面ゲームプレイを UIKit のポインター ロックと組み合わせて、システム ポインターを非表示にし、画面端のシステム ジェスチャーがゲーム プロセスに入るのを防ぐこともできます。

この変更により、移植と変更の両方がより簡単になります。すでにタッチ入力とコントローラー入力があるゲームでは、キャラクターの移動、攻撃、ジャンプ、カメラのロジックを書き直す必要はありません。開発者は、キーボード、マウス ボタン、スクロール ホイール、移動デルタを既存のメソッドに接続するだけです。デモの Fox2 は次のようになります。左クリックで攻撃、マウス移動でカメラを制御、ホイールのスクロールで視野を変更、WASD で移動、左シフトでキャラクターを走らせます。

詳細

キーボード: コールバックは個別のアクションに適しており、ポーリングは各フレームの動きに適しています。

04:42GCKeyboardエントリーポイントはキーボードを組み込むことです。複数のキーボードからの入力がマージされ、通常はゲームが開始されます。GCKeyboardDevice.coalesced?.keyboardInputキーストロークを読み取ります。講演ではまず、キーボード全体を監視するコールバック、または特定のキーのみを監視するコールバックの 2 つが示されました。

if let keyboard = GCKeyboardDevice.coalesced?.keyboardInput {

  // bind to any key-up/-down
  keyboard.keyChangedHandler = {
    (keyboard, key, keyCode, pressed) in
    // compare buttons to GCKeyCode
  }

  // bind to a specific key-up/-down
  keyboard.button(forKeyCode: .spacebar)?.valueChangedHandler = {
    (key, value, pressed) in
    // spacebar was pressed or released
  }

}

キーポイント:

  • GCKeyboardDevice.coalesced?.keyboardInput特定の物理キーボードを気にしないゲームに適した、マージされたキーボード入力を返します。
  • keyChangedHandler任意のボタンの押して放すことができるため、グローバル ショートカット アクションやデバッグ入力に適しています。
  • button(forKeyCode: .spacebar)?.valueChangedHandlerスペースバーのみに注意し、ジャンプや確認などの単一キー操作に適しています。
  • pressedボタンが現在押されているかどうかを示します。ゲームはイベント名から状態を推測する必要はありません。

(05:18) モバイル入力の場合、講演では更新ループで直接ポーリングすることを推奨しています。その理由は、ゲームではフレームごとにキャラクターの方向を計算する必要があり、イベントを待つよりも現在の状態を読み取る方が描画リズムに近いためです。 Apple は、これらのポーリング API は O(1)、ノンブロッキングであり、スレッドを生成せず、スレッドセーフであると明示しています。

func pollInput() {

  if let keyboard = GCKeyboardDevice.coalesced?.keyboardInput {
	  if (keyboard.button(forKeyCode: .keyW)?.isPressed ?? false) { /* move up    */ }
	  if (keyboard.button(forKeyCode: .keyA)?.isPressed ?? false) { /* move left  */ }
	  if (keyboard.button(forKeyCode: .keyS)?.isPressed ?? false) { /* move down  */ }
	  if (keyboard.button(forKeyCode: .keyD)?.isPressed ?? false) { /* move right */ }
  }

}

キーポイント:

  • pollInput()これをゲーム ループに配置し、フレームごとに現在のボタンのステータスを読み取ります。
  • .keyW.keyA.keyS.keyD一般的な WASD の動きに対応します。
  • ?.isPressed ?? falseキーが存在しない、またはキーボードが接続されていない状況を処理し、デフォルトでは移動しません。
  • このコードは「プレイヤーが行きたい場所」を決定するだけであり、実際の変位は後続のシミュレーションとレンダリング ロジックに任せることができます。

マウス: ゲームは画面座標ではなくデルタを取得します

06:05GCMouse核となるイベントは、mouseMovedHandler。返されるものはdeltaXそしてdeltaY、最後の入力以降の変更を示します。講演では、これは画面座標ではないことが強調されています。ゲームでは、十字線、カメラ、または自己描画ポインターの位置、加速度、および画面端の動作を定義する必要があるため、この API が使用されます。

if let mouse = GCMouse.currentMouse {

   mouse.mouseInput.mouseMovedHandler = {
     (mouse, deltaX, deltaY) in
     // use delta to calculate your game's cursor position or other motion
   }

}

キーポイント:

  • GCMouse.currentMouseメイン マウスを 1 つだけ必要とするゲームに適した、現在のマウス デバイスを入手します。
  • mouse.mouseInput.mouseMovedHandlerマウスが動かされたときにのみトリガーされます。
  • deltaXdeltaY一人称視点の回転やカメラ制御に適した変化量です。
  • 画面位置はシステム ポインター モデルに属し、ゲーム モードは通常それ自体で定義する必要があるため、ゲーム コントローラー層には「現在のマウス画面位置をポーリングする」メソッドがありません。

ポインター ロック: 全画面ゲームでのシステム エッジ ジェスチャーを回避する

(07:27) フルスクリーン ゲームでマウス デルタを使用すると、システム ポインターによって 2 つの問題が発生します。まず、プレイヤーは必ずしもシステム カーソルを見たいわけではありません。次に、ポインターを画面の端に移動すると、ドック、コントロール センター、またはマルチタスク ジェスチャがトリガーされる可能性があります。ソリューションはトップレベルにありますUIViewController戻るprefersPointerLocked、その後電話しますsetNeedsUpdateOfPrefersPointerLocked()ステータスを更新するようにシステムに通知します。

class ViewController: UIViewController {

    override var prefersPointerLocked: Bool {
        return true
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.setNeedsUpdateOfPrefersPointerLocked()
    }
}

キーポイント:

  • prefersPointerLockedこれは、トップレベルのビュー コントローラが「ポインタをロックしたい」と指示するためのエントリ ポイントです。
  • 戻るtrueの場合、システムはポインターを非表示にし、全画面アプリケーションでロックします。
  • setNeedsUpdateOfPrefersPointerLocked()UIKit に設定値を再読み取りするように指示するために使用されます。
  • この値は、戦闘中にポインターをロックしたり、試合後のメニューでシステム ポインターを復元したりするなど、動的に変更できます。

接続検出: マウスはデバイスによって処理でき、キーボードは通常、入力を読み取り、マージします。

(08:07) デバイスの検出は、ゲーム コントローラー フレームワークの通知モードに従います。マウスを接続すると、ゲームを保存できますGCMouseそしてイベントを登録します。キーボードを接続した後、通知オブジェクトも保存できますが、複数のキーボードを区別することができず、イベントがマージされることに注意してください。ほとんどのゲームはそれを直接使用しますGCKeyboard.coalescedもっとシンプルに。

class ViewController: UIViewController {
  var keyboard: GCKeyboard? = nil
  var mouse: GCMouse? = nil

  init() {
    let center = NotificationCenter.defaultCenter
    let main = OperationQueue.mainQueue

    center.addObserverForName(GCMouseDidConnectNotification, object: nil, queue: main) {
      (note) in
      self.mouse = note.object as? GCMouse
    }

    center.addObserverForName(GCKeyboardDidConnectNotification, object: nil, queue: main) {
      (note) in
      self.keyboard = note.object as? GCKeyboard // the same as GCKeyboard.coalesced
    }

  }
}

キーポイント:

  • GCMouseDidConnectNotificationマウスが接続されたときにトリガーされ、モバイル、ボタン、ホイール ハンドラーの登録に適しています。
  • GCKeyboardDidConnectNotificationキーボードがいつ表示されるかを知るために使用できます。接続が切断された場合は、ゲームを一時停止し、プレーヤーに入力方法を変更するよう促すこともできます。
  • note.object as? GCMouseそしてnote.object as? GCKeyboard通知オブジェクトを特定のデバイスに転送します。
  • キーボード入力はシステムによってマージされていますが、通常のパスは引き続き読み取られます。GCKeyboard.coalesced

Fox2: 新しい入力を既存のゲーム アクションに接続する

(10:56) デモプロジェクト Fox2 には、すでにキャラクターの移動、カメラ、ジャンプ、攻撃方法が含まれています。キーボードとマウスのサポートを追加するときに、コードはゲームプレイを書き換えず、1 つだけが追加されました。delta状態を確認し、マウス ハンドラーを登録し、フレームごとにキーボードをポーリングします。

var delta: CGPoint = CGPoint.zero

func registerMouse(_ mouseDevice: GCMouse) {

  if #available(iOS 14.0, OSX 10.16, *) {
    guard let mouseInput = mouseDevice.mouseInput else {
      return
    }

    // set up our mouse value change handlers

  }
}

    weak var weakController = self

    mouseInput.mouseMovedHandler = {(mouse, deltaX, deltaY) in
      guard let strongController = weakController else {
        return
      }

      strongController.delta = CGPoint(x: CGFloat(deltaX), y: CGFloat(deltaY))

    }

    mouseInput.leftButton.valueChangedHandler = {(button, value, pressed) in
      guard let strongController = weakController else {
        return
      }

      strongController.controllerAttack()

    }

    mouseInput.scroll.valueChangedHandler = {(cursor, x, y) in
      guard let strongController = weakController else {
        return
      }
      guard let camera = strongController.cameraNode.camera else {
        return
      }

      camera.fieldOfView = CGFloat.maximum(CGFloat.minimum(120,
                                           camera.fieldOfView + CGFloat(y)), 30)

      }
    }

func pollInput() {
  ...

  // Mouse
  let mouseSpeed: CGFloat = 0.02
  self.cameraDirection += simd_make_float2(-Float(self.delta.x * mouseSpeed),
                                            Float(self.delta.y * mouseSpeed))
  self.delta = CGPoint.zero

  // Keyboard
  if let keyboard = GCKeyboard.coalesced?.keyboardInput {

      if (keyboard.button(forKeyCode: .keyA)?.isPressed ?? false) { self.characterDirection.x = -1.0 }
      if (keyboard.button(forKeyCode: .keyD)?.isPressed ?? false) { self.characterDirection.x = 1.0 }
      if (keyboard.button(forKeyCode: .keyW)?.isPressed ?? false) { self.characterDirection.y = -1.0 }
      if (keyboard.button(forKeyCode: .keyS)?.isPressed ?? false) { self.characterDirection.y = 1.0 }

      self.runModifier = (keyboard.button(forKeyCode: .leftShift)?.value ?? 0.0) + 1.0

  }
}

キーポイント:

  • delta最新のマウスの動きは一時的に保存され、次のフレームのカメラの向きに使用されます。
  • mouseMovedHandlerバンドルdeltaXdeltaYに変換するCGPoint、消費のためにメインループに渡されます。
  • leftButton.valueChangedHandler既存のものを再利用するcontrollerAttack()、マウスの左ボタンが攻撃入力になります。
  • scroll.valueChangedHandler改訂camera.fieldOfView、スクロール ホイールにより、タッチ バージョンやコントローラー バージョンにはないズーム機能が実現します。
  • cameraDirection += ...マウス デルタに速度係数を乗算し、それをカメラの方向の変化に変換します。
  • self.delta = CGPoint.zero次のフレームで同じ動きが繰り返し適用されるのを避けるために、消費後に入力をクリアします。
  • WASDの変更characterDirection.leftShift改訂runModifier、一般的なキーボードとマウスの操作を形成します。

重要ポイント

  1. やるべきこと: 一人称または三人称の iPad ゲームにキーボードとマウスの視点コントロールを追加します。 実行する価値がある理由:GCMouseマウスの動きをカメラの方向にマッピングするのに適したデルタ イベントを提供します。 開始方法: 登録するmouseMovedHandler、バンドルdeltaXdeltaYフレームごとに消費されるカメラの状態を保存し、戦闘中にオンにしますprefersPointerLocked

  2. やるべきこと: WASD と Shift 移動ソリューションをアクション ゲームに追加します。 実行する価値がある理由:GCKeyboard.coalesced?.keyboardInput更新サイクルで安全にポーリングできるため、継続的な移動に適しています。 開始方法:pollInput()中をチェックする.keyW.keyA.keyS.keyDそして.leftShift、結果を文字方向と実行倍率に書き込みます。

  3. やるべきこと: 既存のタッチ ゲームに「キーボードとマウス モード」を追加します。 実行する価値がある理由: デモの Fox2 は攻撃、カメラ、キャラクターの移動方法を再利用しており、キーボードとマウスは新しい入力ソースとしてのみ機能します。 開始方法: まず既存のものをリストします。jumpattackmovecameraメソッドを選択し、マウス ボタン、スクロール ホイール、キーボード キーをそれぞれバインドします。

  4. やるべきこと: ゲーム内メニューと実際の戦闘シーンの間でポインターの動作を切り替えます。 実行する価値がある理由: 戦闘ではポインターをロックして非表示にする必要があるため、メニューはシステム ポインター タップ UI を使用するのに適しています。 開始方法: ブール状態で制御するprefersPointerLocked、戦闘に入ると戻りますtrue、メニューが開くと戻りますfalse、スイッチが切り替わるたびに呼び出されますsetNeedsUpdateOfPrefersPointerLocked()

  5. 内容: 戦略ゲームまたは MOBA にマウス選択キーとキーボード修飾キーを追加します。 価値がある理由: 講演では、このタイプのゲームは正確なマウス選択とキーボード修飾子に依存しており、タッチもゲームパッドも完全に適切ではないことが指摘されています。 開始方法: を使用しますGCMouse自己描画カーソルまたは単位選択を処理するには、次を使用します。GCKeyboard修飾キーの状態を読み取り、組み合わせた入力を選択、移動、スキルの解放にマッピングします。

関連セッション

コメント

GitHub Issues · utterances