WWDC Quick Look 💓 By SwiftGGTeam
Advancements in Game Controllers

Advancements in Game Controllers

元の動画を見る

ハイライト

Apple は、GCPhysicalInputProfile、コントローラー コア ハプティクス、電流コントローラー、モーション センサー、ライト、バッテリー、入力リマッピング、および SF シンボル ボタン アイコンをゲーム コントローラー フレームワークに追加し、ゲームが Xbox Elite、Xbox アダプティブ コントローラー、デュアルショック 4 の差別化されたハードウェア機能に適応できるようにしました。

主要内容

コントローラーをサポートするゲームを作成する場合、これまで最も面倒な部分は A/B/X/Y ではないことがよくありました。標準ボタンは抽象化できますが、Xbox Elite パドル ボタン、DualShock 4 タッチパッド、コントローラー振動モーター、ジャイロスコープ、ライト、バッテリー ステータスなど、各コントローラーの追加機能に対処するのは困難です。ゲームが標準レイアウトのみをハードコードしている場合、これらのハードウェアの利点は使用されなくなります。

WWDC 2020 での変更は、これらの違いをゲーム コントローラー フレームワークに戻すことです。それぞれGCController全てphysicalInputProfile、ゲームは実行時にコントローラーの実際の入力をクエリできます。 Core Haptics は、サポートされているコントローラーの振動を駆動することもできます。 iOS 14 および tvOS 14 のプレーヤーによって行われたグローバルまたは単一アプリのボタンの再マッピングは、ゲーム UI の SF シンボル ボタン アイコンにも反映されます。

この設計により、2 つの問題点が直接解決されます。開発者は、コントローラーの種類ごとに一連のブランチを作成する必要も、すべてのボタン マップ マテリアルを自分で管理する必要もありません。ゲームはまず標準のコントロールをサポートし、その後機能強化としてボタン、トラックパッド、モーション、ライト、振動を追加します。これらのハードウェアを持たないコントローラーでもプレイすることができ、これらのハードウェアを備えたプレーヤーはより完全なエクスペリエンスを得ることができます。

詳細

拡張可能な入力: 実行時にコントローラーの実際のボタンを読み取ります。

02:53GCPhysicalInputProfileボタン、トリガー、D-パッド、サムスティックなど、コントローラー上のすべての物理入力を表します。GCExtendedGamepadそしてGCMicroGamepadすべてがそのサブカテゴリになります。ゲームはまだ利用可能ですGCExtendedGamepad標準レイアウトを決定し、物理入力プロファイルを通じて非標準入力を読み取ります。

// Extensible input API example

var attackComboBtn: GCControllerButtonInput?
var mapBtn: GCControllerButtonInput?
var mappedButtons = Set<GCControllerButtonInput>()
var unmappedButtons = Set<GCControllerButtonInput>()

func setupConnectedController(_ controller: GCController) {
    let input = controller.physicalInputProfile

    // Set up standard button mapping
    setupBasicControls(input)

    // Map a shortcut to the player's special combo attack
    attackComboBtn = input.buttons["Paddle 1"]
    if (attackComboBtn != nil) { mappedButtons.insert(attackComboBtn!) }

    // Map a shortcut to the in-game map
    mapBtn = input.buttons[GCInputDualShockTouchpadButton]
    if (mapBtn != nil) { mappedButtons.insert(mapBtn!) }

    // Find buttons that havent' been mapped to any actions yet
    unmappedButtons = input.allButtons.filter { !mappedButtons.contains($0) }
}

キーポイント:

  • controller.physicalInputProfileこれはすべてのコントローラーが持つ入り口であり、現在のハードウェアによって提供される入力をクエリするために使用されます。
  • setupBasicControls(input)通常のハンドルを完全に制御するには、最初に標準ボタンのマッピングを完了します。
  • input.buttons["Paddle 1"]Xbox Elite ワイヤレス コントローラー シリーズ 2 のパドル ボタンを読み取ります。存在しない場合に返すnil
  • input.buttons[GCInputDualShockTouchpadButton]DualShock 4 のタッチパッド ボタンを読み取ります。
  • input.allButtons.filterバインドされたアクションがなく、ゲーム内のボタン設定インターフェイスで使用できるボタンを見つけます。

