GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)

版本记录

版本号 时间
V1.0 2018.12.12 星期三

前言

GameKit框架创造经验,让玩家回到你的游戏。 添加排行榜,成就,匹配,挑战等等。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. GameKit框架详细解析(一) —— 基本概览(一)

开始

首先看下写作环境

Swift 4.2, iOS 12, Xcode 10

在本教程中,您将学习使用Game Center进行身份验证以及其基于回合制(turn-based)的机制的工作原理。 最后,您将拥有如何将多人游戏与GameCenter集成的基础。

2010年9月,Apple发布了GameKit框架,允许开发人员创建社交多人游戏而无需开发后端。 自首次发布以来,Apple已经添加了回合制游戏,玩家照片,朋友推荐,成就点和挑战。 虽然Apple已经缩减了Game Center及其与iOS的集成,但许多游戏仍然使用它的一些功能。

在本教程中,您将为现有游戏添加回合制多人游戏系统。 您将了解Game Center的身份验证以及其回合制机制的工作原理。 最后,您将拥有如何将多人游戏与Game Center集成的基础。

本教程中使用的游戏是名为Nine KnightsNine Men's Morris克隆。 Nine Men's Morris是一款可追溯到罗马帝国的简单策略游戏。 游戏分为三个阶段:放置,移动和飞行。 游戏的平均长度为5-30分钟,具体取决于玩家的技能。

打开入门项目,打开Nine Knights.xcodeproj

示例项目是一个完整的本地多人游戏。 构建并运行以查看菜单场景。 它包含一个play按钮。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第1张图片

您将使用的游戏的主要部分是Models / GameModel.swift和场景组中的场景Scenes。 游戏模型代表游戏的状态并确定游戏玩法逻辑,例如移动验证。 菜单场景现在负责呈现游戏场景。 最后,游戏场景(game scene)是所有面对游戏玩法的玩家发生的地方。


Connecting To Game Center - 连接到游戏中心

与自己对抗很有趣,但现在是时候与其他人联系了。 在使用Game Center之前,您必须在项目和App Store Connect中配置一些内容。

首先,在Xcode中打开项目文件。 选中Nine Knights目标后,在Identity组下选择一个唯一的bundle identifie

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第2张图片

接下来,选择Capabilities选项卡。 您需要打开Game Center开关和Push Notifications开关。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第3张图片

启用Game Center功能是显而易见的,但您启用推送通知功能,以便游戏可以在玩家轮到时接收通知。 信不信由你,这就是你必须在项目中完成的所有配置。

接下来,转到appstoreconnect.apple.com。 登录后,选择My Apps,然后选择左上角的加号按钮创建一个新应用程序。 填写表格中的信息。 确保选择iOS作为平台,并选择您在Xcode中配置的Bundle ID

注意:如果您没有看到使用Xcode注册的Bundle ID,则可以在开developer portal中手动创建一个。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第4张图片

App Store Connect创建应用程序后,选择它。 然后,导航到Features选项卡并选择Game Center

选择排行榜Leaderboard部分标题旁边的加号按钮以创建新的Leaderboard

注意:此步骤可能看起来很奇怪,因为本教程不包括Game Center的排行榜。 但是,如果没有此步骤,Game Center将无法在尝试进行身份验证时识别该游戏。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第5张图片

选择Single Leaderboard选项并根据需要填写下一个表单。 由于这是让游戏中心正常工作的黑客行为,用于填写排行榜的信息并不重要。 确保添加语言,以便App Store Connect可以创建排行榜。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第6张图片

完成后,选择保存按钮,您现在应该已经配置了应用程序的排行榜。 Game Center身份验证现在可供您使用了!


Authenticating with Game Center - 使用Game Center进行身份验证

现在您已经为Game Center配置了应用程序,现在是时候使用它了。 对于Nine Knights来说,最有必要立即向Game Center进行身份验证。 在某些游戏中,等待认证直到玩家表示希望在线玩游戏可能更合适。

您将使用帮助程序类来合并Game Center的逻辑。 这就是你要添加逻辑进行身份验证的地方。

