iOS13から対応のdualshock 4のを試す

はじめに

iOS13からSonyが発売するSony Playstation 4用のコントローラDualshock 4がiOSに対応することが発表されています。

色々調べてみたのですが、新しいライブラリが提供されるのではなくiOS7以上から対応のGameControllerというフレームワークを使うことでDualshock 4からiPhone, iPadなどの操作ができるようです。

古くからあるライブラリなのですが、iOSでゲームを作るとなるとUnityで作ることが多くなってしまうためか、Objective-Cで書かれた古いソースコードしか見つかりませんでした。

せっかくなので、SwiftでGameControllerの検証をしてみました!

コントローラの通知

コントローラの通知を受け取るために以下の2つのNotificationが用意されています。

  • GCControllerDidConnect
  • GCControllerDidDisconnect

コントローラがコネクトした時とディスコネクトしている際にそれぞれのイベントが呼ばれます。

※ handleControllerDidConnectはアプリ起動後設定画面に移動しコントローラと接続した後に再度アプリを起動すると呼ばれます

  override func viewDidLoad() {
        
        NotificationCenter.default.addObserver(
            self, selector: #selector(self.handleControllerDidConnect),
            name: NSNotification.Name.GCControllerDidConnect, object: nil)
        
        NotificationCenter.default.addObserver(
            self, selector: #selector(self.handleControllerDidDisconnect),
            name: NSNotification.Name.GCControllerDidDisconnect, object: nil)
        guard let controller = GCController.controllers().first else {
            return
        }
    }
  @objc
    func handleControllerDidConnect(_ notification: Notification) {
        guard let gameController = notification.object as? GCController else {
            return
        }
        //コネクトした際の処理
    }
    
    @objc
    func handleControllerDidDisconnect(_ notification: Notification) {
        guard let gameController = notification.object as? GCController else {
            return
        }
        //ディスコネクトしたときの処理
    }

取得できるコントローラの種類

GameControllerフレームワークは以下の3種類のコントローラに対応しています。イベントの数以外は同じだと思います。

  • extendedGamepad
  • microGamepad
  • gamepad

extendedGamepad

dualshock4は3種類すべてのオブジェクトを保持していますが、extendedGamepadがボタンが一番多かったのでサンプルはextendedGamepadで判定を行っています。

ios13ではGameControllerフレームワークの更新はないのでdualshockのタッチパネルと中央のplaystationボタンのイベントは検知することができません。

microGamepad

公式に画像が用意されていませんでしたが、以下のボタンを持つコントローラが該当するようです。

2つのデジタルボタン(AおよびX)

タッチパッドとして実装された1つのアナログ方向パッド(Dパッド)

https://developer.apple.com/documentation/gamecontroller/gcmicrogamepad

gamepad

gamepadは現在非推奨になっているので今後使えなくなる可能性があります。

https://developer.apple.com/documentation/gamecontroller/gcgamepad

各種操作

操作は大きく分けて2つで、ボタン(GCControllerButtonInput)と移動用のPad(GCControllerDirectionPad)です。

ボタン

dualshock4で取得できるボタンイベントは以下の13種類です。

buttonB
buttonY
×buttonA
buttonX
OPTIONbuttonMenu
SHAREbuttonOption
L1leftShoulder
L2leftTrigger
R1rightShoulder
R2rightTrigger
Thumbstick LeftleftThumbstickButton
Thumbstick RightrightThumbstickButton
十時キーdpad

NotificationCenterに飛んできたgameControllerのそれぞれGCControllerButtonInput型のプロパティとして保持して、handlerで値を受け付けます。

○ボタンがプッシュされた際の通知を受け取る場合以下のようなコードでイベントを取得します。

        var circleButton: GCControllerButtonInput?

        if let gamepad = gameController.extendedGamepad {
            circleButton = gamepad.buttonB
        }
 
        circleButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("●")
            }
        }

L2, R2以外のボタンはON, OFFの2回イベントが飛んできます。L2, R2はON, OFFだけでなく0.0〜1.0の値で変化した分だけイベントが飛んできます。

Thumbstick & 十字キー

ThumbstickはGCControllerDirectionPad型のプロパティで保持しておきます。handler以外はボタンの時と同じです。

ThumbStick LeftleftThumbstick
Thumbstick RightrightThumbstick
十字キーdpad

十字キーでも同時押しが可能なため↑と→が同時に出力されることがあります。