コントローラーの振動: コア ハプティクスを使用したスケーラブルなフィードバックの再生

(08:45) ゲーム コントローラー Core Haptics と組み合わせると、サポートされているコントローラー用にゲームを作成できますCHHapticEngine。局所性により、ハンドルやインパルストリガーなどの振動ターゲットの位置が決まります。

private func createAndStartHapticEngine() {
    // Create and configure a haptic engine for the active controller

    guard let controller = activeController else { return }

    hapticEngine = controller.haptics?.createEngine(withLocality: .handles)

    guard let engine = hapticEngine else {
        print("Active controller does not support handle haptics")
        return
    }

キーポイント:

  • activeController現在振動を出力しているコントローラーです。
  • controller.haptics?.createEngine(withLocality: .handles)ハンドル グリップの触覚エンジンを作成します。
  • guard let engine = hapticEngine触覚の局所性に対応するハンドルの処理はサポートされていません。
  • 実際のプロジェクトでは、エンジンを起動し、起動失敗や外部停止の状況に対処する必要もあります。

(09:05) この例では、プレーヤーがダメージを受けると、ダメージの強度に基づいてパターン プレーヤーを作成し、すぐに再生します。

// Play haptics whenever the player is damaged

private func playerWasDamaged(damage: Float) {
    do {
        // Calculate the magnitude of damage as percentage of range [0, maxPossibleDamage]
        let damageMagnitude: Float = ...

        // Create a haptic pattern player for the player being hit by an enemy
        let hapticPlayer = try patternPlayerForPlayerDamage(damageMagnitude)

        // Start player, "fire and forget".
        try hapticPlayer?.start(atTime: CHHapticTimeImmediate)
    } catch let error {
        print("Haptic Playback Error: \(error)")
    }
}

キーポイント:

  • damageMagnitudeこのダメージを0から1の範囲の強さに変換します。
  • patternPlayerForPlayerDamage再生する前に、対応する強度の触覚パターンを作成します。
  • CHHapticTimeImmediateできるだけ早く演奏することを示し、ヒットなどの低レイテンシーのフィードバックに適しています。
  • 関数は保存されませんhapticPlayerパターンは最後まで自動的に再生されるためです。

(09:49) パターン自体は、連続イベントと一時イベントで構成されます。連続イベントは基本的な衝撃を提供し、2 つの過渡イベントは損傷強度を衝撃として表します。

// Create a haptic pattern that scales to the damage dealt to the player

private func patternPlayerForPlayerDamage(_ damage: Float) throws -> CHHapticPatternPlayer? {
    let continuousEvent = CHHapticEvent(eventType: .hapticContinuous, parameters: [
        CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
        CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3),
    ], relativeTime: 0, duration: 0.6)

    let firstTransientEvent = CHHapticEvent(eventType: .hapticTransient, parameters: [
        CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
        CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9 * damage),
    ], relativeTime: 0.2)

    let secondTransientEvent = CHHapticEvent(eventType: .hapticTransient, parameters: [
        CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
        CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9 * damage),
    ], relativeTime: 0.4)

    let pattern = try CHHapticPattern(events: [continuousEvent, firstTransientEvent,
                                               secondTransientEvent], parameters: [])

    return try engine.makePlayer(with: pattern)
}

キーポイント:

  • .hapticContinuous0.6 秒間続く基本フィードバックを作成します。
  • .hapticTransient持続時間を設定せずに短いインパクトを与えます。
  • 0.9 * damageヒット強度を一時的なイベント強度にマッピングします。
  • CHHapticPattern(events:parameters:)複数のイベントを 1 つのヒット フィードバックに結合します。
  • engine.makePlayer(with:)現在のコントローラーの触覚エンジンを使用してプレーヤーを作成します。

