Swift 多人调用多次呈现游戏

Swift Multiplayer Calls Present Game Multiple Times

我正在根据《九骑士》的 Ray Wenderlich 教程编写几个 Swift 多人游戏。 (https://www.raywenderlich.com/7544-game-center-for-ios-building-a-turn-based-game)

我使用几乎相同的 GameCenterHelper 文件,除了我使用 UIKit 而不是 Sprite Kit 以及以下重要部分:

现在的媒人:

func presentMatchmaker() {
guard GKLocalPlayer.local.isAuthenticated else {return}

let request = GKMatchRequest()

request.minPlayers = 2
request.maxPlayers = 2
request.inviteMessage = "Would you like to play?"

let vc = GKTurnBasedMatchmakerViewController(matchRequest: request)
vc.turnBasedMatchmakerDelegate = self

currentMatchmakerVC = vc
print(vc)
viewController?.present(vc, animated: true)
}

播放器监听函数:

extension GameCenterHelper: GKLocalPlayerListener {
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool) {
if let vc = currentMatchmakerVC {
  currentMatchmakerVC = nil
  vc.dismiss(animated: true)
}

guard didBecomeActive else {return}

NotificationCenter.default.post(name: .presentGame, object: match)
}
}

通知中心的以下扩展:

extension Notification.Name {
static let presentGame = Notification.Name(rawValue: "presentGame")
static let authenticationChanged = Notification.Name(rawValue: "authenticationChanged")
}

在菜单的 viewdidload 中我调用如下:

override func viewDidLoad() {
    super.viewDidLoad()
    createTitleLabel()
    createGameImage()
    createButtons()
    
    GameCenterHelper.helper.viewController = self
    GameCenterHelper.helper.currentMatch = nil
    
    NotificationCenter.default.addObserver(self, selector: #selector(authenticationChanged(_:)), name: .authenticationChanged, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(presentGame(_:)), name: .presentGame, object: nil)
}

点击多设备按钮调用以下内容:

@objc func startMultiDeviceGame() {
    multiPlayer = true
    GameCenterHelper.helper.presentMatchmaker()
}

通知调用以下内容:

@objc func presentGame(_ notification: Notification) {
  // 1
    print("present game")
  guard let match = notification.object as? GKTurnBasedMatch else {return}
  
  loadAndDisplay(match: match)
}

// MARK: - Helpers
private func loadAndDisplay(match: GKTurnBasedMatch) {
    match.loadMatchData { [self] data, error in
    if let data = data {
      do {
        gameModel = try JSONDecoder().decode(GameModel.self, from: data)
      } catch {gameModel = GameModel()}
    } else {gameModel = GameModel()}
    
    GameCenterHelper.helper.currentMatch = match
        print("load and display")
    performSegue(withIdentifier: "gameSegue", sender: nil)
  }
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    print("prepare to segue")
    if let vc = segue.destination as? GameVC {vc.gameModel = gameModel}
}

希望这很容易理解。

  1. 游戏开始菜单场景添加当前游戏观察者
  2. 玩家点击多设备,出现红娘
  3. 玩家select从媒人那里玩游戏,我认为这激活了玩家监听器功能
  4. 这会发布到当前游戏的通知中心
  5. 在prepare segue的帮助下,通知中心观察者调用当前游戏,调用加载和显示

我的问题是,我第一次这样做时效果很好,并且根据该教程中的框架,我无法弄清楚如何在玩家接受后更改(我认为这是另一个问题的问题)轮到他们返回菜单。他们第二次进入当​​前媒人和 select 游戏时,当前游戏功能被调用两次,第三次轮到他们时没有关闭应用程序,它被调用了 3 次,等等。(我有打印语句在当前的游戏以及加载和显示功能中,它们在第 2 次和第 3 次被连续调用,等等。即使它们只在第一次游戏 select 时被调用来自媒人)

控制台消息

present matchmaker true
<GKTurnBasedMatchmakerViewController: 0x104810000>
present game
present game
present game
load and display
prepare to segue
load and display
prepare to segue
load and display
prepare to segue
2021-03-20 22:32:26.838680-0600 STAX[4997:435032] [Presentation] Attempt to present <STAX.GameVC: 0x103894c00> on <Game.MenuVC: 0x103814800> (from < Game.MenuVC: 0x103814800>) whose view is not in the window hierarchy.
(419.60100000000006, 39.0)
2021-03-20 22:32:26.877943-0600 STAX[4997:435032] [Presentation] Attempt to present <STAX.GameVC: 0x103898e00> on < Game.MenuVC: 0x10501c800> (from < Game.MenuVC: 0x10501c800>) whose view is not in the window hierarchy.

我原以为这是因为我没有删除通知中心观察器,但我在菜单屏幕加载的视图中尝试了以下操作(就在我添加 .presentGame 观察器之前):

NotificationCenter.default.removeObserver(self, name: .presentGame, object: nil)

这并没有解决问题,所以我尝试了以下方法(代替上面的方法):

NotificationCenter.default.removeObserver(self)

那没有用,所以我分别尝试了它们,一次一个在视图中确实消失了游戏视图控制器(我认为这不会起作用,因为 self 指的是菜单 vc, 但我越来越绝望了) 也没有用。

我开始考虑也许我没有添加多个多次调用当前游戏的观察者,因为以下第二次完全不起作用(我只是使用全局变量来跟踪第一次 运行 添加观察者,然后第二次不添加它们):

if addObservers {
    NotificationCenter.default.addObserver(self, selector: #selector(authenticationChanged(_:)), name: .authenticationChanged, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(presentGame(_:)), name: .presentGame, object: nil)
        addObservers = false
    }

因为它正在尝试添加不在视图层次结构中的视图。 (虽然该画面的背景音乐开始播放,但菜单仍然存在,游戏板未显示...)

我不确定我是否错误地删除了通知中心观察器,或者它们是否真的不是问题的根源,所以我决定寻求帮助:)

谢谢!

我明白了。我试图根据以下 link(最底部的答案)从视图控制器的已释放实例中删除通知:

How to avoid adding multiple NSNotification observer?

删除通知的正确方法是在视图中删除通知,如下所示:

override func viewWillDisappear(_ animated: Bool) {
        NotificationCenter.default.removeObserver(self, name: Notification.Name.presentGame, object: nil)
        NotificationCenter.default.removeObserver(self, name: Notification.Name.authenticationChanged, object: nil)
    }

实施后,我停止多次调用通知中心。