左右Thumstickは小さな変化の値が何度も飛んでくるのである程度チューニングする必要がありそうです。

       var directionPad: GCControllerDirectionPad?
       
       if let gamepad = gameController.extendedGamepad {
            directionPad = gamepad.dpad
       directionPad?.valueChangedHandler = {(_ dPad: GCControllerDirectionPad, _ x: Float, _ y: Float) -> Void in
            
            if x == 0 && y == 0 {
                return
            }
            
            print("x: \(x), y: \(y)")
            
            if x == 1.0 {
                print("▶️")
            }
            if x == -1.0 {
                print("◀️")
            }
            if y == 1.0 {
                print("🔼")
            }
            if y == -1.0 {
                print("🔽")
            }
        }

コード一覧

中央のプレイステーションボタンとタッチパネルのイベント以外のコントローラ操作イベントを取得しているコードです。

iOS13でGameControllerライブラリ自体への変更はないのでタッチパネル操作は現状できないんじゃないかと思っています。

import UIKit
import GameController

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupGameController()
    }
    func setupGameController() {
        NotificationCenter.default.addObserver(
            self, selector: #selector(self.handleControllerDidConnect),
            name: NSNotification.Name.GCControllerDidConnect, object: nil)
        
        NotificationCenter.default.addObserver(
            self, selector: #selector(self.handleControllerDidDisconnect),
            name: NSNotification.Name.GCControllerDidDisconnect, object: nil)
        guard let controller = GCController.controllers().first else {
            return
        }
        registerGameController(controller)
    }
    
    @objc
    func handleControllerDidConnect(_ notification: Notification) {
        guard let gameController = notification.object as? GCController else {
            return
        }
        
        registerGameController(gameController)
    }
    
    @objc
    func handleControllerDidDisconnect(_ notification: Notification) {
        guard let gameController = notification.object as? GCController else {
            return
        }
        
        unregisterGameController()
        
        for controller: GCController in GCController.controllers() where gameController != controller {
            registerGameController(controller)
        }
        
    }
    
    func registerGameController(_ gameController: GCController) {
        
        // ▲ ▫️ ✖︎ ●
        var triangleButton: GCControllerButtonInput?
        var circleButton: GCControllerButtonInput?
        var crossButton:GCControllerButtonInput?
        var rectButton:GCControllerButtonInput?
        
        // setting
        var menuButton: GCControllerButtonInput?
        var shareButton: GCControllerButtonInput?
        
        //thumbstich
        var leftThumbstick: GCControllerDirectionPad?
        var rightThumbstick: GCControllerDirectionPad?
        var leftThumbstickButton: GCControllerButtonInput?
        var rightThumbstickButton: GCControllerButtonInput?
        
        //↑←↓→ key
        var directionPad: GCControllerDirectionPad?
        
        // L1, L2, R1, R2 button
        var l1Button: GCControllerButtonInput?
        var l2Button: GCControllerButtonInput?
        var r1Button: GCControllerButtonInput?
        var r2Button: GCControllerButtonInput?
        
        print(String(describing: gameController.vendorName))
        
        //DUALSHOCK4はextendedGamepad
        if let gamepad = gameController.extendedGamepad {
            leftThumbstick = gamepad.leftThumbstick
            rightThumbstick = gamepad.rightThumbstick
            leftThumbstickButton = gamepad.leftThumbstickButton
            rightThumbstickButton = gamepad.rightThumbstickButton
            
            rectButton = gamepad.buttonX
            triangleButton = gamepad.buttonY
            circleButton = gamepad.buttonB
            crossButton = gamepad.buttonA
            
            menuButton = gamepad.buttonMenu
            shareButton = gamepad.buttonOptions
            
            directionPad = gamepad.dpad
            
            l1Button = gamepad.leftShoulder
            l2Button = gamepad.leftTrigger
            r1Button = gamepad.rightShoulder
            r2Button = gamepad.rightTrigger

        }


        //MARK: - 🕹 Thumbstick 🕹
        
        leftThumbstick?.valueChangedHandler = {(_ dpad: GCControllerDirectionPad, _ x: Float, _ y: Float) -> Void in
            
           print("LEFT STICK x: \(x), y: \(y)")
        }
        
        rightThumbstick?.valueChangedHandler = {(_ dpad: GCControllerDirectionPad, _ x: Float, _ y: Float) -> Void in
            
            print("RIGHT STICK x: \(x), y: \(y)")
        }
        
        leftThumbstickButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("left thumbstick pressed")
            }
        }
        
        rightThumbstickButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("right thumbstick presded")
            }
        }
        
        //MARK: -  ▲ ▫️ ✖︎ ●
        
        triangleButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("▲")
            }
        }
        rectButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("■")
            }
        }
        
        crossButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("✖︎")
            }
        }
        
        circleButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("●")
            }
        }
        
        // MARK: - Setting button
        menuButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("menu tapped")
            }
        }
        
        shareButton?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("share tapped")
            }
        }
        
        // MARK: - CROSS KEY
        
        directionPad?.valueChangedHandler = {(_ dPad: GCControllerDirectionPad, _ x: Float, _ y: Float) -> Void in
            
            if x == 0 && y == 0 {
                return
            }
            
            print("x: \(x), y: \(y)")
            
            if x == 1.0 {
                print("▶️")
            }
            if x == -1.0 {
                print("◀️")
            }
            if y == 1.0 {
                print("🔼")
            }
            if y == -1.0 {
                print("🔽")
            }
        }
        
        // MARK: - L1, L2, R1, R2
        l1Button?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("L1")
            }
        }
        l2Button?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("L2")
            }
        }
        
        r1Button?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("R1")
            }
        }
        r2Button?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
            if pressed {
                print("R2")
            }
        }
    }
    
    func unregisterGameController() {
        print("disconnect")
    }
}