iOS 13 支持主機手柄鏈接了!!!須要注意的是,並非手柄去支持 iOS 13,而是 Apple 終於容許自家平臺「發現」手柄了......ios
在 WWDC19 上,apple 正式對外宣稱 iOS,macOS,tvOS 三大平臺正式支持接入遵照 MFi 協議的手柄進行操做,接入方式爲「藍牙」。git
所以,咱們能夠推測出從此在 Apple 生態圈中游戲發佈在以上三個平臺時都會必定去考慮對手柄的支持。仔細觀察目前在 Apple 生態圈中的遊戲,尤爲是 iOS 上的遊戲,或者說是移動平臺上的遊戲,幾乎都逃不掉左邊一個虛擬滾輪,右邊三四個虛擬按鍵。github
能夠說,這些虛擬操做都是在模擬最初手柄對用戶已經養成的習慣,這些習慣既然是從實體手柄時代遺傳下來的,而且在 iOS 13 以前 Apple 並不容許接入其餘手柄,那如今已經容許接入的前提下,咱們爲什麼不順勢而爲呢?swift
還有一種說法,部分遊戲會故意經過虛擬按鍵位置的排布來消減玩家的遊戲體驗,以下降玩家完成某個要求的速度和質量,這就是 QWERTY 的現代玩法。app
我已經有了 Switch,想固然的再入一個 NS Pro Controller,但幸好事先作了調查,發現 NS Pro Controller 和 joy-Controller 均不支持鏈接,我的猜想老任並無爲其手柄申請 MFi 認證。框架
所以,只能回落到 PS4 仍是 Xbox 的手柄選擇上。在最終下單前,也作一番調查,發現你們基本上一窩蜂的倒向 Xbox 手柄手感比 PS4 強太多,原本我也打算確實是選擇 Xbox,後來發現 Apple 官網上的廣告宣傳是 PS4,並且 B 站上衆多 up 主也都基於 PS4 進行測評,想一想最終仍是選擇了 PS4 手柄。ide
到如今總共玩了大概三個小時不到,發現手柄和設備的鏈接很是妥當,我的認爲無延遲,但 PS4 手柄還真出現了網友所說玩久了左手大拇指會疼,由於常常會使用半個大拇指去「搓」滑桿。工具
但看到 PS4 手柄的顏值也就忍了,若是你也想買一個手柄且不是顏值黨,能夠考慮選擇 Xbox。spa
apple 的官方文檔中推薦咱們的 app/遊戲(下文統一使用 app)必定不能只支持手柄,其實也就是說,手柄只是一個提高體驗的工具,而不是一個必須品,只要 apple 不下死命令,咱們可以經過手柄體會到的遊戲就會越日後拖。code
PS4 和 Xbox 的手柄能夠理解爲是兩種風格,以下圖所示:
PS4 和 Xbox 手柄的 ABXY 分別使用不一樣的風格進行標識。但若是你有使用過這兩個手柄,必定可以知道大部分的手柄都是基於「位置等量」的,也就是說,PS4 上的 X
會被「位置等量」到 Xbox 上的 A
,兩者對於開發者來講都是同樣的功能。
這一點一樣被 Apple 所保留,但咱們在使用時 Apple 並不會給咱們識別出當前在 A
這個位置上究竟是 X
仍是 A
,Apple 只有 A
。
Apple 會自動協助咱們在某個「固定時間」內保留最終的按鍵狀態,而不是反覆發送按鍵回調。
這部分 Apple 要求得比較多,簡單來講,要麼所有統一,在 UI 上也作「位置等量」的標識,要否則就得經過鏈接設備的標識符來針對性的返回不一樣的 UI 資源。
瞭解了以上主機手柄適配的前置內容,接下來就能夠正式進入到手柄適配部分。我在適配的過程當中,完全被 Apple 的簡潔所折服了!我覺得對主機手柄各類硬件的調用過程會相似於 Photos
這種看似不該該那麼複雜的卻很是複雜的框架同樣複雜,但簡潔得使人感到驚訝。
對主機手柄接入的全部工做只須要 GameController
便可完成,並且該框架與平臺無關,也就是說,只要咱們封裝好一次對外暴露的主機手柄操做管理類,能夠徹底作到三平臺全通吃!
第一步,咱們得先註冊對手柄鏈接/取消鏈接的事件通知。
import GameController
class GameController {
init() {
NotificationCenter.default.addObserver(self, selector: .didConnect, name: .GCControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: .didConnect, name: .GCControllerDidDisconnect, object: nil)
}
}
複製代碼
在通知的回調方法中完成對事件的篩選和處理。
extension GameController {
@objc fileprivate func gameControllerDidConnect() {
for controller in GCController.controllers() {
if controller.extendedGamepad != nil {
setupControllerControls(controller: controller)
}
}
}
@objc fileprivate func gameControllerDidDisconnect() {
}
func setupControllerControls(controller: GCController) {
controller.extendedGamepad?.valueChangedHandler = {
(gamepad: GCExtendedGamepad, element: GCControllerElement) in
self.controllerInput(gamePad: gamepad, element: element)
}
}
private func controllerInput(gamePad: GCExtendedGamepad, element: GCControllerElement) {
}
}
複製代碼
在 controllerInput
中處理手柄的 valueChangedHandler
回調事件。valueChangedHandler
這個方法須要咱們在接收到手柄的鏈接通知時,傳遞給手柄控制對象一個回調方法,後續當手柄發生按鍵事件時,將經過咱們傳入的回調進行調用。
所以,咱們在 controllerInput
方法中處理手柄被按下時的各類事件值的改變處理。
extension GameController {
@objc fileprivate func gameControllerDidConnect() {
for controller in GCController.controllers() {
if controller.extendedGamepad != nil {
setupControllerControls(controller: controller)
}
}
}
@objc fileprivate func gameControllerDidDisconnect() {
}
func setupControllerControls(controller: GCController) {
controller.extendedGamepad?.valueChangedHandler = {
(gamepad: GCExtendedGamepad, element: GCControllerElement) in
self.controllerInput(gamePad: gamepad, element: element)
}
}
private func controllerInput(gamePad: GCExtendedGamepad, element: GCControllerElement) {
if (gamePad.leftThumbstick == element) {
if (gamePad.leftThumbstick.yAxis.value != 0 && !movingY && !movingX) {
movingY = true
isSelectY?(gamePad.leftThumbstick.yAxis.value > 0)
return
} else if (gamePad.leftThumbstick.yAxis.value == 0) {
movingY = false
}
if (gamePad.leftThumbstick.xAxis.value != 0 && !movingX && !movingY) {
isSelectX?(gamePad.leftThumbstick.xAxis.value > 0)
movingX = true
return
} else if (gamePad.leftThumbstick.xAxis.value == 0) {
movingX = false
}
}
if (gamePad.buttonA == element) {
if (gamePad.buttonA.value != 0) {
isTapButtonA?()
}
}
// ...
}
複製代碼
valueChangedHandler
回調只是當數值發生變化時的回調,咱們還須要手動處理例如手柄搖桿上從按住到歸位的單次計數,固然,我作這個是爲了適配個人遊戲,這部分還沒來得及完善,若是你想持續的監聽到搖桿的持續按住事件,徹底能夠把數值對外暴露,經過係數倍乘的方式來達到例如對賽車的油門控制等好玩的事情。
對外,能夠經過註冊的回調進行傳遞單次按鍵事件。
class GameController {
var movingX = false
var movingY = false
var isSelectX: ((Bool) -> ())?
var isSelectY: ((Bool) -> ())?
var isTapButtonA: (() -> ())?
//...
}
// ...
複製代碼
我確實沒想到對手柄的第一步適配工做竟然不到 80 行代碼搞定了。手柄是一個玩家的武器,同時也是一個標誌,但願你們都可以正視在本身的遊戲中支持手柄操做,提供更好的遊戲體驗。
若是你對個人第一個適配手柄的遊戲「可否關個燈」感興趣,能夠在 github 上找到這個項目,若是你想跟我一塊兒經過 Swift 進行遊戲開發,能夠在小專欄上找到《Swift 遊戲開發》進行。
github 地址:Swift 遊戲開發
小專欄地址:Swift 遊戲開發