首先,打开Helpers / GameCenterHelper.swift并在CompletionBlock声明下添加以下属性:

// 1
static let helper = GameCenterHelper()

// 2
var viewController: UIViewController?

以下是它的工作原理:

  • 1) 您可以使用单例轻松地从应用程序的任何位置访问Game Center逻辑。
  • 2) 在呈现Game Center身份验证视图控制器时使用此视图控制器。

首次创建此帮助程序类时,您希望尝试Game Center身份验证。 添加以下覆盖来完成此任务:

override init() {
  super.init()
  
  GKLocalPlayer.local.authenticateHandler = { gcAuthVC, error in
    if GKLocalPlayer.local.isAuthenticated {
      print("Authenticated to Game Center!")
    } else if let vc = gcAuthVC {
      self.viewController?.present(vc, animated: true)
    }
    else {
      print("Error authentication to GameCenter: " +
        "\(error?.localizedDescription ?? "none")")
    }
  }
}

如果此API让您感到困惑,请不要担心。 通过设置变量进行身份验证等重要逻辑是不正常的。 即使这看起来很奇怪,但它的使用起来相当简单。

您可以通过评估GKLocalPlayer.local.isAuthenticated来确定用户是否在completion块中成功进行了身份验证。 如果用户需要登录Game Center,iOS会在完成块中传递视图控制器以供您呈现。 最后,如果出现任何问题,您可以处理也返回的错误。

要查看此操作,请将以下内容添加到ViewControllers / GameViewController.swiftviewDidLoad()的底部:

GameCenterHelper.helper.viewController = self

构建并运行以查看此操作。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第7张图片

使用Apple ID登录后,您将在控制台中看到熟悉的成功横幅以及消息。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第8张图片

对游戏中心进行身份验证非常简单。 它主要包括设置authenticateHandler变量,Game Center为您处理大部分逻辑。


Telling the App About Authentication - 告诉应用程序关于身份验证

现在,Game Center知道玩家是否登录,但应用程序的其余部分没有登录。 当玩家进行身份验证时,您将使用NotificationCenter提醒应用程序的其他部分。

为此,请将以下内容添加到GameCenterHelper.swift的底部:

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

这个助手使用了一些通知,这是他们要做的:

  • 1) presentGame通知菜单场景呈现在线游戏。
  • 2) authenticationChanged通知应用程序任何身份验证状态更改。

要使用身份验证通知,请将以下内容添加到authenticateHandler块的顶部:

NotificationCenter.default
  .post(name: .authenticationChanged, object: GKLocalPlayer.local.isAuthenticated)

要使用此通知,请打开Scenes / MenuScene.swift并在localButton的声明下面添加以下内容:

private var onlineButton: ButtonNode!

另外,在setUpScene(in :)方法的末尾设置onlineButton

onlineButton = ButtonNode("Online Game", size: buttonSize) {
  print("Pressed the online button.")
}
onlineButton.isEnabled = false
runningYOffset -= sceneMargin + buttonSize.height
onlineButton.position = CGPoint(x: sceneMargin, y: runningYOffset)
addChild(onlineButton)

此按钮节点的配置类似于本地按钮,但在玩家通过身份验证之前,它最初会被禁用。

构建并运行。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第9张图片

要在玩家向Game Center进行身份验证时更新此按钮的状态,您需要侦听helper类触发的通知。

为此,请将以下内容添加到MenuScene类的底部:

// MARK: - Notifications

@objc private func authenticationChanged(_ notification: Notification) {
  onlineButton.isEnabled = notification.object as? Bool ?? false
}

然后,在didMove(to :)中添加观察者:

NotificationCenter.default.addObserver(
  self,
  selector: #selector(authenticationChanged(_:)),
  name: .authenticationChanged,
  object: nil
)

构建并运行

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第10张图片

太好了,现在在线按钮显示正确的状态。

如果您离开menu scene,按钮会出现问题。 启动本地游戏然后返回菜单会导致按钮再次被禁用。

要解决此问题,请打开GameCenterHelper.swift并在GameCenterHelper顶部附近添加以下计算属性:

