Repository navigation
Expand file tree
/
Copy pathGameControllerManager.swift
More file actions
301 lines (252 loc) · 10.9 KB
/
Copy pathGameControllerManager.swift
File metadata and controls
301 lines (252 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
//
// GameControllerManager.swift
// Controllers
//
// Created by 강동영 on 2023/05/29.
//
import GameController
public protocol GameControllerDelegate: AnyObject {
func didConnectedController()
func didDisConnectedController()
}
public protocol ControllerInputDelegate: AnyObject {
func didUpdateControllerInput(input: ControllerInput)
}
public final class GameControllerManager {
public static let shared: GameControllerManager = .init()
/// 연결/해제 이벤트 구독자. 여러 모델(Controller, Tester)이 동시에 받을 수 있도록 weak 컬렉션으로 보관한다.
private let delegates = NSHashTable<AnyObject>.weakObjects()
public weak var inputDelegate: ControllerInputDelegate?
private init() {
addObserver()
}
public func addDelegate(_ delegate: GameControllerDelegate) {
delegates.add(delegate)
}
private var allDelegates: [GameControllerDelegate] {
delegates.allObjects.compactMap { $0 as? GameControllerDelegate }
}
public var controllers: [GCController] = []
private var current: Controller?
public func getControlelrInfo() -> Controller? {
return current
}
public func getBatteryInfo() -> (level: Float, state: Int)? {
guard let current = current else { return nil }
print("current gamepad's batteryLevel = \(current.batteryLevel)")
print("current gamepad's batteryState = \(current.batteryState)")
return (current.batteryLevel, current.batteryState.rawValue)
}
public func setupControllerInputHandlers() {
guard let controller = GCController.current, let gamepad = controller.extendedGamepad else {
return
}
// 전체 입력 핸들러 설정
gamepad.valueChangedHandler = { [weak self] gamepad, element in
guard let self = self else { return }
if let input = self.mapControllerElement(element) {
self.inputDelegate?.didUpdateControllerInput(input: input)
}
}
// 개별 버튼 핸들러 설정
setupFaceButtonHandlers(gamepad)
setupShoulderButtonHandlers(gamepad)
setupTriggerHandlers(gamepad)
setupDirectionalInputHandlers(gamepad)
}
private func setupFaceButtonHandlers(_ gamepad: GCExtendedGamepad) {
// A 버튼
gamepad.buttonA.pressedChangedHandler = { [weak self] _, value, pressed in
self?.inputDelegate?.didUpdateControllerInput(input: .faceButton(button: .a, pressed: pressed))
}
// B 버튼
gamepad.buttonB.pressedChangedHandler = { [weak self] _, value, pressed in
self?.inputDelegate?.didUpdateControllerInput(input: .faceButton(button: .b, pressed: pressed))
}
// X 버튼
gamepad.buttonX.pressedChangedHandler = { [weak self] _, value, pressed in
self?.inputDelegate?.didUpdateControllerInput(input: .faceButton(button: .x, pressed: pressed))
}
// Y 버튼
gamepad.buttonY.pressedChangedHandler = { [weak self] _, value, pressed in
self?.inputDelegate?.didUpdateControllerInput(input: .faceButton(button: .y, pressed: pressed))
}
}
private func setupShoulderButtonHandlers(_ gamepad: GCExtendedGamepad) {
// L1 버튼
gamepad.leftShoulder.pressedChangedHandler = { [weak self] _, value, pressed in
self?.inputDelegate?.didUpdateControllerInput(input: .shoulder(button: .l1, pressed: pressed))
}
// R1 버튼
gamepad.rightShoulder.pressedChangedHandler = { [weak self] _, value, pressed in
self?.inputDelegate?.didUpdateControllerInput(input: .shoulder(button: .r1, pressed: pressed))
}
}
private func setupTriggerHandlers(_ gamepad: GCExtendedGamepad) {
// L2 트리거
gamepad.leftTrigger.valueChangedHandler = { [weak self] _, value, _ in
self?.inputDelegate?.didUpdateControllerInput(input: .trigger(button: .l2, value: value))
}
// R2 트리거
gamepad.rightTrigger.valueChangedHandler = { [weak self] _, value, _ in
self?.inputDelegate?.didUpdateControllerInput(input: .trigger(button: .r2, value: value))
}
}
private func setupDirectionalInputHandlers(_ gamepad: GCExtendedGamepad) {
// D-패드
gamepad.dpad.valueChangedHandler = { [weak self] _, xValue, yValue in
self?.inputDelegate?.didUpdateControllerInput(input: .dpad(x: xValue, y: yValue))
}
// 왼쪽 스틱
gamepad.leftThumbstick.valueChangedHandler = { [weak self] _, xValue, yValue in
self?.inputDelegate?.didUpdateControllerInput(input: .thumbstick(x: xValue, y: yValue, isLeft: true))
}
// 오른쪽 스틱
gamepad.rightThumbstick.valueChangedHandler = { [weak self] _, xValue, yValue in
self?.inputDelegate?.didUpdateControllerInput(input: .thumbstick(x: xValue, y: yValue, isLeft: false))
}
}
private func mapControllerElement(_ element: GCControllerElement) -> ControllerInput? {
switch element {
case let button as GCControllerButtonInput:
return mapButtonInput(button)
case let dpad as GCControllerDirectionPad:
if let gamepad = GCController.current?.extendedGamepad {
if dpad === gamepad.dpad {
return .dpad(x: dpad.xAxis.value, y: dpad.yAxis.value)
} else if dpad === gamepad.leftThumbstick {
return .thumbstick(x: dpad.xAxis.value, y: dpad.yAxis.value, isLeft: true)
} else if dpad === gamepad.rightThumbstick {
return .thumbstick(x: dpad.xAxis.value, y: dpad.yAxis.value, isLeft: false)
}
}
return nil
case let trigger as GCControllerAxisInput:
if let gamepad = GCController.current?.extendedGamepad {
if trigger === gamepad.leftTrigger {
return .trigger(button: .l2, value: trigger.value)
} else if trigger === gamepad.rightTrigger {
return .trigger(button: .r2, value: trigger.value)
}
}
return nil
default:
return nil
}
}
private func mapButtonInput(_ button: GCControllerButtonInput) -> ControllerInput? {
guard let gamepad = GCController.current?.extendedGamepad else { return nil }
if button === gamepad.buttonA {
return .faceButton(button: .a, pressed: button.isPressed)
} else if button === gamepad.buttonB {
return .faceButton(button: .b, pressed: button.isPressed)
} else if button === gamepad.buttonX {
return .faceButton(button: .x, pressed: button.isPressed)
} else if button === gamepad.buttonY {
return .faceButton(button: .y, pressed: button.isPressed)
} else if button === gamepad.leftShoulder {
return .shoulder(button: .l1, pressed: button.isPressed)
} else if button === gamepad.rightShoulder {
return .shoulder(button: .r1, pressed: button.isPressed)
} else if button === gamepad.buttonMenu {
return .auxiliary(button: .menu, pressed: button.isPressed)
} else if button === gamepad.buttonOptions {
return .auxiliary(button: .options, pressed: button.isPressed)
} else if button === gamepad.buttonHome {
return .auxiliary(button: .home, pressed: button.isPressed)
}
return nil
}
deinit {
removeObserver()
}
}
// MARK: - Private Method
extension GameControllerManager {
private func addObserver() {
NotificationCenter.default.addObserver(
self,
selector: #selector(didConnectedController),
name: .GCControllerDidConnect,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(didDisConnectedController),
name: .GCControllerDidDisconnect,
object: nil
)
}
private func removeObserver() {
NotificationCenter.default.removeObserver(
self,
name: .GCControllerDidConnect,
object: nil
)
NotificationCenter.default.removeObserver(
self,
name: .GCControllerDidDisconnect,
object: nil
)
}
@objc
private func didConnectedController() {
NSLog("Connected Game Controller !!)")
if let controller = getCurrentController() {
NSLog("Connected Controller's count: \(controller.controllerCount)")
NSLog("current Controller's vendorName: \(controller.vendorName)")
NSLog("current Controller's batteryLevel: \(controller.batteryLevel * 100)%")
NSLog("current Controller's batteryState: \(controller.batteryState), \(controller.batteryState.description)")
current = controller
setupControllerInputHandlers()
}
allDelegates.forEach { $0.didConnectedController() }
}
private func getCurrentController() -> Controller? {
guard let controller = GCController.current else {
return nil
}
let count = GCController.controllers().count
let batteryLevel = controller.battery?.batteryLevel ?? 0.0
let rawValue = (controller.battery?.batteryState ?? .unknown).rawValue
let state = BatteryState(rawValue: rawValue)
let vendorName = controller.vendorName ?? "Game Controller"
return Controller(controllerCount: count,
batteryLevel: batteryLevel,
batteryState: state ?? .unknown,
vendorName: vendorName)
}
@objc
private func didDisConnectedController() {
NSLog("Disconnected Game Controller !!)")
// 남은 컨트롤러가 없으면 nil 이 되어, 해제 후 이전 컨트롤러 정보가 남지 않는다.
current = getCurrentController()
allDelegates.forEach { $0.didDisConnectedController() }
}
}
// MARK: - User Interaction
extension GameControllerManager {}
public struct Controller {
public let controllerCount: Int
public let batteryLevel: Float
public let batteryState: BatteryState
public let vendorName: String
}
public enum BatteryState: Int, CustomStringConvertible {
case unknown = -1
case discharging = 0
case charging = 1
case full = 2
public var description: String {
switch self {
case .discharging:
"discharging"
case .charging:
"charging"
case .full:
"full"
case .unknown:
"unknown"
}
}
}