版本记录
版本号 | 时间 |
---|---|
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 Knights
的Nine Men's Morris
克隆。 Nine Men's Morris
是一款可追溯到罗马帝国的简单策略游戏。 游戏分为三个阶段:放置,移动和飞行。 游戏的平均长度为5-30分钟,具体取决于玩家的技能。
打开入门项目,打开Nine Knights.xcodeproj
。
示例项目是一个完整的本地多人游戏。 构建并运行以查看菜单场景。 它包含一个play
按钮。
您将使用的游戏的主要部分是Models / GameModel.swift
和场景组中的场景Scenes
。 游戏模型代表游戏的状态并确定游戏玩法逻辑,例如移动验证。 菜单场景现在负责呈现游戏场景。 最后,游戏场景(game scene)
是所有面对游戏玩法的玩家发生的地方。
Connecting To Game Center - 连接到游戏中心
与自己对抗很有趣,但现在是时候与其他人联系了。 在使用Game Center
之前,您必须在项目和App Store Connect
中配置一些内容。
首先,在Xcode中打开项目文件。 选中Nine Knights
目标后,在Identity
组下选择一个唯一的bundle identifie
。
接下来,选择Capabilities
选项卡。 您需要打开Game Center
开关和Push Notifications
开关。
启用Game Center
功能是显而易见的,但您启用推送通知功能,以便游戏可以在玩家轮到时接收通知。 信不信由你,这就是你必须在项目中完成的所有配置。
接下来,转到appstoreconnect.apple.com。 登录后,选择My Apps
,然后选择左上角的加号按钮创建一个新应用程序。 填写表格中的信息。 确保选择iOS作为平台,并选择您在Xcode中配置的Bundle ID
。
注意:如果您没有看到使用Xcode注册的
Bundle ID
,则可以在开developer portal中手动创建一个。
在App Store Connect
创建应用程序后,选择它。 然后,导航到Features
选项卡并选择Game Center
。
选择排行榜Leaderboard
部分标题旁边的加号按钮以创建新的Leaderboard
。
注意:此步骤可能看起来很奇怪,因为本教程不包括Game Center的排行榜。 但是,如果没有此步骤,Game Center将无法在尝试进行身份验证时识别该游戏。
选择Single Leaderboard
选项并根据需要填写下一个表单。 由于这是让游戏中心正常工作的黑客行为,用于填写排行榜的信息并不重要。 确保添加语言,以便App Store Connect
可以创建排行榜。
完成后,选择保存按钮,您现在应该已经配置了应用程序的排行榜。 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.swift
中viewDidLoad()
的底部:
GameCenterHelper.helper.viewController = self
构建并运行以查看此操作。
使用Apple ID
登录后,您将在控制台中看到熟悉的成功横幅以及消息。
对游戏中心进行身份验证非常简单。 它主要包括设置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)
此按钮节点的配置类似于本地按钮,但在玩家通过身份验证之前,它最初会被禁用。
构建并运行。
要在玩家向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
)
构建并运行
太好了,现在在线按钮显示正确的状态。
如果您离开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()
构建并运行,然后选择在线按钮。
最初,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
。
构建并运行。
很好!您现在可以使用Game Center
创建新的匹配项。现在它有点麻烦。创建匹配后,您必须退出视图,关闭matchmaker
,然后再打开matchmaker
。当您实现turn events
的处理时,您将解决此问题。
Under the Hood of Game Center
游戏现在处于一种状态,您可以跟踪身份验证状态更改并显示Game Center
的matchmaker
来管理游戏。完成在线体验的最后一件事是处理游戏的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
)
构建并运行
很好! 现在你可以看到游戏了。 除此之外,您会注意到您可以像在本地模式中一样玩整个游戏。 为了防止这种情况,您需要向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:构建基于回合制的游戏,感兴趣的给个赞或者关注~~~