static var isAuthenticated: Bool {
  return GKLocalPlayer.local.isAuthenticated
}

现在,回到MenuScene.swift,并在setUpScene(in :)结束时,而不是最初禁用在线按钮,检查当前状态,如下所示:

onlineButton.isEnabled = GameCenterHelper.isAuthenticated

Finding an Online Match - 寻找在线匹配

事情开始出现了。 现在玩家已通过游戏中心认证,您需要一种方法来开始新游戏。 您将使用GKTurnBasedMatchmakerViewController完成此操作。

注意:GKTurnBasedMatchmakerViewController没有很多选项来自定义它的显示方式。 如果这对您的应用程序很重要,您可以通过编程方式管理游戏,从而创建自己的匹配。

打开GameCenterHelper.swift并创建一个新方法来处理init()下面的matchmaker视图控制器的表示:

func presentMatchmaker() {
  // 1
  guard GKLocalPlayer.local.isAuthenticated else {
    return
  }
  
  // 2
  let request = GKMatchRequest()
  
  request.minPlayers = 2
  request.maxPlayers = 2
  // 3
  request.inviteMessage = "Would you like to play Nine Knights?"
  
  // 4
  let vc = GKTurnBasedMatchmakerViewController(matchRequest: request)
  viewController?.present(vc, animated: true)
}

下面看一下细节:

  • 1) 确保玩家已通过身份验证。
  • 2) 创建匹配请求以使用匹配器视图控制器发送邀请。
  • 3) 自定义请求。 设置邀请消息以个性化邀请。 根据Nine Knights的规则配置玩家数量。
  • 4) 将请求传递给matchmaker视图控制器并显示它。

您现在可以通过展示matchmaker来使在线按钮正常工作。

打开MenuScene.swift并使用以下命令替换onlineButton节点回调中的print语句:

GameCenterHelper.helper.presentMatchmaker()

构建并运行,然后选择在线按钮。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第11张图片

最初,matchmaker视图控制器显示新的游戏视图。 如果您尝试取消和关闭视图或创建新游戏,视图控制器不会dismiss。 这是因为未设置matchmaker的代理。

要解决此问题,首先在GameCenterHelper.swift的底部添加以下扩展,以使helper类符合GKTurnBasedMatchmakerViewControllerDelegate

extension GameCenterHelper: GKTurnBasedMatchmakerViewControllerDelegate {
  func turnBasedMatchmakerViewControllerWasCancelled(
    _ viewController: GKTurnBasedMatchmakerViewController) {
      viewController.dismiss(animated: true)
  }
  
  func turnBasedMatchmakerViewController(
    _ viewController: GKTurnBasedMatchmakerViewController, 
    didFailWithError error: Error) {
      print("Matchmaker vc did fail with error: \(error.localizedDescription).")
  }
}

然后,在呈现视图控制器之前,添加此项以在presentMatchmaker()中设置代理:

vc.turnBasedMatchmakerDelegate = self

您会注意到matchmaker代理只包含两种方法。 还有其他方法管理查找和退出匹配,但他们已被弃用。 您实现的两种方法记录发生的任何错误并dismiss matchmaker

构建并运行。

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第12张图片

很好!您现在可以使用Game Center创建新的匹配项。现在它有点麻烦。创建匹配后,您必须退出视图,关闭matchmaker,然后再打开matchmaker。当您实现turn events的处理时,您将解决此问题。


Under the Hood of Game Center

游戏现在处于一种状态,您可以跟踪身份验证状态更改并显示Game Centermatchmaker来管理游戏。完成在线体验的最后一件事是处理游戏的turns

Game Center通过存储玩家,当前玩家和匹配数据来处理回合制游戏。它存储了许多其他东西,但它们并不重要。为玩家更新游戏包含三件事:

  • 1) 阅读当前游戏状态并更新UI。
  • 2) 处理玩家输入,更新游戏模型以反映该输入并将模型转换为您保存到Game Center的Data
  • 3) 计算下一个玩家并发送turn

所有这些游戏数据都由GKTurnBasedMatch表示。目前,您可以使用匹配器创建匹配项,但仍需要一种方法来接收匹配项。


