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は現在非推奨になっているので今後使えなくなる可能性があります。
各種操作
操作は大きく分けて2つで、ボタン(GCControllerButtonInput)と移動用のPad(GCControllerDirectionPad)です。
ボタン
dualshock4で取得できるボタンイベントは以下の13種類です。
○ | buttonB |
△ | buttonY |
× | buttonA |
□ | buttonX |
OPTION | buttonMenu |
SHARE | buttonOption |
L1 | leftShoulder |
L2 | leftTrigger |
R1 | rightShoulder |
R2 | rightTrigger |
Thumbstick Left | leftThumbstickButton |
Thumbstick Right | rightThumbstickButton |
十時キー | 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 Left | leftThumbstick |
Thumbstick Right | rightThumbstick |
十字キー | 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")
}
}