You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何持续检测游戏手柄的连接状态?

解决GameController断开通知不触发的问题

核心问题分析

GCControllerDidDisconnect通知不触发的核心原因,几乎都是未持有已连接GCController实例的强引用。系统仅会在控制器实例被释放前发送断开通知,若代码中没有保留引用,控制器对象会被ARC提前回收,导致断开事件无法正常触发。另外你提到的初始状态显示"CONNECTED",大概率是误判了连接状态,或是启动时存在残留的控制器实例。

修复后的NotificationCenter方案

1. 添加控制器引用存储

在GameViewController中新增属性,用来保存已连接的控制器,避免实例被提前回收:

private var connectedControllers: [GCController] = []

2. 修正通知处理逻辑

更新连接/断开的处理方法,确保正确管理控制器引用:

@objc func connectControllers(_ notification: Notification) {
    guard let controller = notification.object as? GCController else { return }
    // 避免重复添加同一控制器
    guard !connectedControllers.contains(controller) else {
        print("控制器已处于连接状态")
        return
    }
    connectedControllers.append(controller)
    
    // 修改SKSpriteNode颜色为连接状态
    updateSpriteColor(isConnected: true)
    
    // 绑定手柄按键监听
    setupControllerInputs(controller: controller)
    
    print("CONNECTED")
}

@objc func disconnectControllers(_ notification: Notification) {
    guard let controller = notification.object as? GCController else { return }
    if let index = connectedControllers.firstIndex(of: controller) {
        connectedControllers.remove(at: index)
    }
    
    // 修改SKSpriteNode颜色为断开状态
    updateSpriteColor(isConnected: false)
    
    stopGame()
    print("DISCONNECTED")
}

3. 调整通知注册时机

将通知注册放在viewWillAppear中,同时主动查询已连接的控制器,避免启动时遗漏已连接的手柄:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    ObserveForGameControllers()
    // 主动检查并处理已连接的控制器
    for controller in GCController.controllers() {
        connectControllers(Notification(name: .GCControllerDidConnect, object: controller))
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // 移除通知监听,防止内存泄漏
    NotificationCenter.default.removeObserver(self)
}

4. 手柄按键监听实现

添加按键响应逻辑,确保连接后能正常处理手柄操作:

private func setupControllerInputs(controller: GCController) {
    guard let gamepad = controller.extendedGamepad else { return }
    
    // 示例:监听A键按下事件
    gamepad.buttonA.pressedChangedHandler = { button, value, pressed in
        if pressed {
            // 此处添加A键按下的业务逻辑
        }
    }
    
    // 可扩展监听方向键、其他按键的逻辑...
}

private func updateSpriteColor(isConnected: Bool) {
    // 假设你有一个名为targetSprite的SKSpriteNode实例
    targetSprite.color = isConnected ? .green : .red
}

替代方案:直接使用GCController断开回调

如果NotificationCenter方案仍有问题,可以改用控制器自带的disconnectHandler回调,逻辑更直接:

func setupControllerDiscovery() {
    // 启动无线手柄搜索
    GCController.startWirelessControllerDiscovery {
        // 发现新手柄的回调(可选)
    }
    
    // 处理已连接的手柄
    for controller in GCController.controllers() {
        bindController(controller)
    }
    
    // 监听新手柄连接通知
    NotificationCenter.default.addObserver(self, selector: #selector(handleControllerConnected(_:)), name: .GCControllerDidConnect, object: nil)
}

@objc private func handleControllerConnected(_ notification: Notification) {
    guard let controller = notification.object as? GCController else { return }
    bindController(controller)
}

private func bindController(_ controller: GCController) {
    updateSpriteColor(isConnected: true)
    setupControllerInputs(controller: controller)
    
    // 设置断开回调
    controller.disconnectHandler = { [weak self] in
        guard let self = self else { return }
        self.updateSpriteColor(isConnected: false)
        self.stopGame()
        print("DISCONNECTED via handler")
    }
}

额外排查点

  • 手柄断开方式:确保用正确操作断开手柄(比如长按Home键选择断开、关闭手柄电源),部分休眠操作不会触发断开事件。
  • 模拟器测试:在Xcode模拟器中通过Hardware > Game Controllers菜单操作虚拟手柄,验证事件触发逻辑。
  • 内存检查:用Xcode的Memory Graph Debugger排查是否有控制器实例被意外保留,导致断开通知无法发送。

内容的提问来源于stack exchange,提问作者anon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 09:25:31