古いランブル ループを動的パラメータに移行する

(12:28) 多くのゲーム エンジンは元々、モーターの強度をフレームごとに直接設定しています。 Core Haptics に移行すると、長時間実行される連続パターンを作成し、フレームごとに送信できます。CHHapticDynamicParameter強度を変更します。

// Update the state of the connected controller's haptics every frame

private func update() {
    updateHaptics()
}

private func updateHaptics() {
    // Update the controller's haptics by sending a dynamic intensity parameter each frame
    do {
        // Create dynamic parameter for the intensity.
        let intensityParam = CHHapticDynamicParameter(parameterID: .hapticIntensityControl,
                                                        value: hapticEngineMotorIntensity,
                                                        relativeTime: 0)

        // Send parameter to the pattern player.
        try hapticsUpdateLoopPatternPlayer?.sendParameters([intensityParam],
                                 atTime: CHHapticTimeImmediate)
    } catch let error {
        print("Dynamic Parameter Error: \(error)")
    }
}

キーポイント:

  • update()メイン ゲーム ループ内の各フレームの更新を表します。
  • .hapticIntensityControl再生中のパターンの全体的な強度を制御します。
  • hapticEngineMotorIntensity現在のフレームに対してエンジンによって計算された 0 ~ 1 の強度です。
  • sendParameters(_:atTime:)新しい動的パラメータを既存のパターン プレーヤーに送信します。
  • 複数のモーターを個別に制御したい場合は、ターゲットごとにこのプロセスを繰り返す必要があります。

現在のコントローラー、動き、ライト、ボタンのアイコン

(14:11) シングルプレイヤー ゲームでは、Siri Remote がゲームパッドとペアになっていることがよくあります。GCController.current最近使用したコントローラーを返します。また、2 つの通知により、現在の入力デバイスに基づいて UI を変更できます。

NSNotification.Name.GCControllerDidBecomeCurrent
NSNotification.Name.GCControllerDidStopBeingCurrentNotification

キーポイント:

  • GCController.current現在の入力ソースを決定するシングルプレイヤー ゲームに適しています。
  • GCControllerDidBecomeCurrentハンドルが現在のハンドルになることを示します。
  • GCControllerDidStopBeingCurrentNotificationハンドルが現在のハンドルではなくなったことを示します。
  • UI は、これらの通知のプロンプト アイコン、チュートリアル テキスト、およびコントロール ステータスを切り替えることができます。

(15:25) DualShock 4 のモーションセンサーが通過GCMotion露出。電力を節約するために、一部のコントローラーではアプリでセンサーを手動で有効にする必要があります。

if motion.sensorsRequireManualActivation {
    motion.sensorsActive = true
}

キーポイント:

  • sensorsRequireManualActivationセンサー スイッチを手動で管理する必要があるかどうかを示します。
  • sensorsActive = trueモーションセンサーを有効にします。
  • 動作を必要としないインターフェイスでは、電力の消費を避けるために、センサーを時間内にオフにする必要があります。

(15:44) コントローラーは重力をユーザーの加速度から分離できない場合があります。この例では、まず機能を決定し、次に対応するデータ ソースを選択します。

if motion.hasGravityAndUserAcceleration {
    handleMotion(gravity: motion.gravity, userAccel: motion.userAcceleration)
} else {
    handleMotion(totalAccel: motion.acceleration)
}

キーポイント:

  • hasGravityAndUserAcceleration重力とユーザーの加速度を分割できるかどうかを示します。
  • 分割がサポートされている場合、ゲームはジェスチャーとプレーヤーのアクションを個別に処理できます。
  • 分割がサポートされていない場合に使用しますmotion.acceleration総加速度を読み取ります。

(16:27) DualShock 4 ライトバーが通過しましたGCDeviceLight露出。ゲームでは、怪我、健康状態の低下、選手番号などのゲームのステータスに色を結び付けることができます。