Taking Nine Knights Online

要在玩家登录时立即开始接收turn事件,您需要注册本地玩家。

在文件末尾添加以下扩展名:

extension GameCenterHelper: GKLocalPlayerListener {
  func player(_ player: GKPlayer, wantsToQuitMatch match: GKTurnBasedMatch) {
    // 1
    let activeOthers = match.others.filter { other in
      return other.status == .active
    }
    
    // 2
    match.currentParticipant?.matchOutcome = .lost
    activeOthers.forEach { participant in
      participant.matchOutcome = .won
    }
    
    // 3
    match.endMatchInTurn(
      withMatch: match.matchData ?? Data()
    )
  }
  
  func player(
    _ player: GKPlayer, 
    receivedTurnEventFor match: GKTurnBasedMatch, 
    didBecomeActive: Bool
  ) {
    // 4
  guard didBecomeActive else {
    return
  }
  
  NotificationCenter.default.post(name: .presentGame, object: match)
  }
}

仔细阅读代码:

  • 1) 当玩家退出Nine Knights时,你需要在游戏中获得另一个活跃的玩家。
  • 2) 将退出玩家的结果设置为lost,并将其他活跃玩家的结果设置为win。 对于这个游戏,应该只有一个其他玩家。
  • 3) 最后,结束与更新数据的匹配。
  • 4) 当应用程序收到turn事件时,该事件包括一个Bool,指示turn是否应该出现在游戏中。 您可以使用之前定义的通知之一来通知菜单场景。

要使用此协议实现,请将init()中的身份验证块中的print语句更改为:

GKLocalPlayer.local.register(self)

如果您尝试运行此选项,您仍会发现选择游戏时匹配器不会被忽略。 您可以通过跟踪匹配器视图控制器并在turn事件方法中根据需要dismiss匹配来解决此问题。

将以下属性添加到GameCenterHelper的顶部:

var currentMatchmakerVC: GKTurnBasedMatchmakerViewController?

接下来,在presentMatchmaker()中显示视图之前分配它:

currentMatchmakerVC = vc

最后,将以下内容添加到player(_:receivedTurnEventFor:didBecomeActive:)的顶部:

if let vc = currentMatchmakerVC {
  currentMatchmakerVC = nil
  vc.dismiss(animated: true)
}

您现在会在选择游戏时看到matchmaker dismiss

接下来,最重要的事情之一是显示游戏。 打开MenuScene.swift并将以下内容添加到类的底部:

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

// MARK: - Helpers

private func loadAndDisplay(match: GKTurnBasedMatch) {
  // 2
  match.loadMatchData { data, error in
    let model: GameModel
    
    if let data = data {
      do {
        // 3
        model = try JSONDecoder().decode(GameModel.self, from: data)
      } catch {
        model = GameModel()
      }
    } else {
      model = GameModel()
    }
    
    // 4
    self.view?.presentScene(GameScene(model: model), transition: self.transition)
  }
}

下面进行详细分析:

  • 1) 确保通知中的对象是您期望的匹配对象。
  • 2) 从Game Center请求匹配数据以确保您拥有最新信息,从Game Center请求匹配数据。
  • 3) 根据匹配数据构建游戏模型。 Nine Knights的游戏模型符合Codable,因此序列化是轻而易举的。
  • 4) 使用Game Center中的模型呈现游戏场景。

对于最后一步,您必须将其连接起来以收听呈现游戏的通知。

didMove(to :)中添加以下另一个通知观察者:

NotificationCenter.default.addObserver(
  self,
  selector: #selector(presentGame(_:)),
  name: .presentGame,
  object: nil
)

构建并运行

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第13张图片

很好! 现在你可以看到游戏了。 除此之外,您会注意到您可以像在本地模式中一样玩整个游戏。 为了防止这种情况,您需要向helper类添加更多内容。

打开GameCenterHelper.swift并在帮助程序类的顶部添加以下内容:

var currentMatch: GKTurnBasedMatch?

var canTakeTurnForCurrentMatch: Bool {
  guard let match = currentMatch else {
    return true
  }
  
  return match.isLocalPlayersTurn
}