guard let controller = GCController.current else { return }
controller.light?.color = GCColor.init(red: 1.0, green: 0, blue: 0)

キーポイント:

  • GCController.current最近使用したコントローラーを取得します。
  • controller.lightコントローラーがライトをサポートしている場合にのみ存在します。
  • GCColorライトバーの色を設定します。

(22:36) 入力の再マッピングにより、プレイヤーに表示される UI プロンプトが変更されます。 iOS 14ではSFシンボルにゲームコントローラーのグリフが追加され、sfSymbolsName現在のコントローラと再マッピング状態に適したシンボル名を返します。

let xboxButtonY = xboxController.physicalInputProfile[GCInputButtonY]!
guard let xboxSfSymbolsName = xboxButtonY.sfSymbolsName else { return }
let xboxButtonYGlyph = UIImage(systemName: xboxSfSymbolsName)

let ds4ButtonY = ds4Controller.physicalInputProfile[GCInputButtonY]!
guard let ds4SfSymbolsName = ds4ButtonY.sfSymbolsName else { return }
let ds4ButtonYGlyph = UIImage(systemName: ds4SfSymbolsName)

キーポイント:

  • physicalInputProfile[GCInputButtonY]現在のハンドルの Y ボタン入力要素を取得します。
  • sfSymbolsName入力要素に一致する SF シンボルの名前を返します。
  • Xbox では丸で囲まれた Y が表示される場合があります。これは、DualShock 4 の三角形のグリフに対応します。
  • プレイヤーが Y を X に再マップすると、ゲームが Y ボタンを参照するときに、UI には再マップされたグリフが反映されます。

重要ポイント

  • 何をすべきか: エリート パドル ショートカット キーをアクション ゲームに追加します。 実行する価値がある理由:GCPhysicalInputProfile実行時にクエリ可能Paddle 1、パドルのないハンドルは依然として標準のボタンを使用します。 開始方法: 最初に使用しますsetupBasicControls(input)基本コントロールをバインドしてから、input.buttons["Paddle 1"]回避、リロード、またはコンボを行うためのマップ。

  • やるべきこと: プレイヤーの打撃、走行面、爆発距離をレイヤー状のコントローラー振動にします。 価値がある理由: Core Haptics は、コントローラー上で連続イベントと一時イベントを再生し、ダメージや距離によって強度を調整できます。 始め方: 現在のところGCControllerハプティックエンジンを作成して使用するCHHapticPatternベーシックショックとショートショックの組み合わせ。

  • 内容: 射手に向けて調整された DualShock 4 ジャイロスコープの照準を追加します。 実行する価値がある理由: セッションでは、きめ細かいカメラの照準にはジャイロスコープを使用し、粗い動きにはサムスティックを使用することが明確に述べられています。 開始方法: 確認してくださいmotion.sensorsRequireManualActivation、センサーを有効にした後、重力/ユーザー加速度または合計加速度を読み取ります。

  • 内容: チュートリアルと HUD に、プレーヤーの現在のコントローラーのボタン アイコンが自動的に表示されるようにします。 実行する価値がある理由:sfSymbolsNameMFi、Xbox、DualShock 4、および入力の再マッピングに基づいて、正しいグリフが返されます。 開始方法:からphysicalInputProfile 取出输入元素,拿到 sfSymbolsName後で使用するためにUIImage(systemName:)与える。

  • 内容: Apple TV のシングルプレイヤー ゲームで最後に使用したコントローラーを自動的に追跡します。 価値がある理由: プレイヤーは Siri リモコンとゲームパッドの両方を同時に接続している可能性があります。GCController.current自作の入力調停ロジックを削減できます。 開始方法: モニタリングGCControllerDidBecomeCurrentそしてGCControllerDidStopBeingCurrentNotification、UI を更新し、通知にステータスを入力します。

関連セッション

コメント

GitHub Issues · utterances