enum GameCenterHelperError: Error {
  case matchNotFound
}

currentMatch跟踪玩家当前正在查看的游戏。 要确定玩家是否可以放置一块,您可以使用当前的匹配并检查它是否是本地玩家的回合。

接下来,在类的末尾添加以下辅助方法:

func endTurn(_ model: GameModel, completion: @escaping CompletionBlock) {
  // 1
  guard let match = currentMatch else {
    completion(GameCenterHelperError.matchNotFound)
    return
  }
  
  do {
    match.message = model.messageToDisplay
    
    // 2
    match.endTurn(
      withNextParticipants: match.others,
      turnTimeout: GKExchangeTimeoutDefault,
      match: try JSONEncoder().encode(model),
      completionHandler: completion
    )
  } catch {
    completion(error)
  }
}

func win(completion: @escaping CompletionBlock) {
  guard let match = currentMatch else {
    completion(GameCenterHelperError.matchNotFound)
    return
  }
  
  // 3
  match.currentParticipant?.matchOutcome = .won
  match.others.forEach { other in
    other.matchOutcome = .lost
  }
  
  match.endMatchInTurn(
    withMatch: match.matchData ?? Data(),
    completionHandler: completion
  )
}

以下是重要方法的作用:

  • 1) 确保当前匹配集。
  • 2) 在序列化更新的游戏模型并修改匹配后结束turn。 能够使用Codable协议来处理序列化大大简化了这种逻辑。
  • 3) 以类似于玩家退出时的方式处理胜利。 更新玩家的结果会更新并结束游戏。

在显示游戏场景之前,打开MenuScene.swift并将以下内容添加到loadAndDisplay(match :)的底部:

GameCenterHelper.helper.currentMatch = match

didMove(to:)中,在feedbackGenerator.prepare()之后,添加:

GameCenterHelper.helper.currentMatch = nil

现在您可以可靠地管理当前匹配,您可以准确计算出该轮到何时进行比赛。

打开Scenes / GameScene.swift并将此guard语句添加到handleTouch(_ :)的顶部:

guard !isSendingTurn && GameCenterHelper.helper.canTakeTurnForCurrentMatch else {
    return
}

这个声明阻止了玩家在没有轮到他们的时候以及他们将turn更新发送到Game Center时的输入。

最后,要开始发送玩家的动作,请将以下内容添加到feedbackGenerator.prepare()下面的else里面的processGameUpdate()底部:

isSendingTurn = true

if model.winner != nil {
  GameCenterHelper.helper.win { error in
    defer {
      self.isSendingTurn = false
    }
    
    if let e = error {
      print("Error winning match: \(e.localizedDescription)")
      return
    }
    
    self.returnToMenu()
  }
} else {
  GameCenterHelper.helper.endTurn(model) { error in
    defer {
      self.isSendingTurn = false
    }

    if let e = error {
      print("Error ending turn: \(e.localizedDescription)")
      return
    }
    
    self.returnToMenu()
  }
}

该逻辑检查是否有游戏的赢家。 如果有,它结束游戏。 否则,turn结束。 它还会更新isSendingTurn,因此玩家不会意外地或恶意地进行多次turn

构建并运行,享受游戏:

恭喜你,你现在可以和你的朋友一起玩Nine Knights

您现在有一款游戏可以向游戏中心进行身份验证,管理比赛并在玩家之间来回转发。 一旦您了解了使用Game Center的回合制游戏,您就可以将这些概念应用到其他iOS或macOS游戏中。

如果您有兴趣为此游戏添加更多功能,可以添加一种方法在应用中指示有新的turn。 您会收到通知,告知您有新的turn,但在应用中,游戏中心通知不会显示。

您还可以查看有关Apple Documentation about GameKit的官方Apple文档,了解它还提供了什么。

后记

本篇主要讲述了iOS的Game Center:构建基于回合制的游戏,感兴趣的给个赞或者关注~~~

GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一)_第14张图片

你可能感兴趣的:(GameKit框架详细解析(二) —— iOS的Game Center:构建基于回合制的游戏(一))