本文翻译整理自:Game Center Programming Guide( Updated: 2016-06-13
 https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/GameKit_Guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008304
文章目录
- 一、关于游戏中心
- 1、概览
- 游戏中心服务在运行时提供一些游戏资源
- 您的游戏显示Game Center的用户界面元素
- Game Center功能需要经过身份验证的玩家
- 排行榜要求你的游戏有一个得分机制
- 排行榜集允许您管理排行榜
- 成就需要你的游戏来衡量玩家的进步
- 挑战允许玩家互相挑战
- 配对需要您的游戏设计来融入多人游戏
 
- 2、如何使用本文档
- 3、先决条件
- 4、另见
 
- 二、开发游戏中心意识游戏
- 1、在您的用户界面中支持游戏中心
- 2、调整您的游戏设计
- 3、自定义游戏中心的声音
- 4、创建和管理游戏中心资源
- 游戏组
 
- 5、将Game Center融入您的游戏
- 测试你的游戏中心游戏
- 需要游戏中心(仅限iOS)
- 在您的游戏中可选支持游戏中心
 
 
- 三、显示游戏中心用户界面元素
- 1、Game Center UI由View控制器显示(iOS)
- 2、Game Center UI由您的窗口显示(OS X)
- 3、显示通知横幅
 
- 四、在游戏中心与玩家合作
- 1、游戏中心管理玩家帐户
- 玩家标识符字符串唯一识别玩家
- 本地播放器是登录到设备的播放器
- 玩家对象提供关于玩家的详细信息
 
- 2、与玩家合作时的常见任务
- 对设备上的本地播放器进行身份验证
- 验证本地播放器
- 玩家成功认证后立即启用其他游戏中心代码
- 在多任务应用程序中验证本地播放器
 
- 检索本地玩家的朋友
- 检索有关玩家的信息
- 为玩家加载照片
 
- 允许本地玩家邀请其他玩家成为朋友
 
 
- 五、排行榜、成就和挑战
- 1、成就
- 支持成就清单
- 设计成就
- 创造成就,展示玩家在游戏中可以做的不同事情
- 创造需要不同技能或奉献水平的成就
- 为游戏中的不同部分或模式创建成就
- 使用隐藏的成就来取悦和奖励玩家
- 为游戏的后续版本节省一些预算
 
- 配置iTunesConnect中的成就
- 为您的游戏添加成就支持
- 向游戏中心报告成就进度
- 报告成就进展的最佳做法
 
- 加载成就进度
- 重置成就进度
 
 
- 2、排行榜
- 支持排行榜清单
- 排行榜需要评分机制
- 一个游戏可以有多个排行榜
- 一个排行榜是默认排行榜
- 组合排行榜从多个单一排行榜收集分数
- 在iTunesConnect中使用排行榜
- 向游戏中心报告分数
- 安全和分数报告
 
- 使用默认排行榜
- 显示标准排行榜
- 检索分数数据
- 使用分数上下文扩展排行榜
 
- 3、排行榜集
- 支持排行榜集的清单
- 一个游戏可以有多个排行榜集
- 游戏组排行榜和排行榜设置限制
- 在iTunesConnect中使用排行榜集
- 为您的游戏添加排行榜集支持
 
- 4、挑战
- 支持挑战清单
- 从你的游戏发出挑战
- 显示挑战用户界面
- 显示和关闭挑战视图控制器
- 发布挑战的最佳实践
 
 
 
- 六、保存游戏
- 1、有关已保存游戏的一般信息
- 2、iCloud要求
- 3、保存游戏
- 保存游戏数据
 
- 4、检索已保存的游戏
- 冲突保存的游戏
 
 
- 七、配对概述
- 1、游戏中心支持多种匹配
- 2、Game Center提供多种方式将玩家连接到拟合中
- 3、配对和游戏是独立的任务
- 4、你可以拟合不同版本的游戏
- 5、创建任何类型的拟合从拟合请求开始
- 拟合请求必须指定拟合中的玩家数量
- 邀请玩家列表
- 玩家组
- 玩家属性
- 实现玩家属性
- 玩家属性示例:角色扮演游戏
- 玩家属性示例:国际象棋
 
 
 
 
- 八、实时匹配
- 1、为您的游戏添加实时配对的清单
- 入门
- 寻找拟合的玩家
- 在拟合参与者之间交换数据
- 将语音聊天添加到拟合
 
- 2、游戏中心实时匹配概述
- 3、寻找拟合的玩家
- 使用标准匹配用户界面
- 呈现配对用户界面
- 处理来自其他玩家的邀请
- 将玩家添加到现有拟合
 
- 实现自定义拟合用户界面
- 自动匹配以填充拟合
- 取消搜索
- 邀请特定玩家进行拟合
- 搜索附近的玩家
 
- 寻找游戏中的玩家活动
 
- 4、在拟合参与者之间交换数据
- 设计你的网络游戏
- 开始拟合
- 确定最佳服务器
- 向其他玩家发送数据
- 从其他玩家接收数据
- 与拟合断开
 
- 5、将语音聊天添加到拟合
- 创建音频会话(仅iOS)
- 创建语音通道
- 启动和停止语音聊天
- 启用和禁用麦克风
- 控制语音聊天的音量
- 观察玩家的状态何时改变
 
 
- 九、主办比赛
- 1、为托管玩家进行拟合
- 2、主办比赛概述
- 3、使用Matchaker View控制器创建托管拟合
- 关闭匹配视图控制器
 
 
- 十、回合制比赛
- 1、实现基于回合的拟合的清单
- 2、每个拟合都有参与者名单
- 3、拟合数据代表拟合状态
- 4、你的游戏决定游戏规则
- 5、每当发生重要事件时保存拟合数据
- 6、使用游戏工具包实现基于回合的拟合
- Game Center对拟合施加限制
- 加入新的拟合
- 显示标准配对用户界面
- 实现自定义拟合接口
- 以编程方式处理邀请
 
- 使用现有匹配项
- 检索关于拟合的信息
- 使用拟合数据
- 加载拟合数据
- 保存拟合数据
 
- 推进拟合状态
- 当参与者离开拟合时设置拟合结果
- 结束拟合
- 响应拟合通知
 
- 7、将交换添加到基于回合的拟合
- 交易所解剖
- 发送交换请求
- 响应交换请求
 
 
一、关于游戏中心
人们喜欢玩游戏。
 应用商店的游戏也不例外——游戏仍然是iOS上最受欢迎的应用类别。
 游戏本质上是一种社交活动。
 有时,这种社交互动是游戏本身的一部分,例如当游戏提供竞争或合作的多人游戏时。
 但即使是针对单人体验的游戏,玩家也喜欢看到并分享他们的成就。
因为社交游戏是玩游戏体验的重要组成部分,Apple直接通过Game Center服务支持它。
 Game Center允许玩家的设备连接到Game Center服务并交换信息。
 下图显示了用户可以与Game Center交互的几种方式。

每个玩家执行不同的活动,但他们都与Game Center互动:
- Bob使用Apple提供的Game Center应用程序查看他们在支持Game Center的游戏中获得的分数。
 Game Center应用程序显示Bob的分数和其他玩家获得的分数。
 即使分数由Game Center应用程序显示,分数数据和格式也由游戏提供给Game Center。
- 乔正在玩一个支持成就的冒险类游戏,他们刚刚发现了一个他们想要完成的任务的物品,游戏向Game Center发送消息以更新存储在那里的进度。
- Mary、Alice和Charlie正在玩一款支持Game Center牵线搭桥的游戏,Game Center为玩家的设备提供了一个相互查找和连接的平台,游戏通过Game Center的服务器在参与者之间交换数据。
- 萨拉玩另一个多人游戏也使用游戏中心的配对。
 萨拉的游戏支持回合制游戏,萨拉收到了一条消息推送,表明轮到他们行动了。
1、概览
Game Center最好被视为为游戏开发者和终端用户提供功能的互联组件的集合:
- Game Center服务是Game Center的在线部分。
 Game Center服务器存储玩家和游戏数据,并将数据和其他服务出售给Mac和iOS设备。
- Game Kit框架提供了开发人员用来为他们的游戏添加对Game Center支持的类。
 Game Kit从iOS4.1和OS X v10.8开始提供。
- Game Center应用程序提供了一个集中式应用程序,玩家可以使用它来访问Game Center的功能。
为了让玩家在你的游戏中利用游戏中心,并且让你的游戏在游戏中心应用程序中可见,你必须明确地在你的游戏中添加对游戏中心的支持。
 你可以通过实现鉴权和至少一个其他游戏中心特征来做到这一点。
游戏中心服务在运行时提供一些游戏资源
所有应用程序都在其捆绑包内包含用于显示应用程序用户界面的图像和本地化文本。
 应用程序根据需要从捆绑包中加载这些资源。
 当您设计一个支持Game Center的游戏时,您创建的一些资源不会存储在捆绑包中。
 相反,这些资源会在您的游戏开发过程中上传到Game Center服务。
 在运行时,您的游戏会从Game Center下载资源。
 将这些资源存储在Game Center上的主要原因是,这些资源也被Game Center应用程序使用。
 例如,当Game Center应用程序显示您的游戏排行榜之一时,它会下载您提供的资源,以便它以与您的游戏相同的方式显示分数数据。
向Game Center提供部分资源的要求会影响您设计、开发和测试游戏的方式。
相关章节 : 开发游戏中心感知游戏
您的游戏显示Game Center的用户界面元素
Game Kit提供了许多向玩家呈现全屏用户界面的类,提供了标准类来显示排行榜、成就和配对屏幕,例如GKGameCenterViewController类提供了在您的游戏中显示Game Center内容的最简单方式。
在iOS,这些是作为视图控制器提供的。
 游戏中的视图控制器在必要时会显示这些视图控制器之一。
 在OS X上,使用相同的类,但Game Center提供了在窗口中显示它们所需的基础设施。
Game Kit还提供了对横幅的支持。
 横幅出现一小段时间以向玩家显示消息。
 当某些事件发生时,Game Kit会自动向玩家呈现一些横幅消息,但您的游戏可以使用GKNotificationBanner类来显示您自己的消息。
相关章节 : 显示游戏中心用户界面元素
Game Center功能需要经过身份验证的玩家
Game Center的所有功能都需要设备上有一个经过身份验证的玩家,即本地玩家。
 在您的游戏使用任何Game Center功能之前,它必须成功验证本地玩家的身份。
 大多数Game Center类只有在有经过身份验证的玩家时才起作用,这些类隐含地引用本地玩家。
 例如,当您的游戏向排行榜报告分数时,它总是报告本地玩家的分数。
当没有经过身份验证的玩家时,您的游戏必须禁用所有Game Center功能。
相关章节 : 在游戏中心与玩家合作
排行榜要求你的游戏有一个得分机制
排行榜允许您的游戏将分数发布到Game Center服务。
 玩家可以通过在Game Center应用程序中查看排行榜来查看这些分数,但您的游戏也可以仅用几行代码显示标准排行榜界面。
 或者,如果您希望自定义排行榜的外观,您的游戏可以下载原始分数数据。
 您可以为游戏创建多个排行榜,并使用游戏的评分机制自定义每个排行榜。
相关章节 : 排行榜、成就和挑战
排行榜集允许您管理排行榜
将排行榜组合成排行榜集,以便对游戏的排行榜进行逻辑分组。
 实施排行榜集会增加游戏允许包含的排行榜数量。
 将单个级别的所有排行榜组合成一个集合,或将每个级别的高分排行榜组合成一个集合,如何组合排行榜的决策取决于您。
相关章节 : 排行榜、成就和挑战
成就需要你的游戏来衡量玩家的进步
成就是玩家在游戏中完成的特定目标,例如“找到10个金币”或“在30秒内捕获旗帜”。
 与排行榜一样,玩家可以在Game Center应用程序或您的游戏中查看成就。
 在Game Center应用程序中,玩家可以将获得的成就与朋友获得的成就进行比较。
 在您的游戏中,您可以选择显示标准用户界面,也可以下载原始数据来创建自己的自定义界面。
相关章节 : 排行榜、成就和挑战
挑战允许玩家互相挑战
挑战从一个玩家发送到另一个玩家。
 每个挑战都是被挑战玩家必须完成的特定目标。
 当挑战完成时,挑战者和被挑战玩家都会收到通知。
 任何支持排行榜或成就的游戏都会自动提供挑战。
 但是,您也可以采取额外的步骤,直接在游戏中实现对挑战的定制支持。
相关章节 : 排行榜、成就和挑战
配对需要您的游戏设计来融入多人游戏
配对让有兴趣玩在线多人游戏的玩家发现彼此并连接成拟合,Game Center支持三种配对:
- 实时拟合要求所有玩家同时连接到Game Center。
 Game Kit提供了实现低级网络基础设施的类,以允许设备实时交换数据。
- hosted match 类似于实时比赛,但在比赛中涉及到你自己的服务器。在这种模式下,你可以使用 Game Center 进行匹配,但要设计和实现自己的底层网络代码。
- turn-based match(基于回合的比赛) 使用存储转发模式。你的游戏会在 Game Center 服务器上存储比赛数据快照,比赛中的任何玩家随后都可以下载这些数据。在任何给定的时间,其中一名玩家都会被指定为可以在比赛中轮流的人。你的游戏下载比赛数据后,玩家就可以进行回合,然后你的游戏会将修改后的比赛数据上传到 Game Center。当一名玩家的回合结束时,你的游戏会指定下一名玩家采取行动,该玩家会收到推送通知。 
  - Exchanges 允许非当前玩家的玩家在游戏中采取行动。玩家通过向一个或多个其他玩家发送交换请求来启动交换。然后,这些玩家可以对交换作出回应,也可以让交换超时。在交换过程中,还会向当前玩家发送更新,以便更新比赛数据。
 
相关章节 : 配对概述、实时比赛、主持比赛、回合制比赛
2、如何使用本文档
如果您是开发Game Center感知游戏的新手,请从阅读开始开发Game Center感知游戏。
 本章描述了设计和实现支持Game Center的游戏的过程。
 然后阅读显示Game Center用户界面元素它描述了在您的游戏中显示Game Center用户界面元素的常见约定。
 这个主题对OS X开发人员来说尤其重要,因为它解释了Game Kit提供的用于在您自己的用户界面上显示Game Center内容的基础设施。
 iOS开发人员会发现Game Center符合视图控制器的标准编程模型。
所有开发者都必须阅读在Game Center中与玩家合作,了解如何在他们的游戏中验证玩家身份。
 然后,根据需要,阅读其他章节,了解如何实现特定的Game Center功能。
虽然本指南描述了与Game Center通信和使用Game Center网络功能的许多方面,但它不是低级网络设计模式的参考。
 Game Kit提供了一些网络基础设施,但要实现实时网络游戏,您需要了解并准备好处理常见的网络问题,例如网络速度慢和断开连接。
3、先决条件
在尝试创建支持Game Center的游戏之前,您应该已经熟悉在您瞄准的任何平台上开发应用程序:
- 开始开发iOS应用程序(Swift)
Game Kit 还严重依赖委托和屏蔽对象。
4、另见
有关Game Kit框架的详细信息,请参阅 Game Kit框架参考 。
这个 GKAuthentication 示例演示了如何实现用户鉴权。
这个 GK排行榜 示例演示了如何实现排行榜。
这个 GK成就 示例演示了如何实现成就。
要了解如何对应用进行代码签名和预配以使用Game Center,请阅读应用分布快速入门。
二、开发游戏中心意识游戏
添加Game Center支持需要的不仅仅是简单的代码更改。Game Center对游戏的设计提出了具体要求。
 例如,要实现排行榜,您的游戏必须有一种定量衡量玩家表现的方法。
 但更重要的是,Game Center改变了您设计和测试游戏的方式。
 您不仅仅是向应用程序添加代码;您还在配置Game Center使用的资产。
 这些资产与您的应用程序包分开配置,但与游戏中的代码密切相关。
 您需要测试这些部分,以确保所有部分都能正常工作。
要创建支持Game Center的游戏,您需要在开始编写代码之前了解这些基础知识。
1、在您的用户界面中支持游戏中心
当您为游戏添加Game Center支持时,游戏用户界面的某些部分会显示Game Center内容。
 其中一些内容,例如玩家鉴权,是必需的。
 其他用户界面元素,例如排行榜,是可选的,但通常是玩家所期望的。
图1-1显示了游戏用户界面的示例模型,显示为一系列屏幕和它们之间的转换。
 并非所有游戏都使用这种精确的用户界面屏幕组织,但它为讨论添加Game Center如何改变游戏用户界面提供了一个有用的起点。
图1-1游戏用户界面屏幕流程
 
当这个示例游戏启动时,玩家首先看到的是加载屏幕。
 游戏通常需要大量记忆密集型图像、3D模型、声音和游戏数据。
 这些资源必须在游戏运行时快速访问。
 通常,为了实现这一点,游戏在首次启动时加载关键资源,并将它们保存在记忆中,直到应用程序终止。
 典型的加载屏幕会显示有关游戏或您公司的信息,以及内容加载时的进度条或动画用户界面。
在游戏加载了足够的资源后,它会过渡到菜单屏幕。
 通常,这是玩家看到的第一个交互式屏幕。
 菜单屏幕有一系列按钮,玩家使用这些按钮来启动游戏玩法或游戏的其他功能。
 例如,通常从菜单屏幕访问以下任何或所有功能:
- 单人游戏模式
- 多人游戏模式
- 游戏配置选项
- 社交网络服务
使用图1-1所示的屏幕流程,点击单人游戏播放按钮允许用户选择游戏的难度。
 这个屏幕的目的是为玩家提供他们想如何玩游戏的选项。
 根据你游戏的设计,这个配置过程可以是单个屏幕上的简单界面,也可以是一系列复杂的用户界面屏幕。
 例如:
- 允许玩家选择颜色的国际象棋游戏。
- 一种益智类游戏,允许玩家选择一个特定的谜题来玩。
- 一种角色扮演游戏,具有一系列允许玩家配置角色的屏幕。
游戏玩法屏幕是执行大部分游戏逻辑的地方。
 项目的这一部分包括渲染代码、用户界面交互和执行游戏概念的游戏逻辑。
 根据您正在实现的游戏类型,您的应用程序的游戏玩法可以是单个屏幕或一系列在它们之间转换的多个屏幕。
玩家完成游戏后,会出现一个尾声屏幕,告诉玩家他们的表现如何。
 例如,包含评分机制的游戏可以向玩家展示他们的分数是如何计算的。
 一旦玩家关闭此屏幕,游戏就会转换回主菜单。
图1-2显示了修改为包含Game Center的示例游戏。
图1-2启用Game Center的UI
 
所有Game Center应用程序都必须对玩家进行身份验证。
 鉴权通常从用户开始游戏时开始。
 在示例游戏中,鉴权过程在显示标题屏幕时开始。
 如果玩家当前未登录Game Center,则标题屏幕将暂时替换为Game Center的鉴权屏幕。
 该屏幕一直持续到玩家完成鉴权过程或取消。
 要么将玩家返回标题屏幕。
 如果玩家已经登录Game Center帐户,则永远不会显示鉴权屏幕。
 相反,会短暂显示横幅以欢迎玩家返回,而无需离开主菜单。
菜单屏幕现在包括显示游戏中心内容的按钮。
 当玩家按下这些按钮之一时,游戏分配、初始化并呈现Game Kit的用户界面类之一。
 当关闭时,控制返回到标题屏幕。
图1-2所示的游戏也进行了修改,以提供多人游戏模式。
 与单人模式一样,游戏呈现一系列屏幕来配置所需的拟合。
 其中一些屏幕是特定于游戏的,用于配置游戏的选项。
 其他屏幕显示内置的匹配用户界面。
 请注意,您自己游戏的配置屏幕可以出现在匹配屏幕之前和之后。
 在匹配屏幕出现之前所做的选择通常是影响匹配体验的选择。
 例如,玩家可以选择一个特定的地图来玩,这样他们就可以与对同一张地图感兴趣的玩家进行匹配。
 匹配完成后出现的配置屏幕用于允许现在连接成拟合的玩家对如何玩拟合做出其他决定。
 两组屏幕都包含拟合配置数据,必须同步到拟合中的所有玩家。
最后,可以启动游戏中心感知游戏,因为玩家收到了与游戏中心相关的消息推送——例如,当玩家被邀请加入拟合时。
 如果您的游戏是由于消息推送而启动的,它可以在鉴权过程完成后立即直接变换/转移到匹配屏幕。
 在这种情况下,标题屏幕只出现很短的一段时间。
虽然图1-2侧重于要显示的内容的新屏幕,但现有屏幕上的内容也可能发生变化,或者您在这些屏幕上执行的任务也可能发生变化。
 例如,在拟合结束时,您可以让您的游戏向Game Center排行榜报告它显示的分数。
 同样,在游戏过程中,当玩家朝着成就前进时,您的游戏会将更新的进度发送到Game Center。
重要的是,您要记住,这只是设计游戏用户界面的一种可能方式。
 在构建用户界面时,您应该考虑其他替代方案,并选择一个对您的游戏最有意义的方案。
 例如,许多游戏使用具有一致艺术主题的自定义图形设计。
 在这种情况下,Game Center的标准用户界面类不会拟合您的游戏主题。
 因此,您必须问的一个问题是,您是要使用标准用户界面(添加起来几乎不费力),还是要合并自己的自定义用户界面。
 如果您决定创建自定义用户界面,您应该进一步决定是将该用户界面创建为内容的不同屏幕,还是将内容合并到现有界面中,例如您的游戏的菜单屏幕。
2、调整您的游戏设计
Game Center中的每个特征都对您的游戏策划提出了要求。
 例如,排行榜分数是代表玩家在玩您的游戏时表现如何的数值。
 要在您的游戏中实施排行榜,游戏策划必须包括一个游戏机制,并根据玩家表现如何计算分数。
 每个Game Center特征的要求和限制将在本文档后面的详细章节中描述。
 表1-1总结了相关特征。
| 特征 | 要求 | 限制 | 资产 | 
|---|---|---|---|
| 排行榜 | 评分机制 | 排行榜的最大数量,排行榜分数的格式选项 | 字符串、排行榜排序、艺术品 | 
| 排行榜集 | 排行榜 | 允许的最大排行榜和集合数。 组中的所有应用程序必须有集合或没有集合。 | 字符串、排行榜分组、艺术品 | 
| 成就 | 跟踪代码以观察玩家在游戏中的操作 | 每场比赛的最大成就数,最大积分分配。 | 弦乐,艺术品 | 
| 挑战 | 您必须实施排行榜或成就 | — | — | 
| 保存游戏 | 加载和保存游戏信息的代码 | 玩家必须拥有iCloud帐户 | — | 
| 牵线搭桥 | 多人游戏策划 | 玩家数量、数据包大小、网络 | 自定义邀请音 | 
3、自定义游戏中心的声音
您可以使用GKInvite更改默认Game Center声音。
 例如,当玩家收到邀请时,Game Center会播放声音来宣布邀请。
 您的游戏可以在收到邀请时提供自定义声音来播放。
 为此,请在应用程序包中包含一个声音文件。
 所有默认Game Center声音都可以使用自定义声音文件进行更改。
 该文件必须是caf、aif或wav格式,并命名为GKInvite以及适当的文件扩展名。
4、创建和管理游戏中心资源
构建支持Game Center的游戏与构建不支持Game Center的游戏不同。
 所有游戏都有资源,例如图像、模型和声音文件,用于渲染游戏的用户界面。
 但是,您还需要创建特定的资源来支持Game Center的用户界面。
 您提供这些资产的方式与在标准应用程序中不同。
 图1-3显示了标准应用程序的流程。
图1-3在应用商店发布应用

- 您在自己的开发机器上开发和测试您的应用程序。
 应用程序资源嵌入在您使用Xcode创建的应用程序包中。
 构建应用程序的过程将这些资源包含在最终包中,它们在运行时由应用程序的二进制文件加载。
- 当您的应用完成后,您通过将应用捆绑包复制到iTunesConnect来提交应用捆绑包以供审核。
 同时,您在iTunesConnect中使用与应用捆绑包中相同的捆绑包ID创建记录。
 该记录包括描述应用的元信息;这些数据中的大部分用于描述商店应用中的应用。
 一旦提交,Apple会审核您的应用,它要么被批准,要么被拒绝。
- 应用获得批准后,您可以选择发布时间。
 发布后,应用捆绑包和元信息将从iTunes连接复制到应用商店。
 在那里,用户发现并购买应用。
 购买后,应用捆绑包将下载到用户的设备。
图1-4显示了创建Game Center资产时所需的附加步骤和流程,虽然没有显示此步骤,但您的应用程序包和元信息会像以前一样提交给iTunesConnect。
图1-4开发和分发Game Center资产

以下是开发过程的一些变化:
- 最初,iTunes连接记录是在开发过程结束时创建的,当时您准备提交应用程序以供审核。
 创建支持Game Center的游戏时,一旦您准备好实施和测试您的Game Center功能,您就会创建iTunes连接记录。
 需要iTunes连接记录来授权您的游戏访问Game Center服务。
- 您在iTunesConnect中创建的资产会自动复制到Apple提供的特殊测试环境中。
 此环境模仿常规Game Center服务,但对开发人员是私有的。
 游戏的开发版本会自动加载测试环境中的资产。
- 当您的游戏发布时,Game Center资产会以与您的游戏复制到应用商店相同的方式复制到Game Center服务。
 这些游戏版本从实时Game Center服务器加载资产。
 一旦你的资产被发布到实时服务器,一些资产变得更加难以更改,因为它们已经被玩家和游戏的实时版本使用。
 例如,排行榜分数使用你创建的排行榜资产进行格式化。
 如果你改变了评分机制,并将排行榜资产更改为拟合,旧的分数仍然会发布在Game Center上,并且与较新的分数不一致。
 出于这个原因,你创建的一些资产在使用这些资产的游戏版本发布后无法修改。
 你应该设置你的开发流程,以确保你的资产(和相关代码)在你提交游戏进行审查之前经过广泛的测试,这样就不需要对这些资产进行更改。
游戏组
游戏组是iTunes连接中的一个附加特征,允许您将多个游戏中心感知游戏绑定到一个共享游戏中心内容部分的连接组中。
 游戏组中的每个游戏都是iTunes连接上的一个不同游戏,具有自己的记录。
 但是,一些资产是共享的。
游戏组的好处是,你可以在应用商店发布多个不同版本的游戏,但仍然允许这些游戏像在线游戏一样运行。
 例如:
- 你有单独的iOS和OS X版本的游戏。
- 你有免费和付费版本的游戏。
在iTunesConnect中可以使用两种不同的分组形式:
- 共享游戏资产 您可以创建一个通用的排行榜和成就资产池,供所有版本的游戏共享。如果玩家同时拥有两个版本的游戏,在其中一个版本中获得的项目在另一个版本中也能看到。
- 匹配兼容性 这可以让您指定哪些版本的游戏可以进行匹配。您可以确保兼容性组中的所有游戏共享相同的网络代码。甚至可以在使用单个捆绑 ID 发行的游戏的不同版本之间组建匹配组。例如,如果你的游戏有四个版本(1.0、1.1、2.0、3.0),你可以指定 1.0 版和 1.1 版的游戏可以一起玩,因为 1.1 版的网络实现没有任何变化。
游戏组给游戏设计带来了额外的进程开销。现在,你不仅需要处理游戏代码与在 iTunes Connect 中创建的资产之间的同步问题,还需要同步不同游戏实现之间的任何实现变更。有关如何在 iTunes Connect 中创建和管理游戏组的信息,请参阅《iTunes Connect 游戏中心配置指南》 中的 组。
5、将Game Center融入您的游戏
以下是设计支持Game Center的游戏时应遵循的合理流程:
- 决定您计划支持哪些Game Center功能。
- 定义你的游戏机制,牢记Game Center的要求和限制。
- 实施游戏引擎,暂时忽略Game Center。
 专注于开发游戏的其他方面,例如游戏引擎和游戏玩法。
 这样做是为了避免需要提前创建iTunesConnect记录。
- 当您需要实施Game Center时: 
  - 使用游戏的捆绑ID为游戏创建iTunes连接记录。
 在iTunes连接记录中启用游戏中心;在iTunes连接记录中设置此标志授权游戏中心服务允许您的游戏连接到它。
- 使用游戏的捆绑ID创建显式应用ID。
- 在此应用ID中启用Game Center;这将授权设备上的应用联系Game Center的服务器。
- 使用这个新的显式应用ID创建新的配置文件。
- 测试以确保您可以使用此配置文件构建和签署您的游戏。
- 将Game Kit框架添加到您的项目中。
- 导入GameKit/GameKit.h表头。
 重要提示:Game Center仅适用于通过iOS应用商店或Mac应用商店分发的应用。
 要了解如何对应用进行代码签名和预配以使用此应用服务,请阅读应用分布快速入门。
 Xcode将为您创建包含明确应用ID的团队预配配置文件。
 
- 使用游戏的捆绑ID为游戏创建iTunes连接记录。
- 如果您的游戏需要Game Center,请将Game Center键添加到您的应用需要从设备获得的功能列表中。
 请参阅在您的游戏中需要Game Center(仅iOS)。
- 如果你的游戏不需要Game Center,你应该建立一个从你的游戏到Game Kit框架的弱链接。
 然后,当你的游戏启动时,测试以确保支持Game Center。
 请参阅您的游戏中可选支持Game Center。
- 实施鉴权。
- 实现其他Game Center功能。
- 使用Game Center开发环境测试您的Game Center功能。
测试你的游戏中心游戏
测试您的Game Center游戏不再需要单独的配置文件进行测试。
 有关移除沙盒环境如何影响测试的更多信息,请参阅 Game Center:没有沙盒的生活 。
需要游戏中心(仅限iOS)
如果您的游戏需要Game Center函数(例如,需要Game Center拟合玩家的多人游戏),您希望确保只有支持Game Center的设备才能下载您的游戏。
 要确保您的游戏仅在支持的设备上运行,请将gamekit键添加到游戏的Info.plist文件中所需设备功能的列表中。
 有关属性列表的信息,请参阅 Information Property List Key参考 。
在您的游戏中可选支持游戏中心
如果您希望您的游戏使用Game Center,但您的游戏不需要Game Center函数正常,您可以将您的游戏弱链接到Game Kit框架,并在运行时测试其是否存在。
 请参阅 SDK兼容性指南 。
三、显示游戏中心用户界面元素
Game Center提供了两种不同类型的用户界面元素供您在游戏中使用。
 第一种类型旨在由您的游戏模态显示,覆盖您自己的游戏用户界面并暂时中断游戏的正常流程。
 通常,这些用户界面屏幕允许玩家与从Game Center加载的内容进行交互或执行Game Center任务。
 当玩家完成与这些内容屏幕之一的交互时,您的游戏会显示下一个适当的屏幕,要么返回到您的一个游戏屏幕,要么前进到另一个内容屏幕。
在iOS,全屏用户界面被打包为视图控制器,并遵循系统上视图控制器的标准约定。
 您的游戏的一个视图控制器预计会在需要时呈现这些视图控制器,然后在视图控制器被关闭时做出响应。
 在OS X上,Game Kit提供的一个特殊类提供了类似的基础架构,以便您的游戏可以呈现用户界面。
第二种类型的用户界面元素是短时间向玩家显示的横幅。
 之后,横幅自动从屏幕上消失。
 虽然玩家可以与一些横幅互动,但通常横幅只是用来向玩家显示一个消息。
 游戏中心代表你的游戏显示许多横幅,但如果你的游戏有你需要显示的信息,你也可以向玩家展示你自己的横幅。
1、Game Center UI由View控制器显示(iOS)
Game Kit使用的约定是让您的一个视图控制器显示Game Kit视图控制器。
 视图控制器充当它显示的视图控制器的委托,以便在玩家完成查看显示的屏幕时通知视图控制器。
 Game Center视图控制器显示许多不同的Game Center内容,因此大多数游戏应该提供一个按钮,将玩家带到这个屏幕,即使游戏也使用自定义用户界面显示Game Center内容。
 要显示Game Kit视图控制器,您需要创建一个GKGameCenterViewController的新实例,设置其委托并显示所需信息。
在大多数情况下,当显示这些标准用户界面类之一时,您的游戏会暂停游戏或其他实时行为。
 当视图控制器稍后被关闭时,它可以恢复这些活动。
2、Game Center UI由您的窗口显示(OS X)
在OS X中,视图控制器的作用与iOS不同。
 在许多情况下,负责用户界面的其他对象会显示Game Center控件。
 它通过使用GKDialogController类来实现这一点。
 该类在游戏提供的窗口中显示用户界面。
以与iOS相同的方式创建和填充Game Center视图控制器。
 但是,创建Game Center视图控制器后,您将创建GKDialogController实例。
 Game Center视图控制器使用presentViewController:方法与GKDialogController相关联。
3、显示通知横幅
在游戏中创建横幅是使用GKNotificationBanner类完成的。
 您可以为横幅创建标题和消息,并将此信息发送到showBannerWithTitle:message:completionHandler:。
 如果要更改横幅显示的时间,请使用showBannerWithTitle:message:duration:completionHandler:方法。
四、在游戏中心与玩家合作
玩家是任何支持Game Center的游戏的关键部分,因为所有Game Center功能都与玩家相关。
 作为游戏开发者,您需要了解Game Center用于支持玩家帐户的一些基础设施,以及如何在您的应用程序中实现它。
 阅读本章后,您将了解如何在游戏中管理玩家信息。
 特别是,您将学习:
- 游戏如何识别Game Center中的不同玩家
- 玩家如何登录Game Center,以及您的游戏如何知道玩家是否登录了正在运行的设备
- 如何从游戏中心检索特定玩家的详细信息
- 如何在游戏中实现支持,以便玩家可以在Game Center上邀请其他玩家成为朋友
1、游戏中心管理玩家帐户
为了利用Game Center的功能,用户创建了一个Game Center帐户,该帐户标识为特定用户。
 该用户在Game Center上被称为玩家。
 Game Center服务跟踪该玩家的关键帐户信息,例如该玩家是谁、该玩家玩过什么游戏、该玩家在每场游戏中的成就以及该玩家的朋友是谁。
 其中一些信息可直接用于您的游戏;通常这是与游戏特别相关的信息以及有关该玩家的一般信息。
当玩家想要在特定设备上访问Game Center时,玩家需要登录或在该设备上进行身份验证。
 玩家通过启动Game Center应用程序或启动任何实现Game Center支持的游戏来验证他们的帐户。
 在任何一种情况下,玩家都会看到一个界面来提供他们的帐户名和密码。
 一旦通过身份验证,玩家就会与该设备永久关联,直到他们在Game Center应用程序中明确退出Game Center。
 一次只能在设备上对一个玩家进行身份验证;要在设备上对新玩家进行身份验证,现有的已通过身份验证的玩家必须先退出。
Game Center旨在成为一种社交体验。
 Game Center允许玩家邀请其他玩家成为朋友。
 当两个玩家成为朋友时,他们可以在Game Center应用程序中看到彼此的状态,比较分数,并更容易地邀请对方加入比赛。
 通过Game Kit,您的游戏还可以访问有关经过身份验证的玩家的朋友的一些信息,或者允许玩家邀请玩家成为朋友。
 例如,您可以使用此功能允许玩家向他们刚刚在您的游戏内玩的拟合中认识的玩家发送朋友邀请。
玩家标识符字符串唯一识别玩家
每个玩家帐户都由GKPlayer对象中包含的玩家标识符字符串唯一标识。
 标识符字符串是在玩家帐户首次创建时创建的,并且永远不会更改,即使帐户中的其他信息发生变化。
 因此,玩家标识符是跟踪特定玩家的唯一可靠方法。
 因此,Game Kit API在需要识别特定玩家的任何地方使用玩家标识符。
 如果Game Center需要识别游戏中的特定玩家,Game Kit API会返回该玩家的标识符。
 您的游戏使用玩家标识符从Game Center检索有关该玩家的信息。
除了在与Game Center的交互中使用玩家标识符之外,您的游戏还应该在想要在本地存储特定玩家的数据时使用玩家标识符。
 例如,如果您的游戏存储数据以跟踪玩家的进度(例如在设备上、在您自己的服务器上或在iCloud上),请使用玩家标识符来区分在同一设备上玩的多个玩家。
 这样,如果不同的玩家登录设备,您可以通过显示特定于该玩家的内容来立即个性化体验。
重要提示:永远不要对玩家标识符字符串的格式或长度做出假设。
 尽管任何单个玩家标识符字符串都是不可变的,但玩家标识符字符串的格式和长度可能会发生变化。
 您必须将玩家标识符字符串视为不透明的标记。
本地播放器是登录到设备的播放器
当你设计你的游戏时,你会发现你的游戏经常同时感知多个玩家。
 例如,如果你使用多人网络设计游戏,那么你就有拟合中每个玩家的信息和玩家标识符。
 但是,在任何特定设备上,一个玩家总是优先于其他玩家。
 本地玩家是当前通过身份验证可以在该设备上玩的玩家。
 在图3-1中,两个玩家在网络拟合中连接。
 在左边的设备上,鲍勃是本地玩家,玛丽是远程玩家。
 在右边的设备上,玛丽是本地玩家,鲍勃是远程玩家。
图3-1本地和远程玩家
 
Game Kit中几乎所有向Game Center发送数据或从Game Center检索信息的类都希望设备拥有经过身份验证的本地玩家。
 这些类所做的工作始终代表本地玩家。
 例如,如果您的游戏向排行榜报告分数,它只能报告本地玩家获得的分数。
 因此,在使用任何Game Center功能之前,您的游戏必须首先验证设备上是否有本地玩家。
 如果Game Kit尝试执行与Game Center相关的任务,而设备上没有经过身份验证的玩家,则会向您的游戏返回错误。
重要提示:支持多任务处理的游戏应特别注意这种行为。
 当您的游戏进入后台时,玩家可能会启动Game Center应用并退出。
 此外,在控制权返回给您的应用之前,另一个玩家可能会签到。
 每当您的游戏移动到前台时,当不再有经过身份验证的玩家时,它可能需要禁用其Game Center功能,或者它可能需要根据新本地玩家的身份刷新其内部状态。
玩家对象提供关于玩家的详细信息
当您的游戏需要特定玩家的详细信息时,它会通过使用玩家的玩家标识符向Game Center进行查询来检索这些详细信息。
 Game Kit在GKPlayer对象中将这些详细信息返回给您的游戏。
 表3-1列出了玩家对象上一些更有趣的属性。
Table 3-1 Important Player Object Properties
| Property | 描述 | 
|---|---|
| playerID | 保存用于检索此玩家信息的玩家标识符字符串的字符串。 | 
| displayName | 您可以在自己的用户界面中为该玩家显示的用户可读字符串。 | 
| 例如,在网络拟合中,您可以显示拟合中每个玩家的显示名称,以便每个人都知道他们在和谁比赛。 | 
GKLocalPlayerGKLocalPlayer是GKPlayer类的特殊子类,包括特定于本地播放器的附加属性:
- 该authenticated属性说明本地播放器是否已被认证。
- 该underage属性说明该玩家是否未成年。
Table 3-2 Local Player Object Properties
| Property | 描述 | 
|---|---|
| authenticated | 一个布尔值,说明本地玩家是否已通过身份验证并登录Game Center。 | 
| 如果玩家未通过身份验证,则某些Game Center功能不可用。 | |
| underage | 一个布尔值,说明本地玩家是否未成年。 | 
| 当此属性的值为 YES时,某些Game Center功能将被禁用;如果您尝试使用这些功能,Game Center会返回GKErrorUnderage错误。 | |
| 您的游戏还可以使用此属性来决定是否应为未成年玩家禁用一些自己的功能。 | 
除了给定玩家的显示名称之外,如果玩家提供了照片,您可以将该玩家的照片下载到您的游戏中,并在游戏的用户界面中使用它。
2、与玩家合作时的常见任务
对设备上的本地播放器进行身份验证
您的游戏应该在启动后尽早开始对玩家进行身份验证,甚至在您呈现用户界面之前。
 鉴权是一个异步过程,不会减慢您的标题屏幕的加载速度。
 等到标题屏幕呈现后再对用户进行身份验证只会增加延迟,直到玩家可以玩您的游戏。
 您需要尽早对玩家进行身份验证的主要原因是为了iOS可以专门启动您的游戏以处理玩家感兴趣的事件。
 例如,如果您的游戏支持回合制匹配,则可能会启动它,因为玩家已经点击了通知横幅。
 因此,您需要尽早进行身份验证,以便您的游戏可以检索和处理Game Center事件。
当你的游戏对玩家进行身份验证时,Game Kit 会首先检查设备上是否已经有通过身份验证的玩家。
 由于玩家在明确退出 Game Center 之前一直都是通过身份验证的,因此设备上已经有通过身份验证的玩家的情况很常见。
 在这种情况下,系统会简短地显示一条横幅,让玩家知道身份验证已经成功,然后会立即通知你的游戏。
如果设备上当前没有经过身份验证的玩家,那么需要显示一个鉴权对话框,以便玩家可以签到现有帐户或创建新的Game Center帐户,这很重要,因为这意味着支持鉴权也意味着透明地支持帐户创建。
重要提示:Game Kit处理在所有支持Game Center的游戏中选择退出Game Center的问题。
 如果玩家已经拒绝创建帐户,当您的游戏对玩家进行身份验证时,它会被告知没有经过身份验证的玩家。
 玩家永远不会看到鉴权对话框。
 因为Game Kit在所有游戏中都处理这个过程,所以您的游戏不应该包含自己的机制来禁用Game Center鉴权或要求玩家允许进行身份验证。
 相反,您的游戏应该在每次启动时简单地对玩家进行身份验证,并在鉴权完成时做出适当的反应。
为了验证本地玩家,您的游戏将获得一个视图控制器,以便在您选择的时间显示。
 通常,这意味着您的游戏可以在显示视图控制器之前干净地暂停自己的动画和其他功能。
 这种机制允许您为玩家创造更好的用户体验。
验证本地播放器
要对本地玩家进行身份验证,您需要创建自己的方法来设置身份验证处理程序属性。
 该方法检索GKLocalPlayer类的共享实例,然后设置该对象的authenticateHandler属性以指向处理鉴权事件的屏蔽对象。
 设置鉴权处理程序后,Game Kit会自动异步对玩家进行身份验证,必要时调用您的鉴权处理程序以完成该过程。
 每次游戏从后台转移到前台时,Game Kit都会自动再次对本地玩家进行身份验证。
 有关如何创建此方法的示例,请参阅authenticateHandler属性。
始终检查本地玩家对象上的authenticated的属性,以确定Game Kit是否能够对本地玩家进行身份验证。
 不要依靠游戏接收到的错误来确定设备上是否有经过身份验证的玩家。
 即使错误返回到您的游戏,Game Kit也可能有足够的缓存信息来向您的游戏提供经过身份验证的玩家。
 此外,当鉴权失败时,您的游戏没有必要向玩家显示错误;Game Kit已经代表您向玩家显示了重要的错误。
 大多数鉴权错误返回到您的游戏主要是为了帮助您进行调试。
玩家成功认证后立即启用其他游戏中心代码
一旦玩家成功通过身份验证,您的游戏就可以读取本地玩家对象上的其他属性,以确定玩家的显示名称和其他属性。
 您还可以使用访问Game Center的其他类。
 在大多数情况下,您的游戏应该立即启用与Game Center相关的其他代码。
 例如,以下是大多数游戏在成功验证本地玩家后执行的一些常见任务:
- 读取displayName属性以检索本地玩家的姓名。
 如果您想知道玩家的详情可见,请在整个游戏中使用此显示名称;不要单独提示玩家输入他们的名字。
- 添加事件处理程序以接收Game Center功能的事件。
 例如,基于回合的比赛、实时比赛和挑战都需要事件处理程序来处理Game Center事件。
 因为您的游戏可能是专门为接收待处理的邀请而启动的;在验证玩家身份后立即添加事件处理程序可以及时处理这些事件。
- 检索本地玩家以前的成就进度。
- 为本地玩家的朋友检索玩家标识符列表。
 这是加载有关这些玩家的更详细信息之前的第一步。
- 如果您的游戏为特定玩家存储了自己的自定义信息(例如指示玩家在游戏中的进度的状态变量),您的完成处理程序也可能加载此数据,以便它可以恢复玩家的进度。
在多任务应用程序中验证本地播放器
同时支持多任务处理和Game Center的游戏在认证本地玩家时必须采取额外的步骤。
 当您的游戏在后台时,经过认证的玩家的状态可能会改变,因为玩家可能会退出或新玩家可能会签到。
 因此,当您的游戏进入后台时,您永远无法依靠信息保持不变。
以下是在支持多任务处理的游戏中验证本地玩家的一些指南:
- 像其他多任务应用程序一样,你的游戏应该在进入后台之前归档。
- 一旦您的游戏移动到后台,本地玩家对象的authenticated属性的值就会变得无效,直到您的游戏移回前台。
 在Game Kit重新认证玩家并调用您的鉴权处理程序之前,您无法读取该值来确定玩家是否仍被认证。
 在调用完成处理程序之前,您的游戏必须表现得好像没有经过认证的玩家。
 一旦调用了您的处理程序,存储在authenticated属性中的值将再次有效。
- 如果authenticated属性的值更改为NO,则不再有本地玩家被授权访问Game Center内容。
 您的游戏必须处理使用以前的本地玩家创建的任何Game Kit对象。
- 如果authenticated属性的值为YES,则设备上存在已认证的本地玩家。
 但是,新玩家可能已登录。
 您的游戏应该在对本地玩家进行身份验证后存储该玩家的玩家标识符。
 然后,在以后调用您的完成处理程序时,将本地玩家对象的playerID属性的值与您的游戏存储的标识符进行比较。
 如果标识符更改,则表示新玩家已登录。
 您的游戏应该处理与前一个玩家关联的任何对象,然后为新本地玩家创建或加载适当的状态。
检索本地玩家的朋友
你可能希望你的游戏引用本地玩家的朋友。
 例如,如果你正在实现自己的自定义配对或挑战界面,并希望允许玩家从朋友列表中挑选玩家,你可能会这样做。
 关于本地玩家朋友的信息只有在你要求时才会加载,因为这些信息可能非常大,需要一段时间才能通过网络发送。
 使用loadFriendPlayersWithCompletionHandler:方法检索关于本地玩家朋友的信息。
检索本地玩家朋友的详细信息需要两个步骤。
 首先,您的游戏加载本地玩家朋友的玩家标识符列表。
 然后,与任何其他玩家标识符一样,您的游戏调用Game Center来检索这些玩家的详细信息。
检索有关玩家的信息
无论哪个Game Kit类将玩家标识符返回到您的游戏,您都可以通过调用GKPlayer类上的loadPlayersForIdentifiers:withCompletionHandler:class方法以相同的方式检索有关这些玩家的详细信息。
 此Game Kit方法采用两个参数,第一个是您感兴趣的玩家的玩家标识符数组。
 第二个是Game Kit从Game Center检索数据后会调用的完成处理程序。
为玩家加载照片
除了在播放器对象中找到的信息之外,您还可以尝试为播放器加载照片。
 并非所有播放器对象都有与之关联的照片,因此您的代码必须能够在没有它们的情况下工作。
 使用loadPhotoForSize:withCompletionHandler:方法导入播放器照片。
 请注意,播放器对象上没有存储返回照片的属性。
 您必须编写自己的代码来将播放器照片与播放器对象关联起来。
允许本地玩家邀请其他玩家成为朋友
Game Center应用允许玩家向其他玩家发送邀请。
 使用GKFriendRequestComposeViewController类允许玩家发送好友请求。
 好友请求必须由游戏创建的视图控制器以模态方式呈现,不能推送到导航控制器上。
 当玩家拒绝好友请求时,将调用委托的friendRequestComposeViewControllerDidFinish:方法。
五、排行榜、成就和挑战
创造一个游戏不仅仅是在屏幕上放一堆精灵和图形。
 游戏需要吸引你的玩家,并创造一个鼓励你继续玩的环境。
 游戏中心为你提供了以下与玩家互动的方式:
- 成就-在游戏过程中为玩家提供反馈,以及玩家可以追求的可选目标。
- 排行榜-让玩家可以看到他们与朋友和世界其他地方相比做得有多好。
- 挑战-为玩家提供挑战他们的朋友以击败高分或完成特定成就的能力。
通过将这些Game Center选项整合到您的游戏中,您可以扩展游戏的可重玩性,同时使游戏对您的玩家更具吸引力。
1、成就
成就是追踪玩家在你的游戏中做了什么,并给予玩家更多动力继续玩你的游戏的好方法。
 成就代表了玩家在你的游戏中可以完成的量化目标。
 当本地玩家玩你的游戏时,他们在完成成就方面取得了进展。
 当玩家达到或超过目标时,成就被认为是赢得的,玩家得到奖励。
 你的游戏定义了目标和描述玩家如何获得成就的游戏机制。
 在实践中,Game Center不需要知道任何关于你的游戏策划的事情;它只知道玩家在成就方面取得了什么进展。
在Game Center中,一项成就可以赚取玩家成就积分。
 当您定义一项成就时,您可以决定它的价值。
 玩家可以看到在您的游戏中可能获得的积分总数以及他们当前获得的积分。
 Game Center应用程序允许玩家将他们的成就与朋友的成就进行比较;此比较屏幕包括获得的总积分。
当您在游戏中添加成就时,您需要配置如何授予成就以及如何向玩家描述成就。
 您还可以设计游戏机制,让玩家在完成成就方面取得进展。
支持成就清单
要为您的游戏添加成就,您需要采取以下步骤:
- 在添加成就之前,添加代码以验证本地玩家。
 请参阅在Game Center中与玩家合作。
- 设计游戏的成就。
 请参阅设计成就。
- 转到iTunes连接并为您的游戏配置成就。
 您提供向玩家显示成就所需的所有数据。
 请参阅iTunes连接中的配置成就。
- 添加代码以向Game Center报告本地玩家的进度。
 通过将进度存储在Game Center上,您还可以在Game Center应用程序中显示玩家的进度。
 请参阅向Game Center报告成就进度。
- 添加代码以从Game Center加载本地玩家先前的成就进度。
 本地玩家的进度应在玩家成功认证后不久加载。
 请参阅加载成就进度。
设计成就
成就的设计开头是对成就目标的简单陈述。
 以下是一些可以定义游戏的示例目标:
- 捕获10个海盗。
- 击败1个boss
- 找到1000个金币。
- 在5场比赛中获得第一名。
- 在5个不同的赛道上获得第一名。
目标陈述描述了一个触发条件(“在不同的赛道上获得第一名”)和一个数量(5)。
 触发条件是必不可少的,因为它定义了你的游戏逻辑需要跟踪的东西。
 在比赛示例中,你的游戏必须在每场比赛结束时检查胜利条件,但它也需要记录玩家击败了哪些赛道。
数量通常决定了玩家任务的粒度。
 对于一些成就,数量是无关紧要的;成就要么是赚来的,要么不是赚来的。
 例如,“击败最终游戏老板”不是玩家通常可以在完成时获得部分信用的目标。
 相比之下,“捕获10个海盗”的目标是一个更精细的任务。
 你的游戏需要跟踪捕获的海盗数量,并使用这个计数向游戏中心报告进度。
当你设计一个成就时,考虑一些其他特征:价值、可见性和可重复性。
 这些特征影响的不仅仅是游戏的设计;稍后,当你在iTunesConnect中定义你的成就时,你将根据这些特征设置属性:
- 成就的价值是衡量你认为玩家获得成就有多难的标准。
 有些成就很容易在几分钟的游戏中获得。
 困难的成就可能需要数小时的专注游戏,或者需要玩家发展高级游戏技能或策略来获得成就。
- 成就的可见性决定了玩家是否可以在游戏开始时看到成就,或者是否必须在游戏过程中发现成就。
 默认情况下,成就对玩家是可见的。
 这很有帮助,因为玩家可以扫描可用成就列表,看看哪些动作可以获得奖励。
 然而,如果隐藏,一些成就可能更有用。
 例如,如果您的游戏包含一个故事或情节,那么在游戏开始时列出所有成就可能会向玩家揭示太多的故事。
 对故事至关重要的成就可以标记为隐藏,以便您可以选择玩家何时看到它们。
- 成就通常是不可重复的。
 获得成就后,玩家不会看到关于该成就的进一步消息。
 如果一项成就被标记为可重复,那么每次你的游戏报告玩家完成该成就时,玩家都会看到相应的奖励横幅。
对于你创造的成就,你有很大的创造性空间。
 以下是一些设计成就的常见策略,可以指导你完成整个过程。
创造成就,展示玩家在游戏中可以做的不同事情
当有经验的玩家购买新游戏时,他们会检查成就列表,看看这些游戏中有什么可能。
 你的成就列表应该强烈传达你希望玩家在玩游戏时思考或做什么。
 你希望成就提供玩家可以努力实现的各种不同目标。
 当你提供各种目标和成就时,玩家会继续玩你的游戏。
创造需要不同技能或奉献水平的成就
为新玩家和有经验的玩家——以及介于两者之间的玩家——提供成就。
 成就是对玩家玩游戏的奖励。
 如果你游戏的所有成就都太容易被击败,奖励就会显得很便宜,玩家也不会感到受到挑战。
 另一方面,如果所有成就都太难获得,那么玩家可能不会经常得到奖励来继续玩你的游戏。
 努力包括学习如何玩游戏时可以获得的成就,以及奖励玩家学习如何玩好你的游戏的成就。
加入更难的挑战的另一个原因是它们鼓励玩家在挑战其他玩家时使用它们。
 参见挑战。
为游戏中的不同部分或模式创建成就
出于各种原因,您可能会选择将游戏划分为较小的游戏或游戏模式。
 一些原因包括:
- 不同的游戏规则集或规则变体(夺旗、生存)
- 不同的游戏关卡或地图
- 一种游戏,其基本故事被分成不同的行为或故事弧
如果你的游戏确实将其游戏划分为较小的迷你游戏,定义每个游戏的成就,因为它鼓励玩家尝试每种游戏模式或完成你的游戏。
使用隐藏的成就来取悦和奖励玩家
隐藏的成就应该谨慎使用。
 通常,你想让玩家知道在你的游戏中对他们的期望,这样他们就可以积极地为自己设定目标。
 隐藏的成就不能用来创造新的挑战。
 然而,隐藏的成就可能是一个让玩家意想不到的好机会。
 例如,以下任何一个地方都可能是隐藏成就可能有所帮助的地方:
- 情节或故事中意想不到的元素。
- 玩家在疯狂的特技中成功。
- 你可以将传统从玩家努力获得的成就转变为更不光彩的成就,当他们失败时获得的。
 这个成就代表了一种将幽默注入情境的方式。
 例如,一个叫做热脚的成就可能会在玩家掉进熔岩20次后出现。
为游戏的后续版本节省一些预算
你可以在游戏中创造多少成就以及奖励玩家多少积分是有限制的:
- 每个游戏最多可以有100个成就。
- 每场比赛最多可奖励1000分。
- 个人成就不得超过100分。
避免将所有预算都花在游戏的初始版本上。
 节省一些预算来支持更新和新内容。
配置iTunesConnect中的成就
当您准备好添加成就时,您首先在iTunes连接中定义它们。
 对于每个成就,您提供表4-1中列出的所有属性的值。
 此外,对于您计划将成就本地化为的每种语言,您提供表4-2中列出的所有属性的数据。
Table 4-1 Achievement properties
| Property | 描述 | 
|---|---|
| 成就参考名称 | 您必须为每个成就提供的内部名称,仅在iTunes连接中使用。 如果您在iTunes连接中搜索成就,将使用此名称。 | 
| 成就ID | 为您的成就选择的字母数字标识符。 此ID限制为100个字符。 您的成就ID是永久设置,因此不能在以后编辑。 成就ID在您的游戏中使用,详情可见。 | 
| 点值 | 你的成就值多少分。 | 
| 隐藏 | 标记为隐藏的成就在游戏中心对玩家保持隐藏,直到您第一次报告该成就的进度。 | 
| 不止一次的成就 | 表示用户是否可以多次获得成就。 | 
Table 4-2 Achievement language properties
| Property | 描述 | 
|---|---|
| 语言 | 您希望这项成就出现的语言。 | 
| 标题 | 此成就的本地化标题,因为您希望它出现在Game Center中。 | 
| 预先获得的描述 | 游戏中心玩家在获得成就之前对其成就的描述。 | 
| 赢得描述 | 游戏中心玩家在获得成就后对其成就的描述。 | 
| 图像 | 表示成就的本地化图像。 图像必须是 .jpeg、.jpg、.tif、.tiff或.png文件,其大小为512 x 512或1024 x 1024像素,至少72 dpi,并且在RGB色彩空间中。此属性是必需的。 | 
有关在iTunes连接中设置排行榜的更多信息,请参阅成就。
为您的游戏添加成就支持
当您准备好在游戏中实现成就时,您可以使用表4-3中列出的类来实现。
| 类名 | 描述 | 
|---|---|
| GKAchievement | 保存有关玩家完成成就的进度的信息。 您的游戏创建 GKAchievement对象以向Game Center报告进度。Game Kit还可以加载存储在Game Center上的当前进度,并使用 GKAchievement对象将其返回到您的游戏。 | 
| GKAchievementDescription | 保存成就的本地化文本和图像。 成就描述的数据在运行时从Game Center检索,并基于您在iTunesConnect创建成就时提供的数据。 | 
| GKGameCenterViewController | 提供标准用户界面以向玩家显示Game Center内容。 此内容包括成就页面。 | 
| GKAchievementViewController | 提供标准用户界面以显示成就页面。 如果 GKGameCenterViewController类可用,则应改用该类。 | 
向游戏中心报告成就进度
每当玩家完成一项成就时,您的游戏都应该向Game Center报告进度。
 通过在Game Center中存储进度信息,您可以获得一些好处:
- Game Center应用程序可以显示玩家取得成就的进度。
- 如果一个玩家有多台设备,你在另一台设备上的游戏可以检索玩家的成就进度。
您可以使用percentComplete属性存储用户的成就进度。
 当您向Game Center报告进度时,会发生两件事:
- 如果该成就之前被隐藏,则会显示给玩家。
 即使你的玩家在成就上没有取得任何实际进展(百分比为0.0),该成就也会被显示。
- 如果报告值高于之前报告的成就值,游戏中心上的值将更新为新值。
 玩家永远不会失去成就的进度。
当进度达到100%时,成就被标记为完成,当玩家查看成就屏幕时,图像和完成描述都会出现。
无论是报告单个成就的进度还是同时报告多个成就的进度,当发生错误时,您的游戏很少需要执行任何特定操作。
 如果发生错误,例如网络不可用,Game Kit会在适当的时间自动重新发送数据。
报告成就进展的最佳做法
在设计一款使用Game Center成就的游戏时,您需要在响应玩家需求和使用尽可能少的设备资源的目标之间取得平衡。
 考虑到这两个概念,请考虑以下做法:
- 玩家取得进展后立即报告成就的进度。
 不要延迟报告直到以后;如果玩家获得成就,横幅应该立即显示。
- 仅当玩家实际取得进一步进展时才报告进度。
 如果玩家没有取得进展,请勿向Game Center报告更改,因为这会消耗网络资源而无法实际更改Game Center上数据的状态。
- 如果您的游戏在玩家获得成就时显示自定义横幅或指示器,请在向Game Center报告成就之前将showsCompletionBanner属性设置为NO。
加载成就进度
通过调用loadAchievementsWithCompletionHandler:方法从Game Center加载本地玩家的当前进度信息。
 如果操作成功完成,它将返回GKAchievement对象的数组,每个对象代表您之前报告的游戏进度。
重置成就进度
您可能希望允许玩家重置游戏中成就的进度。
 有关如何重置玩家进度的信息,请参见resetAchievementsWithCompletionHandler:。
当您的游戏重置玩家的成就进度时,所有进度信息都将丢失。
 隐藏的成就,如果之前显示过,将再次隐藏,直到您的游戏报告它们的进度。
 例如,如果这些成就最初被隐藏的唯一原因是它们与应用内购买相关联,那么您将再次显示这些成就。
2、排行榜
许多游戏提供评分系统来衡量玩家在游戏中的表现。
 分数不仅仅是玩家衡量自己进步的一种方式;它们还为玩家提供了一种将自己的技能与其他玩家进行比较的方法。
 在游戏中心,排行榜是分数数据的数据库。
 你的游戏将分数发布到排行榜上,这样玩家以后就可以查看这些分数。
当您将排行榜添加到游戏时,您可以定义分数在游戏中的含义以及分数数据的显示格式。
支持排行榜清单
要将排行榜添加到您的游戏中,您需要采取以下步骤:
- 在添加排行榜之前,添加代码以验证本地玩家。
 请参阅在Game Center中与玩家合作。
- 决定如何在游戏中使用排行榜。
 您可以选择使用多少排行榜以及每个排行榜如何解释其分数数据。
 您可以为每个排行榜设计不同的评分机制。
 请参阅排行榜需要评分机制。
- 转到iTunes连接并为您的游戏配置排行榜。
 对于每个排行榜,您可以配置记录的分数类型以及分数的显示格式。
 排行榜格式可以针对不同的语言和地区进行本地化。
 请参阅在iTunes连接中使用排行榜。
- 添加代码以向Game Center报告分数。
 请参阅向Game Center报告分数。
- 添加代码以向本地播放器显示排行榜。
 请参阅使用默认排行榜。
 或者,您可以从Game Center检索分数数据,并使用它来创建您自己的自定义排行榜用户界面。
 请参阅检索分数数据。
排行榜需要评分机制
要在游戏中加入排行榜,你必须有一个游戏机制,允许你的游戏计算分数。
 你可以随心所欲地设计你的游戏玩法和得分机制。
 唯一的限制是你的得分机制必须返回一个64位的整数值。
 当你报告分数时,你把这个整数值报告给游戏中心,这样它就可以被存储起来。
如何将整数变成更有趣的东西?作为开发过程的一部分,您可以在iTunesConnect中配置排行榜描述,以便Game Center了解排行榜中的分数含义。
 您向Game Center描述排行榜中存储的分数类型以及如何将您的分数转换为字符串以供显示。
 这种机制的一个优点是Game Center应用程序使用相同的描述来显示您的游戏分数。
最关键的决策是在排行榜中存储什么样的分数,Game Center提供了三种基本的格式化类型:
- 抽象数,例如整数或定点数。
- 时间值,例如分钟或秒。
- 货币价值,如美元或欧元。
你还可以决定分数的排序顺序。
 当分数从低到高排序时,较低的分数被认为是更好的分数——例如,记录完成比赛所需时间的竞速游戏。
 在这种情况下,较快的时间——即较低的分数——更好,因此从低到高的排序顺序是合适的。
 相比之下,为玩家在游戏中的每个成功动作增加分数的游戏期望得分越高越好,因此从高到低的排序顺序会更合适。
选择格式类型和排序顺序后,您还可以使用本地化格式字符串自定义最终结果。
 例如,如果您选择将分数表示为整数,您可以选择“point”和“point”作为存储在该排行榜中的分数的单数和复数值的英文本地化。
 其他游戏可能使用相同的分数类型,但使用不同的本地化字符串(“圈”、“汽车”)。
当你设计一个评分机制时,确保你考虑了可能的(合法的)分数值的范围。
 当你创建排行榜描述时,你也可以为报告给它的分数提供极小点和最大值。
 提供分数范围给你的排行榜增加了一层安全性,因为它减少了玩家在发现一种方法来报告荒谬的高分时作弊的似然性。
一个游戏可以有多个排行榜
如果您的游戏不支持排行榜集,您可以为游戏(或游戏组)定义多达100个不同的排行榜。
 当排行榜集已启用时,每场游戏允许的不同排行榜数量增加到500个排行榜。
 有关详细信息,请参阅排行榜集。
 每个排行榜都有自己的分数类型、排序顺序和格式信息。
 当您实现多个排行榜时,您可以选择在每个排行榜中记录什么样的分数数据,这本质上意味着每个排行榜可能有自己的评分机制,如果需要的话。
以下是如何在游戏中使用不同排行榜的几种可能性:
- 具有多个难度级别的游戏可以为每个难度级别设置不同的排行榜。
- 具有不同地图(关卡、曲目)的游戏可以为每个地图设置不同的排行榜。
- 具有不同游戏模式(规则集)的游戏可以为每个游戏模式设置不同的排行榜。
- 您可以创建多种评分机制,以不同的方式评估玩家的技能,并为每个机制创建不同的排行榜。
 例如,竞速游戏可能会评估玩家的最佳赛道时间、最佳圈速时间,以及基于玩家在赛道上驾驶时漂移程度的分数。
 以不同的方式评估玩家可以让玩家找到多种方法来提高他们的技能。
为了让排行榜在你的游戏中有所区别,你给每个排行榜分配一个排行榜ID。
 你的游戏使用排行榜ID向适当的排行榜报告分数。
 例如,如果你的游戏包含三个不同难度的排行榜,你可以使用myGame.easy、myGame.normal和myGame.hard作为排行榜ID。
一个排行榜是默认排行榜
当您在iTunes连接中定义排行榜时,一个排行榜被指定为默认排行榜。
 当您的游戏在没有指定排行榜的情况下执行排行榜任务时,会自动选择默认排行榜。
默认排行榜的初始值是您在iTunesConnect中分配的值。
 但是,当玩家玩游戏时,您可以为该玩家设置不同的默认排行榜。
 您可以在报告新分数时自动更改默认排行榜,也可以独立于分数报告更改默认排行榜。
 例如,如果您的游戏有多个连续关卡和每个关卡的排行榜,您可以在本地玩家每次进入更高的关卡时更改默认排行榜。
 这样,未来的分数(或显示的排行榜)会自动显示玩家玩过的最佳关卡。
组合排行榜从多个单一排行榜收集分数
组合排行榜是一种特殊类型的排行榜,它将报告给多个排行榜的分数组合成一个排行榜。
 单个排行榜继续存在,是您报告分数的地方。
 您不向组合排行榜报告分数。
 例如,如果您的游戏提供多个赛道,您可以为每个赛道创建一个排行榜,并创建一个组合排行榜,显示在任何赛道上获得的最佳时间。
在决定是否为您的游戏设置一个或多个组合排行榜时,请牢记以下准则:
- 只有非组合排行榜才能包含在组合排行榜中。
 也就是说,您不能将其他组合排行榜组合成组合排行榜。
- 非组合排行榜只能附加到一个组合排行榜。
- 附加到组合排行榜的非组合排行榜必须具有相同的得分格式类型和排序顺序。
 实际上,它们应该在你的游戏中共享相同的得分机制。
在iTunesConnect中使用排行榜
在你考虑了评分机制的设计之后,你可以去iTunesConnect精确定义每个排行榜。
 定义排行榜是关键的一步;一旦你的游戏发布给客户,排行榜的许多部分就无法更改。
 重要的是要注意,你输入iTunesConnect的信息必须拟合你的游戏的实现。
 因此,你应该在创建排行榜时保持谨慎,并在发布之前彻底测试你的游戏。
有关管理游戏排行榜和排行榜集的完整详细信息,请参阅 Game Center配置指南foriTunesConnect 。
 但是,为了方便起见,表4-4描述了用于定义单个排行榜的属性。
 表4-5列出了您为计划将游戏本地化为的每种语言设置的属性。
Table 4-4 Properties for a single leaderboard
| Property | 描述 | 
|---|---|
| 排行榜参考名称 | 排行榜的内部名称,仅在iTunes连接中使用。 这是您用于在iTunes连接中搜索排行榜的名称。 | 
| 排行榜ID | 为您的排行榜选择的字母数字标识符。 此ID限制为100个字符。 您的排行榜ID是永久设置,以后无法编辑。 | 
| 分数格式类型 | 选择您希望在排行榜中表示分数的格式类型-例如,整数、经过的时间或金钱。 | 
| 排序顺序 | 在“从低到高”或“从高到低”之间进行选择以显示排行榜分数。 如果您希望最先显示最低分数,请选择“从低到高”。 如果您希望最先显示最高分,请选择“从高到低”。 | 
| 分数范围 | 使用64位有符号整数定义分数范围。 这些值必须介于long min(-2^63)和long max(2^63-1)之间。 超出此范围的任何分数都将被删除。 分数范围值是可选的,但如果添加它们,则必须设置两个值,并且它们不得相等。 当首次添加分数范围时,或将来将其更改为限制数据的较小范围时,范围之外的所有数据都将丢失并且无法恢复。 | 
Table 4-5 Leaderboard language properties
| Property | 描述 | 
|---|---|
| 语言 | 这是您的排行榜显示的语言。 | 
| 姓名 | 出现在标准排行榜用户界面中的排行榜名称。 | 
| 分数格式 | 此属性确定当排行榜以指定语言显示时,您的分数如何显示。 例如,如果您的排行榜将分数存储为金钱,您可能需要根据您选择的语言指定不同类型的金钱。 此属性的允许值基于您的分数格式类型。 | 
| 分数格式后缀(单数) | 此后缀添加到以单数形式显示的分数的末尾。 此后缀是可选的,但对于阐明存储在排行榜中的分数类型很有用。 示例包括“点”和“命中”。 | 
| 分数格式后缀(复数) | 此后缀被添加到以复数形式显示的分数的末尾。 此后缀是可选的,对于阐明排行榜中存储的分数类型很有用。 示例包括“积分”、“硬币”和“点击”。 | 
| 图像 | 表示排行榜的本地化图像。 此属性是可选的;如果指定,图像将显示为标准排行榜用户界面的一部分。 图像必须是 .jpeg、.jpg、.tif、.tiff或.png文件,为512 x 512或1024 x 1024像素,至少72 dpi,RGB色彩空间。 | 
有关在iTunes连接中设置排行榜的更多信息,请参阅排行榜和排行榜集。
向游戏中心报告分数
您的游戏通过创建GKScore对象数组来传输分数到Game Center,无论您是报告单个分数还是多个分数。
 分数对象具有获得分数的玩家的属性、获得分数的日期和时间、应该报告分数的排行榜标识符以及获得的实际分数。
 您可以为每个分数分配并初始化一个新的分数对象,配置它们的属性,然后调用reportScores:withCompletionHandler:方法将数据发送到Game Center。
分数对象使用它向其报告分数的排行榜的排行榜ID初始化,然后您的分数报告方法将值属性设置为玩家获得的分数。
 排行榜ID必须是您在iTunes连接中配置的排行榜的标识符。
 获得分数的玩家和获得分数的时间在创建分数对象时自动设置。
 始终为本地玩家报告分数。
您的游戏应该创建分数对象,并在获得分数后立即向游戏中心报告分数。
 这会准确地设置日期和时间,并确保正确报告分数。
 如果由于某种原因,由于网络错误而无法报告分数,Game Kit会在网络可用时自动重新发送数据。
安全和分数报告
当你设计一款向Game Center报告分数的游戏时,你还应该考虑游戏的安全需求。
 你希望向Game Center报告的分数能够准确地记录玩家的表现。
 这里有两个建议:
- 以安全格式存储您的游戏偏好和保存的游戏,而不是明文。
 如果您的游戏数据以明文形式存储,玩家可以使用iTunes下载保存的游戏数据,对其进行修改,并将其重新同步回设备。
 这可能会让玩家获得比您预期的更高的分数。
- 始终为排行榜设置合理的极小点和最大值。
使用默认排行榜
默认排行榜是在iTunes连接中创建排行榜时设置的。
 但是,默认排行榜也是每个玩家的设置;当玩家玩游戏时,您可以更新显示给该玩家的默认排行榜。
更新默认排行榜最常见的时间是在您报告分数时,Game Kit提供了一种方便的方法来设置默认排行榜。
 创建分数对象,然后将其shouldSetDefaultLeaderboard属性设置为YES。
 报告分数时,分数对象leaderboardIdentifier属性中的值成为新的默认排行榜ID。
但是,您也可以使用GKLocalPlayer对象直接通过调用setDefaultLeaderboardIdentifier:completionHandler:方法来更新默认排行榜。
显示标准排行榜
除了将分数发送到Game Center之外,您还应该允许玩家查看游戏中的分数。
 最简单的方法是使用GKGameCenterViewController对象。
 您可以调整Game Center视图控制器的行为,使其显示的初始内容是排行榜页面。
 配置其他属性以显示排行榜页面,仅显示最后一天获得的分数。
 然后显示视图控制器。
 在OS X中,您使用GKDialogController类来显示视图控制器,如显示游戏中心用户界面元素中所述。
当玩家完成查看排行榜时,将调用委托。
 覆盖gameCenterViewControllerDidFinish:方法并关闭显示的视图控制器。
检索分数数据
有时您希望从Game Center检索分数数据。
 下载分数数据的最常见原因是当您希望创建自定义排行榜用户界面时。
 您的游戏使用GKLeaderboard对象来检索分数数据。
 在这种情况下,排行榜对象表示对存储在Game Center上的数据的查询。
 您将GKLeaderboard对象上的属性设置为滤波器哪些分数返回到您的游戏,然后告诉对象加载分数。
检索存储在Game Center上的排行榜数据子集有以下步骤:
- 从存储在排行榜中的所有分数的集合开始。
- 丢弃任何不拟合playerScope、timeScope和identifier属性的分数。
- 只保留每个玩家的最佳剩余分数。
- 从最好到最差对分数列表进行排序。
- 返回range属性请求的分数子集。
图4-1显示了一个可能搜索的示例。
 在这个示例中,超过一天的分数被忽略。
 然后,从剩余的分数集中,我们看到玛丽有两个分数,所以只保留他们的最佳分数。
 最后,分数被排序和排名。
 然后将选择的范围返回到游戏中。
图4-1排行榜数据被过滤、排序并返回到您的游戏中
 
如果您提供与组合排行榜匹配的排行榜标识符,则在过滤和排序发生之前,单个排行榜中的所有分数都会汇集在一起。
 图4-2显示了与前面示例中相同的分数,但现在您可以看到分数实际上存储在构成组合排行榜的两个不同排行榜中。
图4-2组合排行榜包括来自单个排行榜的分数

需要注意的是,组合排行榜是在iTunesConnect中设置的,被认为是唯一的排行榜。
 您无法确定组合排行榜的分数来自应用程序内部的位置。
 如果您想显示该信息,您需要通过每个排行榜的排行榜ID单独查询每个排行榜,并手动确定用于创建组合排行榜的分数。
使用分数上下文扩展排行榜
虽然分数和格式系统允许您创建有趣的排行榜,但您可能希望扩展排行榜系统以包含特定于您的游戏的其他信息。
 您可以使用分数上下文来执行此操作。
 GKScore对象具有一个包含64位整数的context属性。
 该属性中存储的值与分数一起存储。
 Game Center根本不使用此值,也不影响分数格式。
 您的游戏可以自由使用此整数来存储您想要的任何信息。
以下是几种可能性:
- 您可以使用context属性来存储提供附加内容的标志。
 例如,在竞速游戏中,使用标志来存储该人使用哪种汽车完成赛道。
 然后,在您自己的自定义排行榜用户界面中,您可以在每个分数旁边显示适当的汽车图像。
- 将用户的操作和其他数据记录到文件中。
 然后,您可以设计游戏引擎来重放该文件的内容。
 您将文件存储在自己的服务器上,并使用上下文字段作为索引来引用该文件。
 然后,您的自定义排行榜用户界面可以提供查看某人是如何获得分数的能力。
- 您可以直接在游戏中实现重放。
 例如,在假设的竞速游戏中,您可以使用玩家的重放文件显示一辆幻影汽车,供当前玩家与之比赛。
3、排行榜集
排行榜集为开发人员提供了将多个排行榜组合成一个组的能力。
 iOS开发人员能够从iOS7开始创建排行榜集。
 OS X v10.9不支持排行榜集。
下面的例子说明了为什么你想在游戏中加入排行榜集。
- 创建的游戏有几个不同的世界,每个世界都包含几个排行榜——例如,收集到的大多数硬币、获得的最高分数和捕获的大多数敌人的排行榜。
- 开发人员将不同的排行榜组合成一个针对每个世界的排行榜集。
- 用户可以看到显示的世界排行榜集列表。
 打开一个集合会显示该集合中包含的排行榜。
重要提示:在您决定使用排行榜集后,每个排行榜都必须放置在排行榜集中。
 在支持排行榜集的游戏中,您不能在排行榜集之外拥有排行榜。
图4-3显示了每个世界的排行榜组合成一个排行榜集。
图4-3排行榜组合成排行榜集
 
支持排行榜集的清单
要将排行榜集添加到您的游戏中,您需要采取以下步骤:
- 在添加成就之前,添加代码以验证本地玩家。
 请参阅在Game Center中与玩家合作。
- 确保你的游戏至少包含一个排行榜。
 有关创建排行榜的信息,请参阅排行榜。
- 转到iTunes连接并为您的游戏配置排行榜集。
 对于每个排行榜集,将所需的排行榜添加到排行榜集。
 请参阅iTunes连接中使用排行榜集。
- 添加代码以加载与当前游戏关联的排行榜集。
 请参阅将排行榜集支持添加到您的游戏。
一个游戏可以有多个排行榜集
您可以为您的游戏定义多达100个不同的排行榜集。
 每个排行榜集最多可包含100个排行榜,最多可包含500个排行榜。
 您可以选择在每个排行榜集中放置哪些排行榜。
 不使用排行榜集的游戏仅限于100个排行榜。
以下是在游戏中使用不同排行榜集的几种可能方法:
- 一个对每个世界都有多种评分机制的游戏(得分、捕获的敌人、收集的硬币)可以将每个世界的排行榜组合成一个排行榜集。
- 一个对每个世界都有多种评分机制的游戏(得分、捕获的敌人、收集的硬币)可以将所有排行榜组合成一个单一的评分机制。
- 具有多个可玩角色(战士、弓箭手)的游戏可以将特定角色的所有排行榜组合成一个排行榜集。
只要不超过单个最大值,您可以以任何格式组合排行榜和排行榜集的数量。
 以下都是允许的组合:
- 5套排行榜,每套包含100个排行榜
- 100套排行榜,每套包含5个排行榜
- 3套排行榜,每套包含50个排行榜;2套排行榜,每套包含100个排行榜;1套排行榜包含1个排行榜
游戏组排行榜和排行榜设置限制
组内的排行榜和排行榜集的数量仅受组内应用程序数量的限制。
 组内的每个游戏都可以有多个排行榜集和排行榜,如一个游戏可以有多个排行榜集。
 例如,如果一个组内部有三个应用程序,那么该组总共可以有300个排行榜集和1500个排行榜。
 但是,单个应用程序仍然限制为100个排行榜集和500个排行榜。
在iTunesConnect中使用排行榜集
决定在游戏中使用排行榜集后,您必须在iTunes连接中配置排行榜集。
 在iTunes连接中,您可以创建新的排行榜集并将排行榜移入其中。
 有关管理游戏排行榜和排行榜集的完整详细信息,请参阅排行榜和排行榜集。
为您的游戏添加排行榜集支持
要在游戏中实现排行榜集支持,您可以使用表4-6中列出的类。
| 类名 | 类函数 | 
|---|---|
| GKScore | 一个 GKScore对象包含玩家获得的分数信息。您的游戏创建 GKScore对象以将分数发布到Game Center的排行榜。当游戏从排行榜检索分数信息时,这些分数将作为 GKScore对象返回。 | 
| GKLeaderboard | 一个 GKLeaderboard对象提供有关排行榜的信息,包括其排行榜ID和标题。当您想从排行榜中检索分数数据时,您还可以创建 GKLeaderboard对象。如果您想创建自己的自定义排行榜用户界面,您通常会这样做。 | 
| GKLeaderboardSet | 一个 GKLeaderboardSet对象提供有关游戏中包含的排行榜集的信息。每个集合包含分配给该集合的1到100个 GKLeaderboard对象。 | 
| GKGameCenterViewController | 这个 GKGameCenterViewController类提供了一个标准的用户界面来向玩家显示Game Center的内容。 | 
| GKLeaderboardViewController | 该 GKLeaderboardViewController类提供了显示排行榜的标准用户界面。如果 GKGameCenterViewController类可用,则应改用该类。 | 
在加载集合中包含的排行榜之前,您必须加载集合本身。
 从Game Center检索游戏的排行榜集列表并显示它们的标题。
 使用loadLeaderboardSetsWithCompletionHandler:方法检索应用的排行榜集。
 然后,您可以使用loadLeaderboardsWithCompletionHandler:方法加载特定排行榜集的排行榜。
4、挑战
成就和排行榜都允许玩家衡量他们在玩游戏时的进度。
 但是玩家想互相测试他们的进度也很常见。
 击败朋友的分数比仅仅获得一个体面的分数更令人满意。
 游戏中心以挑战的形式接受了这一想法。
 游戏中心的玩家可以挑战其他游戏中心成员来击败赢得的分数或成就。
 挑战代表了一种不同风格的多人游戏体验,玩家间接地相互竞争。
当一个玩家向另一个玩家发出挑战时,会向被挑战的玩家发送消息推送。
 被挑战的玩家可以接受或拒绝挑战。
 如果玩家接受挑战,挑战将被放在与该玩家相关的挑战列表中。
 稍后,如果玩家击败了挑战,游戏中心会通知被挑战的玩家和挑战者挑战完成。
Game Center支持两种挑战:
- 分数挑战是根据挑战者先前获得的排行榜分数发出的。
 当被挑战的玩家获得更好的分数时,挑战就完成了。
 当被挑战的玩家击败分数挑战时,游戏中心会自动向原始挑战者发出新的分数挑战。
 分数挑战继续在两个玩家之间来回传递,因为他们努力击败对方的分数。
- 成就挑战是从挑战者已经完成的成就发出的。
 当被挑战的玩家完成成就时,挑战就完成了。
如果您的游戏支持成就或排行榜,它会自动支持挑战,而无需任何额外代码。
 在这种情况下,玩家会前往Game Center应用程序挑战他们的朋友。
 但是,您也可以自定义您的游戏以直接支持挑战:
- 你可以允许玩家从你的游戏中挑战其他玩家。
- 您可以检索本地玩家的开放挑战列表。
- 当玩家点击横幅以迎接新挑战时,您的游戏可以收到通知。
- 您的游戏可以在玩家收到或完成挑战时接收通知,您甚至可以在您的游戏运行时阻止挑战横幅显示,让您完全控制您的游戏收到挑战事件时显示的用户界面。
- 您可以通过将GKShowChallengeBanners添加到Info.plist来确定您的游戏是否在游戏内显示挑战横幅。
 详情请参阅可可键。
支持挑战清单
要为您的游戏添加挑战,您需要采取以下步骤:
- 首先,为游戏添加对排行榜或成就的支持。
 请参阅排行榜和成就。
- 或者,添加代码以允许玩家在游戏中发送挑战。
 请参阅从您的游戏中发出挑战
- 或者,添加代码以接收挑战通知并采取行动。
 请参阅从您的游戏发出挑战。
每种类型的挑战都由GKChallenge的不同子类定义。
 表4-7列出了子类以及如何检索向玩家显示挑战所需的剩余信息。
| 子类 | 描述 | 
|---|---|
| GKScoreChallenge | 读取 score属性以获得表示用于生成挑战的分数的分数对象。 | 
| GKAchievementChallenge | 读取 achievement属性以获得表示完成挑战的成就的成就对象。 | 
从你的游戏发出挑战
您可以在游戏中使用GKScore对象或GKAchievement对象发出挑战。
 两个类都实现了具有相似签名的issueChallengeToPlayers:message:方法。
 发出挑战不会向发出挑战的玩家显示用户界面;这是您需要自己实现的代码。
 以下部分显示了一系列可以用来发出成就挑战的方法。
基于分数的挑战的设计类似。
 创建一个分数对象,提交分数,然后使用它来生成挑战请求。
显示挑战用户界面
例4-1开始挑战过程。
 当玩家完成一项成就时调用此方法。
 代码所做的第一件事是创建一个新的成就对象,告诉Game Center成就已经完成。
 如果玩家一次完成一项挑战的多个成就,则为每个成就创建一个新对象,并将成就添加到数组中。
 请注意,挑战只能在完成的成就上执行。
 它禁用了成就的默认完成横幅,因为它计划改为显示自定义挑战屏幕。
 该方法向Game Center报告进度,然后触发segue以显示游戏的自定义挑战用户界面。
例4-1显示挑战用户界面
- (void) bossMonsterDefeated
{
    GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:@"MyGame.bossDefeated"];
    achievement.percentComplete = 100.0;
    achievement.showsCompletionBanner = NO;
 
    [achievement reportAchievements: [NSArray arrayWithObjects:achievement, nil] WithCompletionHandler:NULL];
    [self performSegueWithIdentifier:@"achievementChallenge" sender:achievement];
}
显示和关闭挑战视图控制器
例4-2显示了发布视图控制器的prepareForSegue:sender:方法。
 当该方法检测到这是显示成就用户界面的segue时,它将发布视图控制器设置为委托。
 成就对象也提供给挑战视图控制器,以便它可以自定义其用户界面以拟合获得的成就。
例4-2准备挑战视图控制器
- (void )prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"achievementChallenge"])
    {
        MyAchievementChallengeViewController* challengeVC = (MyAchievementChallengeViewController*) segue.destinationViewController;
        challengeVC.delegate = self;
        challengeVC.achievement = (GKAchievement*) sender;
    }
}
例4-3显示了发出控制器对委托方法的实现。
 当用户解除挑战视图控制器时,将调用委托。
 委托方法采用一个参数,该参数说明玩家是否发出了挑战。
 如果玩家发出了挑战,则该方法从挑战视图控制器检索玩家列表和消息并发出挑战。
 请注意,这种设计将发出挑战的控制权交给了原始视图控制器。
 如果您的设计不需要这种额外的灵活性,您可以简单地直接从挑战视图控制器发出挑战。
例4-3关闭挑战视图控制器
- (void) challengeViewController:(MyAchievementChallengeViewController*)controller wasDismissedWithChallenge:(BOOL)issued
{
    [self dismissViewControllerAnimated:YES completion:NULL];
    if (issued)
    {
        [controller.achievement issueChallengeToPlayers:controller.players message:controller.message];
    }
}
发布挑战的最佳实践
本地玩家必须始终控制发出挑战的时间和方式。
 在玩家没有首先批准挑战的情况下,您的游戏绝不能代表玩家发出挑战。
 当您实现挑战的自定义用户界面时,您必须确保玩家可以选择不发出挑战。
 即使当玩家发出挑战时,玩家也需要控制谁收到挑战以及向这些玩家显示什么消息。
理想情况下,你的游戏应该提供符合玩家期望的默认行为。
 默认消息和玩家列表应该是玩家可能做出的合理选择。
 例如,当玩家想要发起分数挑战时,你的第一个冲动可能是让玩家的所有朋友都加入挑战。
 实际上,这可能不是最好的答案。
 玩家的一些朋友可能已经获得了更高的分数。
 相反,考虑类似清单4-4中的算法。
 它使用仅限朋友的滤波器搜索排行榜。
 然后过滤生成的朋友列表,以便只有没有获得高分数的玩家才会受到挑战。
示例4-4检索得分低于刚刚得分的玩家列表
- (void) challengeLesserMortalsForScore: (int64_t) playerScore inLeaderboard: (NSString*) leaderboard
{
    GKLeaderboard *query = [[GKLeaderboard alloc] init];
    query.leaderboardIdentifier = leaderboard;
    query.playerScope = GKLeaderboardPlayerScopeFriendsOnly;
    query.range = NSMakeRange(1,100);
    [query loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
        NSPredicate *filter = [NSPredicate predicateWithFormat:@"value < %qi",playerScore];
        NSArray *lesserScores = [scores filteredArrayUsingPredicate:filter];
        [self presentChallengeWithPreselectedScores: lesserScores];
    }];
}
然后,您的挑战视图控制器可以使用分数数据来填充其用户界面。
 例如,它可以使用嵌入在分数中的玩家标识符来加载每个被挑战玩家的姓名和照片。
 您可以使用selectChallengeablePlayers:withCompletionHandler:class方法对成就挑战执行类似的操作。
 例4-5显示了对此的可能实现。
例4-5确定可以完成成就挑战的玩家列表
- (void) challengePlayersToCompleteAchievement: (GKAchievement*) achievement
{
    [achievement selectChallengeablePlayers:[GKLocalPlayer localPlayer].friends withCompletionHandler:^(NSArray *challengeablePlayerI, NSError *error) {
        if (challengeablePlayers)
        {
            [self presentChallengeWithPreselectedPlayers: challengeablePlayers];
        }
    }];
}
六、保存游戏
在iOS设备之间保存和共享游戏数据可能很困难。
 当前的iCloud策略没有为开发者提供在设备之间保存游戏数据的良好解决方案。
 无论是要求开发者实现几个不同的API,还是限制他们可以保存的信息量,当前的iCloud策略都没有为开发者提供可行的解决方案。
 要在设备之间提供保存的游戏数据,开发者需要创建自己的解决方案,要么使用自己的服务器,要么使用辅助服务,如Facebook。
 使用在GKSavedGame、GKLocalPlayer和GKSavedGameListener类中找到的新保存游戏方法,开发者可以轻松地将游戏数据保存到iCloud。
1、有关已保存游戏的一般信息
目前,保存的游戏应用编程接口最好用于单人游戏或单个设备上的传球游戏。
 只有本地玩家需要保存游戏,任何设备都可以用来检索保存的游戏信息。
 基于回合的游戏已经有了保存状态,因为回合从一个玩家传递到另一个玩家,实时游戏要求所有玩家在玩游戏的任何时候都在场,只有一个玩家可以保存游戏。
在您的应用中实现已保存的游戏时,您必须牢记以下信息:
- 玩家必须拥有iCloud帐户才能保存游戏
- 对可以保存的数据量没有具体限制
- 如果玩家的iCloud帐户中没有空间,游戏将无法保存
- 该应用程序控制如何保存以及保存哪些数据
- 保存的游戏绑定到iCloud帐户,而不是Game Center帐户
表5-1显示了GKSavedGame对象使用的公共属性。
Table 5-1 Common saved game properties
| Property | 描述 | 
|---|---|
| deviceName | 创建已保存游戏文件的设备的名称。 | 
| modificationDate | 上次修改保存的游戏文件的日期。 | 
| name | 保存的游戏文件的名称。 | 
2、iCloud要求
使用保存的游戏API保存游戏需要用户拥有iCloud帐户。
 使用iCloud为用户提供了一种从任何具有公网连接的设备保存游戏的方法。
 除了提供从任何地方保存游戏的能力外,iCloud还为用户提供了保存大型数据文件的能力。
 保存的游戏文件的大小仅限于用户iCloud帐户中的空间量。
 但是,您应该始终努力最小化保存的数据量。
 这可以防止用户空间不足,并减少拉取数据或保存游戏文件所需的时间。
3、保存游戏
今天的游戏可以在多个设备上玩,这取决于用户碰巧在哪里。
 无论他们是在家玩iMac,还是坐在电视机前玩iPad,还是在总线上玩iPhone,玩家都想从他们上次离开的地方继续玩游戏,而不必担心他们在哪个设备上玩。
 包括保存和检索保存的游戏的能力大大提高了游戏的可玩性。
保存游戏数据
由您决定用户如何以及何时可以保存游戏。
 如果您希望玩家在执行该操作之前考虑每个操作,那么您可能希望只允许单个保存的游戏文件。
 但是,如果您不介意您的玩家回去尝试不同的操作,然后允许玩家命名和创建几个不同的保存游戏文件。
 在任何一种情况下,都由您为您的应用程序创建保存游戏机制。
玩家创建新保存的游戏文件后,使用saveGameData:withName:completionHandler:方法将游戏数据保存到iCloud。
 如果已经有同名保存的游戏对象,则新保存的游戏数据覆盖旧保存的游戏数据。
 否则创建并保存新的GKSavedGame对象。
4、检索已保存的游戏
玩家希望继续玩他们保存的游戏,无论他们是在当前设备上保存游戏还是在其他设备上保存游戏。
 要检索玩家从设备上保存的游戏列表,您的应用程序调用fetchSavedGamesWithCompletionHandler:方法来检索玩家的游戏列表。
 此方法返回包含每个已保存游戏的标识信息的GKSavedGame对象列表。
 在向玩家呈现已保存游戏列表之前,您必须确保所有已保存的游戏都没有相同的名称。
 如果多个已保存的游戏具有相同的名称,则必须在向玩家呈现已保存游戏列表之前解决名称冲突。
解决任何冲突后,向玩家显示已保存的游戏列表(如果适用)。
 然后玩家选择他们希望继续玩的已保存游戏文件。
 调用loadDataWithCompletionHandler:方法来检索与玩家选择的GKSavedGame对象相关的数据。
冲突保存的游戏
用户有多个设备,玩家同时在不同的设备上玩同一个游戏并不罕见。
 因此,有可能有多个保存的游戏具有相同的名称和来自不同的设备。
 由您的应用程序来查找任何冲突的游戏并解决冲突。
确定保存的游戏之间存在冲突后,需要为冲突的游戏创建一个只包含GKSavedGame对象的数组,然后将该数组发送到resolveConflictingSavedGames:withData:completionHandler:方法,该方法解决任何保存的游戏冲突并修改数组,使其不包含任何保存的游戏冲突。
七、配对概述
在Game Center中,当一群玩家想一起玩游戏时,就会形成拟合。
 每个玩家都在自己的设备上玩游戏,每个设备上的体验都是根据在该设备上玩游戏的玩家量身定制的,拟合作为一个整体代表共享体验。
 为了实现这一点,游戏的实例相互共享数据,以便就拟合的状态达成一致。
Game Center配对提供了必要的基础设施,允许玩家找到对玩拟合感兴趣的其他玩家。
 它允许玩家邀请特定的玩家——几乎总是朋友——或者简单地在你的游戏中找到寻找拟合的其他玩家。
 一旦这些玩家被发现并形成拟合,Game Center就可以让你轻松实现游戏所需的网络代码。
 必要时,Game Kit会通过Game Center的服务器路由网络数据,以便所有比赛的参与者都相互连接,无论他们在哪里或在什么样的网络上。
Game Center上的配对是一个复杂的话题,因为您可以创建许多类型的匹配以及创建它们的许多方法。
 本章概述了该过程。
 接下来的章节详细介绍了Game Center提供的特定类型的匹配。
1、游戏中心支持多种匹配
表6-1列出了Game Center匹配支持的不同种类的匹配,每种拟合都有不同的限制和能力。
| 拟合类型 | 描述 | 
|---|---|
| 实时的 | 在拟合期间,所有玩家都同时连接到Game Center。 Game Kit为 GKMatch类中的实时拟合提供了所有低级网络支持。您的游戏实现了自己的逻辑来坐在这个网络基础设施之上。 虽然旨在用于实时游戏,但它可用于任何需要玩家同时连接的游戏。 | 
| 回合制 | 拟合的状态存储在Game Center的服务器上,它在需要时传输给参与者,拟合的参与者之间没有连接,只有当他们想要读取或写入拟合数据时才连接到Game Center。 当玩家进行转弯时,他们的游戏实例会更新Game Center上存储的拟合数据。 Game Kit在 GKTurnBasedMatch类中提供了对这种匹配方式的支持。 | 
| 自托管 | 由您的游戏和您自己的服务器提供的网络实现。 当您创建托管拟合时,Game Center帮助您为您的拟合(配对)找到玩家,但依赖于您的游戏来实现自己的低级网络代码。 它主要用于已经有现有网络实现的游戏。 | 
2、Game Center提供多种方式将玩家连接到拟合中
Game Center让您的游戏尽可能轻松地将玩家连接到拟合中:
- 您的游戏可以呈现标准的配对用户界面,该界面允许玩家邀请朋友进行拟合,或者允许剩余的空槽由其他玩家搜索拟合来填补。
- 您的游戏可以通过编程方式邀请玩家或搜索其他等待玩家。
 在这种情况下,您负责实现自己的匹配用户界面,让玩家控制这个过程。
 当您想在匹配过程中添加自己的步骤或希望匹配用户界面符合您游戏的艺术风格时,您通常会使用这种方法。
- 玩家可以使用Game Center应用程序与其他玩家进行拟合。
即使游戏没有在玩家的设备上启动,玩家也可以被邀请加入拟合。
 当玩家收到邀请时,会向该玩家的设备发送消息推送。
 当玩家接受邀请时,游戏会立即启动以处理邀请。
 这种行为很重要,因为它增加了玩家玩你游戏的似然性。
 事实上,玩家可以被邀请为他们不拥有的游戏进行拟合。
 在这种情况下,玩家立即有机会购买和下载你的游戏。
3、配对和游戏是独立的任务
为拟合寻找玩家是一种不同于实际玩拟合的独特行为。
 在大多数情况下,匹配和游戏性发生在不同的时间。
 大多数游戏的典型行为是首先创建拟合,然后玩拟合。
 然而,其他场景也是可能的:
- 对于实时拟合,即使您搜索更多玩家,拟合也可能已经存在。
 在这种情况下,拟合与匹配过程同时存在,这意味着拟合参与者可以在发现其他玩家的同时交换数据。
- 对于基于回合的拟合,匹配不需要在拟合开始之前完成。
 相反,每当需要新玩家继续拟合时,匹配会自动执行以填补该位置。
4、你可以拟合不同版本的游戏
在iTunesConnect中,您可以提供兼容性信息,描述您的游戏的哪些版本在配对过程中可以看到对方。
 每个兼容的设备组和您的游戏版本都作为单独的组进行匹配。
 创建这些配对组的能力相当灵活:
- 当您创建游戏的新版本(例如bug修复)时,您可以声明新版本是否与旧版本兼容(特别是哪些版本)。
- 您可以创建不同的游戏(使用不同的捆绑标识符),并且仍然允许它们相互对抗。
- 您可以在OS X和iOS上创建不同的游戏,并将它们相互拟合。
重要提示:在每种情况下,iTunesConnect和Game Center提供的只是跨版本和产品创建这些更大匹配组的能力。
 Game Center本身不提供任何特殊代码来使您的游戏兼容。
 在实施您的代码时,您有责任确保参与者之间交换的网络数据可以被您游戏的所有设备和版本正确解释。
 例如,如果您引入了具有不同网络数据格式的新版本游戏或行为发生了重大变化,您需要确保此版本不与不兼容的游戏版本相匹配。
有关配置多人游戏兼容性的详细信息,请参阅分发游戏中心应用程序。
5、创建任何类型的拟合从拟合请求开始
所有新的匹配都以拟合请求开始,该请求描述了拟合所需的参数。
 本质上,拟合请求告诉Game Center要为拟合找到多少玩家以及要为拟合找到哪些玩家。
 Game Kit还使用拟合请求来自定义其匹配行为。
 例如,当您的游戏显示标准匹配界面时,该请求用于自定义屏幕外观。
 Game Kit和Game Center使用拟合请求的方式将在后面的章节中针对每种类型的拟合进行描述。
 但是,由于所有匹配共享一个通用的基础架构,因此在阅读其他章节之前,您需要了解如何创建和配置拟合请求。
在设计游戏的拟合基础架构时,首先实现对配对的基本支持,让你的网络游戏正常工作。
 然后,考虑添加一些更高级的功能。
拟合请求在Game Kit中由GKMatchRequest类定义。
 表6-2列出了拟合请求的属性。
| Property | 描述 | 
|---|---|
| minPlayers | 为拟合寻找的极小点玩家数量。 此属性必须在每个拟合请求上设置。 请参阅拟合请求必须指定拟合中的玩家数量。 | 
| maxPlayers | 为拟合寻找的最大玩家数量。 此属性必须在每个拟合请求上设置。 请参阅拟合请求必须指定拟合中的玩家数量。 | 
| defaultNumberOfPlayers | 要为拟合查找的默认玩家数量。 这用于配置默认用户界面以显示适当数量的玩家槽。 然而,只要玩家数量保持在极小点和最大玩家数量之间,玩家就可以添加或减去槽。 | 
| recipients | 一个可选属性,声明要邀请到拟合的一组玩家的玩家ID列表。 请参阅邀请一个玩家列表。 | 
| inviteMessage | 如果您邀请一组玩家进行拟合,您可以提供显示给这些玩家的自定义字符串消息。 通常,您为玩家提供在自定义用户界面中创建此字符串的机会,然后使用提供的字符串填充您的拟合请求。 | 
| playerGroup | 一个可选属性,允许您在游戏中创建不同的玩家子集。 每个子集单独匹配。 使用这些子集允许玩家提供更多关于他们正在寻找什么样的拟合的信息。 请参阅玩家组。 | 
| playerAttributes | 一个可选属性,允许您指定玩家想要在拟合中扮演的不同角色。 使用玩家属性允许玩家定义他们想要玩你的游戏的内容或方式。 请参阅玩家属性。 | 
拟合请求必须指定拟合中的玩家数量
创建新的拟合请求时,您需要设置拟合允许的极小点和最大玩家数。
 这样做我在创建拟合后设置minPlayers和maxPlayers属性。
 Game Center强制执行这些边界,并始终确保最终拟合具有正确的玩家数量。
玩家的极小点数量必须至少为2个。
 允许的玩家最大数量取决于正在创建的拟合类型。
 表6-2列出了当前最大值;这个最大值可能会改变。
| 拟合类型 | 最大玩家数 | 
|---|---|
| 点对点 | 4 | 
| 托管 | 16 | 
| 回合制 | 16 | 
在运行时,您的游戏调用maxPlayersAllowedForMatchOfType:方法来确定每种拟合允许的玩家的确切数量。
邀请玩家列表
默认情况下,Game Center使用拟合请求来寻找其他等待的玩家来填充拟合。
 例如,如果拟合需要四个人在场才能开始,那么Game Center找到四个等待的参与者,将他们形成拟合,然后返回该拟合。
 这个场景很有用,因为它允许等待的玩家快速匹配在一起。
 然而,并不是每个人都希望在每次玩拟合时与一组随机的玩家一起玩。
 通常,玩家希望专门与朋友对战。
 出于这个原因,标准用户界面允许玩家邀请特定的朋友加入游戏。
 但是,您的游戏也可以选择通过为拟合请求分配玩家标识符字符串数组来以编程方式提供这种支持。
提供玩家列表会改变 Game Center 的匹配行为。下面是几个行为变化的示例:
- 标准的用户界面允许玩家在拟合中选择一个特定的位置,并邀请特定的人来填补它。
 Game Kit 为受邀玩家保留位置,直到他们做出回应或过程超时。
- 如果您正在使用用于创建实时拟合的编程界面,并且您提供了要邀请的玩家列表,Game Center尝试仅将这些玩家添加到拟合中。
 它不执行正常的匹配过程。
 但是,如果拟合需要更多玩家,您的游戏可以创建另一个执行正常匹配的拟合请求。
 这种行为分离允许您精确控制随机匹配发生的时间。
- 在基于回合的拟合中,玩家会立即加入拟合,但是只有当轮到该玩家时,玩家才会收到加入拟合的邀请。
玩家组
Game Center的默认行为是自动将任何等待玩你的游戏的玩家拟合成任何需要更多玩家的拟合。
 这具有将玩家快速匹配到匹配中的优点,但这也意味着可以将玩家添加到他们不感兴趣的匹配中。
 在这种情况下,您可以允许玩家定义他们想要玩的匹配类型,然后仅与志同道合的玩家进行拟合。
 这是通过玩家组来完成的。
玩家组由一个无符号的32位整数定义。
 当您将拟合请求上的playerGroup属性设置为0时,该玩家可以匹配到任何等待拟合中。
 当您将playerGroup属性设置为非零数字时,该玩家仅与拟合请求共享相同玩家组号的玩家匹配。
 通常,如果您的游戏使用玩家组,它提供了一个自定义界面,允许玩家选择他们感兴趣的精确选项。
 它接受玩家的选择并将它们组合起来形成一个玩家组号。
以下是一些如何分区玩家列表的示例:
- 按技能水平分隔玩家。
- 通过一组用于判定拟合的规则来分隔玩家。
- 根据拟合的地图分开玩家。
虽然具体要创建多少玩家组由你决定,但不要仅仅为了创建而创建组,创建许多较小的玩家组可能会导致每个玩家等待很长时间才能玩拟合,而是创建玩家可以认同的大组。
玩家属性
玩家属性允许玩家在拟合中选择他们想扮演的角色。
 有了玩家属性,每个玩家都可以选择一个特定的角色,游戏中心会找到一个合适的玩家组合,以确保所有角色都被填满。
以下是一些在游戏中使用玩家属性的方法:
- 像国际象棋这样的游戏可以使用玩家属性来决定玩家是想成为黑白棋子。
- 角色扮演游戏可以提供不同的角色角色,具有不同的优势、劣势和玩家给拟合带来的能力。
- 体育游戏可以使用玩家属性来允许玩家指定团队中的位置,例如投手或四分卫。
- 基于回合的拟合可以将地图上的不同位置分配给不同的派系,并使用属性来确保每个位置都被填充。
玩家属性有一些限制:
- 仅对自动匹配的玩家检查角色。
 如果玩家邀请朋友加入拟合,朋友不会选择角色,也没有属性集。
- Game Kit提供的配对标准用户界面中不显示角色,您的游戏必须提供自己的自定义用户界面以允许玩家选择角色。
- 返回到您游戏的拟合对象不会告诉您玩家选择了哪些角色,您的游戏必须在创建拟合后单独发送角色选择信息。
- 你的游戏定义了一套完整的角色;你的游戏定义的所有角色必须在拟合创建时被填充。
 因为这需要你定义的角色和拟合中允许的玩家数量之间的仔细协调。
 因此,玩家属性需要额外的设计和测试工作。
实现玩家属性
Player属性在拟合请求上使用playerAttributes属性实现,默认情况下该属性的值为0,表示忽略属性属性,如果属性的值非零,Game Center则使用它来拟合特定角色。
要创建玩家可以在游戏中扮演的角色,您需要为玩家可以扮演的每个角色创建一个32位的掩码。
 您可以在游戏中添加一个允许玩家选择角色的自定义用户界面。
 您可以在玩家选择的掩码的拟合请求上设置playerAttributes属性,如清单6-1所示。
 然后,像往常一样执行标准或自定义匹配。
例6-1设置拟合请求的播放器属性
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 4;
request.maxPlayers = 4;
request.playerAttributes = MyRole_Wizard;
如果Game Center在拟合请求中看到非零玩家属性,它会智能地将玩家添加到拟合中,以便所有玩家在使用按位或操作组合时拥有完整的FFFFFFFFh。
算法大致如下所示:
- 比赛的面具开头是邀请玩家的面具。
- Game Center查找具有非零玩家属性值的拟合请求的玩家。
 Game Center搜索掩码包含当前不在匹配掩码中的设置位的玩家。
- 玩家被添加到拟合后,新玩家的玩家属性值的值按位或进入比赛的蒙版。
- 如果匹配的蒙版不等于FFFFFFFFh,Game Center会搜索其他玩家加入拟合。
 如果拟合完成,但游戏仍然需要更多玩家到达极小点,Game Center会继续搜索更多玩家添加。
玩家属性示例:角色扮演游戏
假设您正在创建一个包含四个角色的角色扮演游戏:战士、巫师、小偷和牧师。
 每个拟合由四个玩家组成,每个角色的拟合必须有一个且只有一个玩家。
 为了实现这一要求,您的游戏为每个角色创建符合前面描述的四步算法的面具。
 例6-2提供了一个面具示例集。
示例6-2为字符类创建掩码
#define MyRole_Fighter 0xFF000000
#define MyRole_Cleric 0x00FF0000
#define MyRole_Wizard 0x0000FF00
#define MyRole_Thief 0x000000FF

没有一个角色掩码重叠;如果任何两个掩码由AND连接,则结果值始终为00000000h。
 当所有四个角色掩码按位或排列在一起时,整个拟合掩码被填充(FFFFFFFFh)。
 这确保了在有四个玩家的拟合中,每个角色只填充一次。
玩家属性示例:国际象棋
对于国际象棋,您可能希望允许玩家选择他们想玩黑色棋子还是白色棋子,或者他们不在乎他们从什么颜色开始。
 清单6-3为这种设计提供了一组示例蒙版。
示例6-3为国际象棋创建掩码
#define MyRole_Black  0xFFFF0000
#define MyRole_White  0x0000FFFF
#define MyRole_Either 0xFFFFFFFF
国际象棋拟合总是需要两个玩家,所以有了这些面具,有四种可能的玩家组合可以满足完整面具的条件:
- 一个黑人玩家一个白人玩家
- 一个白人玩家和一个不在乎的玩家
- 一个黑人玩家和一个不在乎的玩家
- 两个玩家不关心他们的棋子是什么颜色
图6-1可能的掩模组合
 
八、实时匹配
在实时拟合中,玩家同时连接到Game Center(从而相互连接)。
 这种拟合形式适用于实现任何需要现场参与的游戏类型。
 实时匹配的一个关键优势是Game Kit和Game Center为实时匹配提供了广泛的支持,这简化了您需要编写的代码。
 特别是,它解决了尝试寻找玩家加入拟合时的许多问题,它提供了一个高级网络接口,让您可以忽略底层网络层的许多问题,并且它为语音聊天提供了广泛的内置支持。
 Game Kit允许您专注于实现您的游戏,而无需担心游戏和语音数据如何传递的精确机制。
然而,即使Game Kit解决了许多此类问题,您的游戏策划仍然需要准备好应对实时网络中出现的复杂问题,包括:
- 需要游戏的多个实例(在不同的设备上运行)同时或几乎同时执行任务,同时在设备之间同步状态
- 网络可靠性
- 网络延时
实现实时拟合需要大量的工作来设计、实现和测试你的游戏。
1、为您的游戏添加实时配对的清单
创建支持实时匹配的游戏的清单比其他Game Center技术更广泛,包括三个主要领域:匹配、数据交换和语音聊天。
 作为这个过程的一部分,你还需要就游戏中如何进行匹配做出许多设计决策,包括:
- 有多少玩家可以同时玩
- 你游戏的网络版本是什么样的?
- 构建设计需要共享哪些数据
这些和其他问题会影响您的游戏玩法和网络代码的设计。
入门
在开始匹配实现之前,必须执行以下任务:
- 如果您的应用是OS X应用,则必须添加以下权利,否则所有实时功能都将失败: 
  - com.apple.security.network.client
- com.apple.security.network.server
 
- 向游戏添加代码以验证本地玩家。
 请参阅在Game Center中与玩家合作。
- 熟悉配对概述中描述的配对概念以及本章的内容。
寻找拟合的玩家
确定您的游戏是否将使用标准匹配界面、显示自定义匹配界面或使用两者的组合。
 无论您选择哪种实现,您都必须支持游戏内部选择的实现行为,并添加对处理从游戏外部收到的邀请的支持。
- 如果要实现标准用户界面,请参阅使用标准匹配用户界面。
- 如果要实现自定义用户界面,请参阅实现自定义拟合用户界面。
在拟合参与者之间交换数据
这是该过程中最复杂的步骤,因为它需要您为实时拟合的游戏机制和拟合参与者之间同步通信的低级细节执行大量设计工作。
- 设计你的游戏用来与拟合中的其他参与者交流的网络信息。
 大多数游戏在拟合开始时交换信息,为每个参与者提供相同的初始游戏状态,然后在游戏中发生事件时发送更新。
 请参阅设计你的网络游戏。
- 编写代码以使用拟合对象向其他拟合参与者发送数据您的游戏将游戏状态编码为二进制数据,并使用拟合对象将其传输给其他玩家参见向其他玩家发送数据。
- 实现拟合委托以处理拟合期间可能发生的事件: 
  - 当其他玩家在游戏开始时连接时,会通知委托。
 您的委托通常会等到所有人都连接后再开始游戏。
 请参阅开始拟合。
- 委托接收其他玩家发送的数据。
 您的委托通过解码和操作来自其他玩家的数据来反转用于发送数据的过程。
 请参阅从其他玩家接收数据。
- 如果玩家在游戏运行时断开连接,代表将收到通知,必须决定是否停止拟合或重新配置游戏以处理减少的玩家数量。
 请参阅从拟合断开连接。
 
- 当其他玩家在游戏开始时连接时,会通知委托。
将语音聊天添加到拟合
语音聊天支持是可选的,但可以把一个好游戏变成一个伟大的游戏。
 它允许玩家进行更密切、更自然的交互。
 以下是在游戏中实现语音聊天的步骤:
- 决定你的游戏需要多少语音通道。
 例如,免费拟合可以为所有参与者使用一个通道,而与多个团队的拟合可能为每个团队使用一个单独的通道,以及一个包含游戏中所有玩家的附加通道。
- 如果您的游戏在iOS中运行,请配置音频会话以启用麦克风。
 所有录制或播放音频的游戏都必须有音频会话。
 请参阅创建音频会话(仅iOS)。
- 通过调用拟合对象的voiceChatWithName:方法创建语音通道。
 拟合返回一个GKVoiceChat对象。
 请参阅创建语音通道。
- 调用语音聊天对象的start方法来激活通道。
 请参阅启动和停止语音聊天。
- 当玩家需要对着麦克风说话时,为聊天通道启用麦克风。
 每个参与者一次只能对着一个通道说话。
 请参阅启用和禁用麦克风。
- 在游戏中提供控件,允许用户在通道内启用和禁用语音聊天、设置音量级别或静音玩家。
 请参阅控制语音聊天的音量。
- 决定你的游戏是应该支持一键通模型,还是应该持续对麦克风进行采样。
 在一键通游戏中,提供玩家按下的控件,以便向其他玩家传输语音数据。
 在持续对麦克风进行采样的游戏中,提供将麦克风静音的控件。
- 实现一个更新处理程序,当玩家连接或断开连接,或者当玩家开始或停止说话时调用。
 通常,您使用此处理程序更新游戏的用户界面,以显示语音聊天中每个玩家的最新信息——例如,当玩家说话时突出显示用户界面项目。
 请参阅查看玩家状态何时改变。
2、游戏中心实时匹配概述
表7-1列出了您在Game Center中设计使用实时匹配的游戏时使用的类。
| 班 | 描述 | 
|---|---|
| GKMatchRequest | 拟合请求对象指定要创建的拟合的属性。 请参阅创建任何类型的拟合从拟合请求开始。 | 
| GKMatchmaker | 所有支持实时配对的游戏都使用此单例。 游戏必须向媒人对象提供邀请处理程序,以便及时处理启动游戏的推送。 可选地,想要创建自定义配对用户界面的游戏使用媒人以编程方式搜索匹配项。 | 
| GKInvite | 当玩家接受拟合游戏邀请时传递。 | 
| GKMatchmakerViewController | 显示标准匹配用户界面的视图控制器类。 默认用户界面允许玩家向拟合添加朋友,搜索连接到游戏中心的其他玩家,甚至找到物理上彼此靠近的玩家,即使他们当前没有连接到游戏中心。 | 
| GKMatch | 这个返回到游戏的对象包含拟合中玩家的信息。 游戏的每个实例都使用自己的 GKMatch对象与拟合中的其他玩家进行通信。 | 
| GKVoiceChat | 由拟合对象返回并表示玩家可以通过的特定语音通道进行通信。 | 
3、寻找拟合的玩家
当玩家想要创建拟合时,你的游戏必须显示一个用户界面,让玩家看到并配置拟合的状态。
 实现这一点的最简单方法是使用GKMatchmakerViewController类提供的标准用户界面。
 它用少量的代码投资实现了许多常见的匹配场景。
 但是,如果你想实现自己的自定义行为,你可以设计自己的自定义用户界面,然后调用GKMatchmaker类以编程方式执行此任务。
 matchaker类将数据返回到你的游戏,以便它可以更新自己的用户界面。
使用标准匹配用户界面
标准的匹配用户界面通常是响应用户点击游戏标题屏幕上的按钮而显示的。
 它可以在点击按钮后立即出现,也可以在标题屏幕和匹配屏幕之间的几个临时屏幕后出现。
 例如,如果您的拟合请求包括玩家组或玩家属性,您通常有自己的自定义用户界面屏幕来配置拟合请求,然后再显示默认的匹配用户界面。
呈现配对用户界面
创建和配置一个新的拟合请求,并使用拟合请求初始化一个新的GKMatchmakerViewController对象。
 呈现视图控制器使自己成为匹配视图控制器的委托,然后呈现匹配界面。
 玩家与匹配屏幕交互,直到拟合准备好开始或发生错误。
作为委托者,视图控制器(或其他类)必须实现一些方法来响应事件。
 这些方法中的每一个都关闭视图控制器,然后执行游戏所需的任何特定操作。
当玩家在创建拟合之前取消匹配过程时调用matchmakerViewControllerWasCancelled:委托方法。
 通常,你的游戏会关闭视图控制器,然后回到标题屏幕(或其他一些合理的用户界面屏幕)。
当匹配在尝试设置拟合时遇到错误时调用matchmakerViewController:didFailWithError:委托方法——例如,当设备失去网络连接时。
 与玩家取消匹配过程类似,您的游戏会关闭视图控制器并移动到前一个屏幕。
最后,如果已经创建了拟合并且每个人都准备好开始,则调用委托的matchmakerViewController:didFindMatch:方法。
 此方法向您的游戏返回一个GKMatch对象。
 通常,当拟合开始时,拟合将返回给所有参与者,因此此委托方法在多个设备上同时调用。
处理来自其他玩家的邀请
当玩家接受另一个玩家的邀请时,你的游戏就会启动(如果有必要),并向你的游戏发送邀请。
 你的游戏通过向本地玩家注册侦听器来接收此邀请。
 创建一个实现GKLocalPlayerListener协议的类。
 在你的类中,实现player:didAcceptInvite:方法来处理邀请。
将玩家添加到现有拟合
有时,拟合可能会低于游戏所需的玩家数量。
 例如,如果玩家失去网络连接,就会出现这种情况。
 在这种情况下,您可能需要添加额外的玩家,而不是完全解散拟合。
要在你的游戏中实现这一点,创建并呈现一个媒人视图控制器,就像你在默认情况下所做的那样。
 你只能从一个已经参与拟合的设备上执行此操作。
 呈现视图控制器后,调用视图控制器的addPlayersToMatch:方法,传入拟合以将玩家添加到。
 拟合接口使用拟合请求中的信息将玩家添加到现有拟合中,而不是创建新的拟合。
 拟合对象仍然返回到你的委托的matchmakerViewController:didFindMatch:方法。
当匹配屏幕显示时,拟合仍然处于活动状态;拟合中的设备仍然可以相互交换数据。
 但是,您可能需要暂停实际游戏,直到找到新玩家。
 由于拟合处于活动状态,语音聊天频道将继续工作。
实现自定义拟合用户界面
实现完整的自定义拟合界面可以像显示网络进度指示器一样简单,直到自动匹配完成,也可以像实现复制标准行为的完整自定义视图控制器一样复杂。
 后者可能是对编程时间的重大投资,因为它需要包括对以下所有内容的支持:
- 邀请特定玩家进行拟合
- 倾听受邀玩家的回应
- 寻找附近的玩家(可通过Wi-Fi或蓝牙获得)
本节开头是最简单的场景-寻找没有用户界面实现的拟合-然后构建构建自己的用户界面所需的更详细的概念。
自动匹配以填充拟合
您的游戏可以提供的最简单的界面是一个“即时拟合”按钮。
 当按下此按钮时,玩家被添加到拟合中,而无需输入任何其他信息。
 创建您自己的方法,其中包含一个GKMatchRequest。
 检索匹配器单例对象,并要求它使用findMatchForRequest:withCompletionHandler:findMatchForRequest:withCompletionHandler:方法为玩家找到拟合。
 当找到拟合或发生错误时,将调用匹配的完成处理程序。
 如果拟合返回到完成处理程序,则拟合被分配给一个属性。
 和以前一样,您的游戏执行代码来确定拟合是否准备好开始。
您还可以将玩家添加到现有拟合中。
 您的游戏不是调用findMatchForRequest:withCompletionHandler:方法,而是调用addPlayersToMatch:matchRequest:completionHandler:方法,传入拟合以添加玩家。
取消搜索
配对过程需要时间,对某些玩家来说可能完成得不够快。
 如果你的游戏包括对程序化匹配的支持,它需要提供一个允许玩家取消活动搜索的用户界面。
 调用媒人单例对象上的cancel方法来取消活动搜索。
邀请特定玩家进行拟合
如果你要实现一个完整的自定义用户界面,你需要允许玩家邀请他们的朋友来拟合。
 通常,这意味着你的游戏呈现了一个界面,显示当前拟合中的所有玩家,并有空槽来填补位置。
 然后玩家可以选择一个或多个空槽,并向特定玩家发出邀请。
 为了实现这一点,你的游戏需要能够发送邀请并在处理邀请时接收通知。
您可以通过向拟合请求分配更多信息来处理这两个问题。
 将玩家列表分配给recipients属性并为recipientResponseHandler属性实现屏蔽语句。
当收到响应时,如果响应等于GKInviteeResponseAccepted,则将播放器添加到拟合中。
 更新用户界面以显示该播放器现在是拟合的一部分。
 如果收到任何其他响应,则将播放器从用户界面中删除,以便插槽再次显示为空。
当拟合请求包含玩家对象列表时,那么匹配行为就会发生变化(无论您是创建新的拟合还是将玩家添加到特定拟合中)。
 为拟合寻找玩家的正常过程被暂停。
 相反,匹配代码所做的唯一事情就是将邀请扩展到拟合请求中的玩家并等待响应。
 假设您的游戏没有取消匹配过程,它会在处理所有邀请时完成并向您的游戏返回拟合。
因为有些邀请可能不会被接受,所以您可能会收到一个仍然需要更多玩家的拟合。
 考虑到这一点,您的自定义用户界面应该实现以下行为:
- 玩家第一次邀请玩家加入拟合时,与那些玩家一起创建拟合请求并调用findMatchForRequest:withCompletionHandler:方法等待拟合返回(或者如果玩家取消邀请则取消请求)。
- 如果玩家希望在处理邀请后向拟合添加更多玩家,请再次执行相同的过程,但调用addPlayersToMatch:matchRequest:completionHandler:方法。
- 如果玩家想要自动匹配剩余的插槽,请创建一个拟合请求,但不要包含要邀请的玩家列表。
 调用addPlayersToMatch:matchRequest:completionHandler:方法来填充剩余的插槽。
 您必须等到所有被邀请的玩家都连接起来,因为在自动匹配剩余的玩家插槽时,所有未决的拟合请求都会被取消。
- 如果玩家想与已经在拟合中的玩家开始拟合,调用finishMatchmakingForMatch:方法完全结束匹配。
如果你想创建一个同时包含受邀玩家和自动匹配玩家的拟合,你必须首先与受邀玩家一起创建拟合。
 在所有受邀玩家都连接后,你可以通过自动匹配添加玩家。
搜索附近的玩家
标准用户界面允许玩家发现附近的其他玩家,即使当前两个玩家都没有直接连接到Game Center。
 发现使用本地Wi-Fi或蓝牙来寻找其他玩家。
 当玩家远离他们的正常网络但仍想互相对抗时,此功能非常有用。
 您可以在自定义用户界面中实现类似的行为。
以下是您通常如何实现此行为:
- 在用户界面中提供一个按钮,允许玩家搜索附近的玩家。
 按下此按钮后,会显示一个新的用户界面屏幕,显示它找到的任何附近的玩家。
 或者,您的用户界面可以简单地自动显示附近的玩家。
 如果您选择这种方法,通常将此玩家列表与用户界面的其余部分分开是有意义的。
- 调用共享媒人的startBrowsingForNearbyPlayersWithReachableHandler:方法,在玩家进入和离开该区域时传递一个屏蔽以接收玩家通知。
 当找到玩家时,将该玩家添加到您可见的玩家列表中。
 当玩家消失时,删除该玩家。
- 如果玩家选择一个或多个玩家发送邀请,创建拟合请求并将这些玩家的玩家标识符添加到拟合请求中。
 然后,按照邀请特定玩家进行拟合中描述的行为进行操作。
- 当您的浏览屏幕从屏幕上移除时(因为玩家取消了浏览或因为他们已经邀请了玩家)调用stopBrowsingForNearbyPlayers方法。
寻找游戏中的玩家活动
正在寻找多人拟合的玩家通常希望立即匹配,或者至少要知道什么时候匹配可能需要更长的时间。
 例如,如果玩家在玩家不经常在线的一段时间内在线,那么有兴趣加入拟合的玩家数量可能比黄金时段要少得多。
 确定有多少玩家在线被称为玩家活动。
 GKMatchmaker类提供了queryActivityWithCompletionHandler:和queryPlayerGroupActivity:withCompletionHandler:方法来测试与您的游戏相关的Game Center上的活动。
 任一方法返回的值都是最近请求拟合的玩家数量。
4、在拟合参与者之间交换数据
创建拟合后,拟合中的参与者需要交换数据以同步彼此之间拟合的状态。
 本节介绍如何实现和设计您的匹配代码的这一部分。
设计你的网络游戏
拟合中的每个参与者都依赖于一个GKMatch对象来与连接到拟合的其他参与者交换数据。
 GKMatch类不定义网络消息的格式或内容。
 相反,它只是将您的消息视为要传输的字节。
 这为您设计网络游戏提供了极大的灵活性。
 本节的其余部分描述了在实现网络游戏之前您需要理解的关键概念。
每当您向其他参与者发送数据时,您可以决定拟合应该使用多少努力来发送数据。
 匹配可以可靠地发送您的数据,这意味着拟合重传数据直到目标接收到,或者不可靠地发送数据,这意味着它只发送一次数据。
- 可靠的传输更简单,但可能会更慢;缓慢或容易出错的网络可能要求设备多次发送消息,然后才能成功传递给预期的收件人。
 拟合还保证从一台设备发送到同一收件人的多条可靠消息按照发送顺序传递。
- 不可靠传输的消息可能永远无法到达目的地,或者可能无法正常传递。
 不可靠传输对于实时交易最有用,因为使用可靠消息导致的任何传输延迟都会使消息内容无效。
 例如,如果您的游戏传输航位推算算法的位置和速度信息,可靠消息可能会提供在传递给接收者时严重过时的定位数据。
 通过使用不可靠消息,消息传递得更快。
 您的游戏通过发送带有更新位置和推算信息的新消息来承担网络错误的责任。
消息的大小对于数据能够多快地传递到其目标也起着重要作用。
 大消息必须被拆分成更小的数据包(这种拆分称为碎片)并由每个目标重新组装。
 这些较小的数据包中的每一个都可能在传输过程中丢失或乱序传递。
 大消息应该可靠地发送,以便Game Kit能够处理碎片和组装。
 但是,重新发送和组装的过程需要时间。
 您不应该使用可靠的传输来发送大量的实时数据。
请注意,您的网络数据正在通过您无法控制的服务器和路由器传输。
 您的消息会受到网络上其他设备的巡检和修改。
 当您在一台设备上的游戏接收到其他设备上参与者的网络数据时,它应该将该消息视为不受信任的数据,并在使用前验证其内容。
 有关如何避免安全漏洞的信息,请参阅 安全编码指南 。
以下是设计游戏网络时要遵循的一些一般准则:
-  您的消息格式需要包含区分消息类型的方法。 
 GKMatch类对消息的内容一无所知,因此您必须在游戏中实现该功能。
 例如,您可以创建一个枚举类型来标识不同类型的消息,并以该枚举类型启动每个消息。
-  以最低的频率发送消息,使您的游戏功能良好。 
 您的游戏的图形引擎可能以每秒30到60帧的速度运行,但您的网络代码发送更新的频率要低得多。
-  使用完成任务的最小消息格式。 
 应仔细检查频繁发送的消息或其他参与者必须快速接收的消息,以确保没有发送不必要的数据。
-  在不丢失有价值信息的情况下,将您的数据打包成最小的表示形式。 
 例如,程序中的整数可能使用32位或64位来存储其数据。
 如果整数中存储的值始终在1到10的范围内,您可以将其存储在网络消息中,只需4位。
-  将不可靠消息的大小限制为1000字节或更小。 
-  将可靠消息的大小限制为87 KB或更小。 
-  只向需要消息中包含的信息的参与者发送消息。 
 例如,如果你的游戏有两个不同的团队,团队相关的消息应该只发送给同一团队的成员。
 向拟合中的所有参与者发送数据会消耗网络带宽,但收益很小。尽管 GKMatch对象在所有参与者之间创建了完整的对等连接,但您可以通过在其之上分层环形或客户端-服务器网络架构来减少流量。
 图7-1显示了四人游戏的三种可能的网络拓扑结构。
 在左侧,对等游戏在各种设备之间有12个连接。
 但是,您可以通过提名其中一个设备充当主机来在此之上分层客户端-服务器架构。
 如果您的游戏仅向主机传输或从主机传输,您可以将连接数减半。
 环形架构允许设备仅将网络数据包转发到下一个设备,但会进一步减少连接数。
 每个拓扑提供不同的性能特征,因此您将希望测试不同的模型以找到一个提供您的游戏所需性能的模型。
图7-1网络拓扑

- 指定如何处理网络中断。
 网络本质上是一种不可靠的通信媒介。
 在拟合进行中,参与者可以随时断开连接。
 您的游戏必须处理断开连接消息。
 例如,如果您实现游戏以使用客户端-服务器拓扑,那么当服务器与拟合断开连接时,您的游戏可能需要指定一个新设备来成为新服务器。
开始拟合
当Game Kit向您的游戏发送GKMatch对象时,与拟合中其他参与者的连接可能尚未建立。
 如果没有其他玩家连接,则该用户设备上的players数组可以为空,或者它可能包含已经连接的玩家子集。
 您的游戏必须等到所有玩家都连接后才能开始拟合。
 要确定有多少玩家正在等待加入拟合,您的游戏读取匹配的expectedPlayerCount属性。
 当该属性的值达到0时,所有玩家都连接,拟合准备开始。
 执行此检查的适当位置是在拟合委托的match:player:didChangeConnectionState:方法。
 每当拟合成员连接或断开连接时,都会调用此方法。
甚至在拟合开始之前,所有已经连接到拟合的玩家就已经可以相互交换数据了,这允许你的游戏创建语音通道(或者你自己的用户界面),让已经在那里的玩家相互交流。
确定最佳服务器
如果您在拟合对象提供的对等模型之上实现客户端-服务器架构,选择正确的服务器对于实现良好的网络性能和结果拟合的延时至关重要。
 出于这个原因,Game Kit提供了内置支持,允许游戏中的所有对等方相互测试以确定最佳服务器。
 您通过调用chooseBestHostPlayerWithCompletionHandler:方法来实现这一点。
 当操作完成时,返回的玩家标识符是其设备将为您的游戏提供最佳服务器的人。
如果您打算使用此方法搜索最佳服务器,则拟合中的所有设备都必须运行在支持此方法的Game Kit版本上,并且拟合中的每个设备都必须同时调用此方法。
 这允许所有设备同时相互测试和通信。
 通常,在所有人都连接起来并且游戏准备好开始后,您会找到最佳服务器。
向其他玩家发送数据
要将数据从一个设备发送到连接到拟合的其他设备,您的游戏创建一个消息并将其封装在NSData对象中。
 您可以使用sendDataToAllPlayers:withDataMode:error:方法将此消息发送给所有连接的玩家,或使用sendData:toPlayers:dataMode:error:方法发送给玩家的子集。
从其他玩家接收数据
当拟合接收到另一个参与者发送的数据时,消息通过调用拟合委托的match:didReceiveData:fromRemotePlayer:方法传递。
 此方法的实现需要解码消息并对其内容进行操作。
与拟合断开
当玩家准备离开拟合时,您的游戏调用拟合对象的disconnect方法。
 如果玩家的设备在一定时间内没有响应,也可能会自动断开连接。
 当玩家从拟合断开连接时,拟合中的其他参与者会通过调用拟合委托的match:player:didChangeState:方法得到通知。
5、将语音聊天添加到拟合
Game Kit 提供所有低级语音编码和传输,因此您不必自己处理。
 然而,任何支持语音聊天的游戏都应该提供自己的自定义用户界面,允许玩家配置语音聊天。
 这意味着玩家应该能够:
- 选择加入或退出您提供的任何语音聊天选项。
- 静音其他玩家以及控制语音聊天的整体音量。
- 将麦克风静音。
- 如果您的游戏支持多个通道,请选择他们说话的通道。
- 看看哪个玩家在说话。
创建音频会话(仅iOS)
在您的iOS游戏可以使用拟合对象提供的语音聊天服务之前,您必须创建一个音频会话,该会话具有播放和录制声音的能力。
 如果您的游戏提供其他音效,则您可能已经拥有音频会话。
注意 : 收到AVAudioSessionDidBeginInterruptionNotification后,您的应用需要停止当前活动的任何语音聊天。
 您可以在收到AVAudioSessionDidEndInterruptionNotification后重新启动任何语音聊天。
有关创建和使用音频会话的更多详细信息,请参阅 音频会话编程指南 。
创建语音通道
您的游戏从不直接创建GKVoiceChat对象。
 相反,语音聊天对象由GKMatch对象代表您创建。
 使用voiceChatWithName:方法创建新通道。
 单个拟合可以创建多个通道,单个玩家可以一次分配到多个通道。
 在任何通道上接收到的音频混合在一起并通过扬声器输出。
对于不同设备上的多个参与者加入同一个通道,他们需要一种方法来识别特定的通道。
 这种识别是通过通道名称来完成的。
 通道名称是一个字符串,由您的游戏定义,它唯一地命名通道。
 当两个或多个参与者以相同的名称加入一个通道时,他们会自动连接到彼此的语音聊天。
启动和停止语音聊天
在通道启动之前,不会通过语音通道发送或接收语音数据。
 您可以通过调用语音聊天对象的start方法开始语音聊天。
 调用start方法后,如果以下情况为真,则该设备上的语音聊天对象将连接到通道中的其他参与者:
- 该设备在Wi-Fi网络上。
- 有一个麦克风连接到设备。
如果不满足这些条件中的任何一个,语音聊天对象将等待,直到两者都为真,然后再连接到通道。
类似地,当玩家准备离开通道或当您希望暂时关闭通道时,您可以调用语音聊天对象的stop方法。
 停止通道(而不是简单地让其他玩家静音)的一个优点是,其他玩家不需要向离开通道的玩家发送数据。
 数据传输的减少为游戏的消息传递留下了更多带宽。
启用和禁用麦克风
当您希望玩家能够说话时,您可以启用麦克风。
 根据游戏的性质,您可能希望在游戏运行时连续启用麦克风,或者您可能希望在界面中包含一键通按钮。
通道通过将语音聊天对象的active属性设置为YES麦克风:
一次只能有一个通道启用麦克风。
 当您为一个通道启用麦克风时,先前所有者的active属性会自动设置为NO。
控制语音聊天的音量
您的游戏可以通过两种方式控制语音聊天的音量。
 首先,您的游戏可以通过更改语音聊天对象的volume属性来设置聊天的整体音量级别:volume属性接受0.0和1.0之间的值。
 0.0的值使整个通道静音;1.0的音量以全音量输出语音数据。
其次,你的游戏可以有选择地让通道中的玩家静音。
 通常,如果你的游戏打算让玩家静音,它应该提供一个用户界面,允许玩家选择他们想让哪些玩家静音。
 要让玩家静音,你可以调用语音聊天对象的setPlayer:muted:方法和YES。
 要解除玩家静音,你可以发出同样的呼叫,而是传递NO。
观察玩家的状态何时改变
当您的游戏希望收到玩家状态变化的通知时,您可以实现更新处理程序。
 playerVoiceChatStateDidChangeHandler属性处理程序是语音聊天对象在玩家连接或断开通道以及玩家开始和停止说话时调用的屏蔽对象。
 例如,当玩家说话时,您可以使用更新处理程序在用户界面中突出显示玩家的名字。
九、主办比赛
托管匹配允许您的游戏使用Game Center的匹配服务来寻找玩家,但使用您自己的服务器将玩家相互连接。
 托管匹配需要您实现自己的自定义低级网络代码,因为Game Center的服务器根本不参与拟合游戏。
 然而,托管游戏提供更大的拟合大小以及将自己的服务器添加到每个拟合中的能力。
1、为托管玩家进行拟合
您为托管匹配编写的代码类似于您为实时匹配编写的代码。
 在您尝试创建使用托管匹配的游戏之前,您应该已经熟悉实时匹配。
 这两种模型之间的关键区别在于,托管匹配返回玩家标识符,而不是完成的拟合对象。
 然后,您的游戏将该玩家连接到您自己的服务器。
 您的服务器必须将该玩家的网络地址拟合到玩家标识符中,并且类似于拟合,当拟合中的一个设备需要与其他设备通信时执行路由。
表8-1列出了实时匹配的最常见方法以及托管匹配的相应方法或实现细节。
| 实时撮合 | 主持相亲 | 
|---|---|
| 视图控制器委托实现 matchmakerViewController:didFindMatch:方法来接收拟合。 | 您的委托实现 matchmakerViewController:hostedPlayerDidAccept:方法来接收玩家已接受加入拟合邀请的通知。 | 
| 编程匹配代码调用 findMatchForRequest:withCompletionHandler:来创建新的拟合。 | 您的编程代码调用 findPlayersForHostedRequest:withCompletionHandler:为拟合找到一组新玩家。 | 
| 您的编程代码调用 addPlayersToMatch:matchRequest:completionHandler:将玩家添加到拟合中。 | 您的编程代码调用 findPlayersForHostedRequest:withCompletionHandler:为拟合寻找一组新玩家。因为您实现了自己的拟合架构,所以您需要在调用时决定是否将玩家添加到现有的托管拟合中。 | 
| 你的游戏会收到一个邀请。 | 该 GKInvite对象的hosted属性告诉您拟合是否是托管拟合。 | 
2、主办比赛概述
默认情况下,当您执行匹配过程时,Game Kit会在连接到拟合的每个设备上返回GKMatch对象的实例。
 然后每个设备使用其拟合对象与其他参与者进行通信。
 对于大多数游戏来说,这种默认行为正是您想要的。
 GKMatch类抽象了底层网络拓扑,以便您的游戏可以简单地专注于向其他参与者发送数据,而无需担心数据是如何传输的。
但是,有些游戏可能希望支持的玩家数量超过Game Center上支持的最大玩家数量,或者可能希望自己的服务器仲裁拟合。
 在这些情况下,您仍然可以使用匹配来为托管拟合寻找玩家。
 托管拟合不使用GKMatch对象。
 相反,您的游戏的每个副本都会收到拟合中所有玩家的玩家标识符。
 然后,您的游戏会采取额外步骤将玩家连接到您的服务器。
创建托管拟合需要游戏实现游戏所需的所有低级网络。
 特别是,您必须在游戏中执行以下所有操作:
- 设计和实现您自己的网络代码以将每个设备连接到您的服务器。
- 设计和实现您自己的网络协议,以通知其他设备拟合中任何参与者的状态。
- 设计并实现您自己的服务器实现,将玩家标识符映射到连接到服务器的特定设备。
 因此,您的服务器负责在玩家之间路由网络数据。
- 如果您的游戏使用Game Kit的标准配对用户界面,您必须确保每个设备在连接到您的服务器后通知Game Kit。
 此信息允许Game Kit更新其用户界面。
3、使用Matchaker View控制器创建托管拟合
创建和配置一个新的拟合请求,然后使用拟合请求初始化一个新的GKMatchmakerViewController对象。
 呈现视图控制器使自己成为匹配视图控制器的委托,然后呈现匹配界面。
 玩家与匹配屏幕交互,直到拟合准备好开始或发生错误。
当视图控制器被托管时,您的委托将实现matchmakerViewController:hostedPlayerDidAccept:方法来处理新玩家。
 您的委托需要根据您的游戏在哪个设备上运行而采取不同的行为:
- 在玩家的设备上调用时,设备需要连接到自己的服务器,连接成功后调用setHostedPlayer:connected:方法告诉视图控制器玩家已经连接到拟合。
- 当在另一个玩家的设备上调用时,你的游戏需要确定它可以通过你的服务器与新玩家的设备对话,一旦它知道它可以向另一个客户端发送消息,调用setHostedPlayer:connected:方法来更新用户界面。
关闭匹配视图控制器
在所有受邀者都接受了您的应用的拟合请求后,您将有关玩家的信息发送到服务器,然后向所有受邀者发送开始游戏消息。
 当受邀者收到开始游戏消息时,他们必须手动关闭其应用的视图控制器。
十、回合制比赛
在基于回合的拟合中,拟合的玩家不需要同时连接到Game Center一起玩拟合。
 相反,游戏采用存储转发的方法;一个玩家在将下一个游戏传递给另一个玩家之前轮流玩。
 玩家继续轮流玩,直到拟合结束,一次只有一个玩家能够对游戏进行更改。
回合制比赛有许多有用的特性,使它们非常适合实现棋盘游戏和其他类似风格的游戏;例如:
- 一个玩家可以同时参与多个比赛。
 游戏加载玩家感兴趣的任何拟合观看或前进。
- 玩家必须连接到游戏中心才能转一圈。
- 拟合可以用少于一个完整的玩家来创建,甚至是一个玩家。
 根据需要添加其他玩家。
在iOS7中,交易所被引入了回合制比赛。
 交易所允许两个或更多的玩家采取行动,即使轮不到他们。
 这允许开发者通过允许不仅仅是本地玩家对游戏进行更改来创建比目前更复杂和多样化的回合制游戏。
 交易所的一些可能用途是:
- 两个玩家想在你的游戏中交换卡片。
 这可以在另一个玩家的回合中完成。
- 一个玩家想要拍卖一张卡,所有玩家都有机会同时对这张卡出价。
- 两个玩家互相攻击。
 他们轮流交换直到结束。
您可以通过在现有的回合制游戏模型中添加交易所来构建游戏,也可以仅通过使用交易所来创建游戏。
 本章首先描述如何实现传统的回合制拟合,然后在后面添加有关交易所的信息。
当您在游戏中实施回合制匹配时,玩家列表、匹配数据和其他详细信息都存储在Game Center上。
 您的游戏会根据需要下载此信息。
 Game Center主要负责存储数据。
 您负责提供使用此基础架构的游戏逻辑。
 特别是,您定义:
- 游戏中心必须存储哪些数据
- 需要更新拟合数据时
- 当游戏传给另一个玩家时
1、实现基于回合的拟合的清单
要为您的游戏添加基于回合的支持,您需要编写代码来处理以下活动:
- 允许本地玩家加入拟合。
 请参阅加入新拟合。
- 允许本地玩家查看现有匹配列表。
 请参阅使用现有匹配。
- 允许本地玩家查看拟合进行中的状态。
 请参阅使用拟合数据。
- 允许玩家在拟合中轮流。
 参见推进拟合的状态。
- 当玩家离开拟合时,设置玩家的拟合结果。
 请参阅设置拟合结果当参与者离开拟合时。
- 当所有玩家都有拟合结果集时,结束拟合。
 参见结束拟合。
- 处理邀请和其他拟合事件。
 请参阅响应拟合通知。
- 扩展您的游戏以包括交换。
 请参阅将交换添加到基于回合的拟合。
2、每个拟合都有参与者名单
把Game Center上的回合制拟合想象成桌子中间的桌游,桌子周围都是空座位,等待潜在玩家的填补,玩家数量是在拟合首次创建时设定的,永远不会改变。
当拟合开始时,只有部分座位可能由玩家填补。
 其他座位可能为特定玩家保留,也可能由Game Center的匹配服务填补。
 例如,考虑图9-1。
 玛丽创建了一个有四个座位的新拟合。
 因为玛丽创建了拟合,他们执行第一个回合,然后向另一个玩家传球。
图9-1玛丽开始新的拟合

此时,游戏中心看到这个拟合需要一个新玩家继续玩。
 当鲍勃搜索拟合时,游戏中心看到鲍勃想和玛丽玩同样的游戏,并将鲍勃分配为玛丽拟合的新玩家。
 因为这个游戏正在等待新玩家继续拟合,现在轮到鲍勃了。

Game Center总是按照特定的顺序跟踪拟合的玩家。
 无论您的游戏在哪个设备上运行,该顺序在拟合的持续时间内永远不会改变。
 在这个例子中,如果玛丽在第一个座位(插槽),他们会在整个游戏中呆在第一个座位上。
每个拟合都有当前玩家的概念。
 当前玩家在整个拟合过程中发生变化,代表轮到谁行动的玩家。
 当该玩家完成一个回合时,你的游戏选择另一个玩家行动。
 该玩家被告知现在轮到他们了,并成为当前玩家。
3、拟合数据代表拟合状态
游戏向Game Center发送拟合数据,描述拟合状态。
 Game Center对拟合数据的大小设置限制,但不关心其内容。
 游戏应存储保持拟合状态所需的任何数据。
在任何时候,拟合中的玩家都可以启动你的游戏来查看拟合。
 当玩家启动游戏时,你的游戏加载拟合数据,解释它,并显示给玩家。
图9-2当前玩家想要查看拟合

只允许当前玩家更改拟合数据,为当前玩家提供允许他们在游戏中采取行动的用户界面,随着玩家采取行动,您的游戏更新拟合数据并将其传回Game Center。

4、你的游戏决定游戏规则
当你设计回合制游戏时,你要制定规则来决定玩法。
 你可以准确地决定一个玩家可以采取什么行动,以及什么时候把游戏传给另一个玩家。
 当游戏传给另一个玩家时,你决定下一个玩家玩什么。
举个例子,国际象棋。
 白棋的棋手先移动。
 白棋之后,棋传给控制黑棋的棋手。
 黑棋移动后,棋传回白棋。
 棋手轮换,直到拟合以将死或相持结束。
 规则规定,玩游戏的人轮流玩,每个玩家每次都走一步。
国际象棋对玩家使用的规则非常一致,但Game Center允许您设计更灵活的游戏。
 例如,每次玩家进行转弯时,您都可以向他们展示不同的用户界面屏幕,并向玩家呈现不同的游戏选项。
 玩家的转弯可以像做出单个决策一样简单,也可以像选择构建什么、决定向何处发送资源以及与另一个玩家进行战斗一样复杂。
 您的游戏跟踪玩家所做的动作以及他们被允许做出的动作,作为您拟合数据的一部分。
考虑另一种流行的策略游戏风格:鼓励玩家探索、扩展、开发和消灭的4X游戏。
 在这种风格的游戏中,在拟合的不同点使用多种类型的转弯。
 这里有一种可能的组织方式:
- 在拟合开始时,每个玩家进行一个起始回合。
 游戏按顺序通过整个玩家列表。
 在起始回合中,玩家动作包括命名玩家的帝国,并选择他们打算如何在拟合中玩的选项。
 例如,一个常见的4X比喻是玩家选择一个在游戏中提供特定优势和劣势的阵营。
 在所有玩家完成起始回合后,拟合开始时允许玩家管理他们的帝国。
- 在管理游戏回合中,每个活跃的玩家进行一次回合,游戏按顺序通过玩家列表。
 每个参与者执行控制他们帝国所需的所有行动。
 所有玩家完成命令后,管理回合结束。
 然后你的游戏同时执行所有命令,通常在最后一个玩家的客户端进行管理回合。
 如果任何玩家单位发生冲突,则执行冲突游戏回合;否则,新的管理游戏回合开始。
- 在冲突游戏回合中,游戏会创建一个包含所有参与冲突的玩家的列表。
 然后,列表中的每个玩家都有一个回合,允许他们发出战斗命令。
 在所有玩家发出命令后,命令会同时执行,冲突会立即解决。
 如果任何玩家因战斗而被淘汰,游戏会在未来的回合中把他们从拟合中带走。
 如果只有一个玩家在拟合中保持活跃,拟合结束,该玩家被宣布为获胜者。
 否则,游戏继续进行另一个行政游戏回合。
图9-3显示了这个假设游戏的逻辑是如何处理的。
 它有三种不同的玩家回合,每种回合都有不同的用户界面、可能的用户动作和要存储在拟合数据中的数据。
图9-3假设的4X游戏的回合流程图
 
每当玩家进行转弯时,拟合数据都会显示出玩家进行的转弯类型。
 玩家进行转弯后,他们的动作会存储在拟合数据中。
 该设计还避免了出于非平凡的原因将转弯传递给其他玩家。
 每个玩家在将控制权传递给另一个玩家之前同时做出多个决定。
 当最终玩家完成转弯时,该玩家的游戏客户端会同时处理每个人的转弯,并将任何必要的更改写入拟合数据。
 虽然这种设计不是必需的,但它通过避免小而琐碎的玩家转弯来保持游戏的移动。
5、每当发生重要事件时保存拟合数据
当前玩家的设备可以在采取任何行动时立即将数据保存到Game Center,而无需将控制权转移给其他玩家。
 每当发生任何有趣的事情时,您都应该保存拟合数据。
 将以下列表视为应将数据发送到Game Center的关键位置:
- 如果其他玩家选择看拟合,这个动作应该立即被其他玩家看到。
- 该动作应该是不可撤销的,例如当该动作揭示了之前对玩家隐藏的信息或产生了需要持续的随机结果时。
- 动作改变当前玩家。
6、使用游戏工具包实现基于回合的拟合
要在游戏中实现回合制支持,您可以使用表9-1中列出的类。
| 类名 | 类函数 | 
|---|---|
| GKTurnBasedMatch | 描述存储在Game Center中的基于回合的拟合。 您可以使用此对象来检查拟合的当前状态,并在当前玩家进行回合时更新拟合的状态。 | 
| GKMatchRequest | 描述拟合的特征,可能包括要邀请的玩家的初始列表。 | 
| GKTurnBasedParticipant | 描述拟合中的玩家。 它的大多数属性是只读的,但 matchOutcome属性不是;当玩家离开拟合时,您将结果分配给该属性。 | 
| GKTurnBasedMatchmakerViewController | 提供标准用户界面,允许本地玩家创建新比赛或查看现有比赛。 | 
| GKTurnBasedEventHandler | 当玩家收到加入拟合的邀请或现有拟合已更新的通知时处理事件。 Game Kit为您提供了一个单一的事件处理器对象。 玩家通过身份验证后,您为这个单例对象分配一个事件处理器。 传递到您的游戏的确切事件因您的游戏当前是否是设备上的前台应用程序而异。 当您的游戏处于前台时,它会接收与基于回合的比赛相关的所有事件。 如果不在前台运行,则只有在收到重要事件时,您的游戏才会启动或带到前台。 | 
| GKTurnBasedEventListener | 处理回合制游戏的事件。 不要直接实现此类,而是使用 GKLocalPlayerListener。 | 
Game Center对拟合施加限制
表9-2描述了设计游戏时需要考虑的两个重要限制。
 这些限制可能会发生变化,并且都可以在运行时查询。
| 项目 | 极限 | 运行时访问 | 
|---|---|---|
| 玩家数量 | 16 | 调用 GKMatchRequest类的maxPlayersAllowedForMatchOfType:class方法。 | 
| 拟合数据大小 | 64k | 读取 GKTurnBasedMatch对象的matchDataMaximumSize属性。 | 
加入新的拟合
对于玩家来说,当玩家加入拟合时,新的拟合就开始了。
 与其他形式的匹配一样,您的游戏通过创建一个GKMatchRequest对象来开始这个过程,如创建任何类型的拟合从拟合请求开始。
 拟合请求并不能保证会创建新的拟合;它也可能将玩家拟合到一个现有的拟合中,该拟合的位置为当前玩家的空位置。
您可以选择显示Game Center提供的标准用户界面或实现您自己的自定义用户界面,无论您使用哪种技术,返回到您游戏的拟合始终具有本地玩家作为当前玩家期望轮流。
显示标准配对用户界面
您可以使用GKTurnBasedMatchmakerViewController对象来显示标准用户界面。
本示例中的呈现视图控制器实现了GKTurnBasedMatchmakerViewControllerDelegate模式。
 对于匹配,您需要处理协议的三种方法:
- 当玩家通过点击取消按钮关闭配对屏幕时调用turnBasedMatchmakerViewControllerWasCancelled:委托方法。
 在大多数情况下,您应该简单地返回游戏中的前一个屏幕。
- 当匹配在尝试查找拟合时遇到错误时调用turnBasedMatchmakerViewController:didFailWithError:委托方法。
 例如,设备可能失去了网络连接。
 您的实现也应该返回到游戏的前一个屏幕。
- 当找到或创建拟合时调用turnBasedMatchmakerViewController:didFindMatch:方法。
 拟合对象将返回给您的委托。
 通常,您的游戏会关闭媒人视图控制器,并立即启动自己的用户界面,让玩家玩一个回合。
实现自定义拟合接口
要创建自己的自定义拟合界面,您通常使用GKTurnBasedMatch类上的方法。
 创建一个新的拟合请求,然后使用findMatchForRequest:withCompletionHandler:findMatchForRequest:withCompletionHandler:方法找到一个新的拟合。
 (通常,如果您的游戏正在实现自定义拟合界面,它可能会设置请求对象的其他属性,例如playersToInvite属性。)
 如果找到拟合,变换/转移到游戏屏幕。
Game Center自动支持一个常用场景,当一个拟合结束时,你可以调用它的rematchWithCompletionHandler:实例方法来创建一个新的有相同参与者的拟合。
以编程方式处理邀请
如果拟合请求包含要邀请的玩家列表,那么这些玩家将被添加到新创建的拟合中的玩家列表中。
 但是,直到其中一个玩家成为当前玩家,邀请才会实际传递。
 当这种情况发生时,会向玩家发送消息推送。
 处理通知的过程将在后面的响应拟合通知中描述。
 现在,您需要知道的是,您可以显示自己的界面,允许玩家接受或拒绝拟合邀请。
 您调用拟合对象的acceptInviteWithCompletionHandler:接受邀请的方法或其拒绝邀请的declineInviteWithCompletionHandler:方法。
使用现有匹配项
如果您显示标准的匹配用户界面,那么玩家也会看到现有的匹配。
 玩家可以选择已经进行中的现有拟合并执行其他常见任务。
 如果玩家选择现有拟合,则调用turnBasedMatchmakerViewController:didFindMatch:方法,与创建新拟合时的调用完全相同。
 请注意,在这种情况下,玩家可能不是当前玩家。
玩家可以从标准用户界面中选择退出拟合。
 您的委托必须实现turnBasedMatchmakerViewController:playerQuitForMatch:方法来处理玩家辞职。
 您的游戏必须为辞职玩家设置拟合结果,如有必要,选择其他玩家进行拟合。
 有关详细信息,请参阅当参与者离开拟合时设置拟合结果。
表9-3列出了玩家在拟合时可能想要执行的最常见动作。
| 行动 | 实施 | 
|---|---|
| 查看拟合 | 使用拟合对象作为输入,呈现您的游戏用户界面。 | 
| 删除已完成的拟合 | 调用拟合对象的 removeWithCompletionHandler:方法。 | 
| 退出拟合 | 设置玩家的结果,然后调用方法退出拟合。 请参阅设置拟合结果当参与者离开拟合时。 | 
| 结束所有参与者的拟合 | 设置所有玩家的结果,然后调用比赛的 endMatchInTurnWithMatchData:completionHandler:方法。请参阅结束拟合。 | 
| 创建重赛 | 在结束的拟合上调用 rematchWithCompletionHandler:方法。 | 
检索关于拟合的信息
当玩家决定查看拟合时,你的游戏读取拟合的属性以确定拟合的当前状态,表9-4列出了你需要访问的最常见属性。
| Property | 描述 | 
|---|---|
| matchID | 唯一标识拟合的字符串。 如果您想在其他地方存储拟合特定的数据,请使用此字符串作为键。 您的游戏也可以保存此ID,并在以后使用它来加载此特定拟合。 要加载特定拟合,请调用 loadMatchWithID:withCompletionHandler:class方法。 | 
| status | 说明拟合是否仍在进行中。 | 
| message | 您的游戏设置的文本字符串,用于为拟合提供人类可读的状态。 通常,您在更改当前玩家之前更新此属性。 消息由标准用户界面显示。 如果您显示自定义界面,您也应该显示消息。 | 
| participants | GKTurnBasedParticipant对象的数组。数组中的对象总是以相同的顺序存储。 您读取参与者对象的属性来构建您的用户界面。 这些对象也被用作拟合对象上某些方法的输入。 表9-5列出了用于实现您的游戏的最常见属性。 | 
| currentParticipant | 预计在比赛中出场的下一名球员的参与者对象。 该对象总是存储在 participants数组中的对象之一。 | 
Table 9-5 lists the most common participant properties used to implement your game.
| Property | Description | 
|---|---|
| playerID | 选手的选手标识符,假设该席位已满。 用它来加载选手的显示名称和照片。 | 
| status | 声明此席位是否已填满,如果填满,则声明玩家是否仍在拟合中活跃。 | 
| timeoutDate | 玩家在放弃一个回合之前必须采取行动的时间。 你的游戏决定了一个回合被放弃时会发生什么。 对于某些游戏,放弃的回合可能会结束拟合。 对于其他游戏,你可能会为玩家选择一套合理的默认动作,或者什么都不做。 | 
| matchOutcome | 当玩家离开拟合时,这个属性描述了玩家发生了什么。 玩家可能赢得或失去了拟合。 | 
使用拟合数据
首次创建拟合时,拟合数据为空。
 但是一旦拟合开始,玩家开始轮流,你的游戏就有望提供对你的游戏有意义的拟合数据。
 拟合数据是一个NSData对象,它的大小是有限的,所以你需要有创意,对游戏的数据进行编码,使其尽可能少地填充空间。
一般来说,拟合数据需要存储足够的信息,以便您的游戏能够显示拟合的当前状态。
 如果玩家是当前玩家,那么拟合数据也应该存储足够的数据,以便您的游戏知道玩家可能会采取什么样的回合。
 以下是您在设计拟合数据格式时可以遵循的几个可能策略:
- 仅对玩家动作进行编码:在这种设计中,您的拟合数据简单地由玩家所做的动作组成。
 例如,在国际象棋中,您知道白棋先走,移动总是轮换,一个棋子从一个棋盘位置移动到另一个棋盘位置。
 这使得您可以轻松地将每个移动编码为棋子移动的开始和结束位置。
 其余的数据(谁做了移动)可以完全推断出来。
 当您的游戏加载拟合数据时,它会快速重放移动以生成当前棋盘位置。
 这种策略最适用于具有少量可能动作和每次拟合少量动作的游戏。
 此外,使用这种模型,您的游戏很有可能在其用户界面中重播这些动作,让玩家能够准确地看到其他对手做出了什么动作来使棋盘进入新状态。
 向玩家展示这些动作使玩家很容易理解游戏是如何达到当前状态的。
- 仅对拟合的当前状态进行编码:对于非常复杂的游戏,对游戏进行编码所需的实际状态可能非常大。
 在这种情况下,您可能需要对拟合的当前状态进行编码,而不必担心生成拟合数据的操作。
 对于非常复杂的游戏尤其如此,因为移动列表可能会变得太大,无法放入可用的存储空间。
 建议将此策略作为最后的手段。
 玩家会失去之前拟合回合中发生的所有情况。
 对于回合之间超时较长的游戏,如果玩家无法记住他们正在玩的拟合状态,他们可能会感到无聊或沮丧。
 当玩家同时参与多个比赛时尤其如此。
- 对拟合的当前状态和一组最近的玩家动作进行编码:这是一种混合策略,借鉴了其他两种策略的元素。
 本质上,存储在游戏中心的拟合数据包括拟合的最近快照以及自上次快照拍摄以来对最近动作进行编码的其他数据。
 当记录动作的数据变得太大时,你的游戏会重放其中一些动作来更新拟合快照,然后从动作列表中删除这些动作。
 使用这种策略,你通常需要确定当前玩家看到了哪些动作(基于他们最后一次转弯的时间)。
 当你的游戏使拟合数据变平时,它永远不会删除所有参与者没有看到的任何数据。
 这允许所有参与者看到他们的对手所做的动作。
这里还有一些其他的一般准则:
- 除了最琐碎的游戏之外,避免使用NSCoder类。
 虽然对通用应用程序很有用,但NSCoder类可能无法为您的游戏以足够紧凑的格式归档数据。
 您无法精确预测它产生的归档大小。
 相反,您可以分配自己的记忆屏蔽并执行自己的数据序列化。
- 根据需要对单个项目进行紧凑编码。
 例如,在16人游戏中,您可以将玩家的参与人数编码为4位。
 在代码中平衡紧凑性需求和易读性需求。
 例如,国际象棋位置可以编码为6位,但实际上国际象棋拟合不会接近拟合数据限制。
- 如果游戏在OS X和iOS中运行,请使用网络数据编码技术来避免字节排序问题。
加载拟合数据
当需要加载拟合数据时,调用拟合对象的loadMatchDataWithCompletionHandler:方法来检索拟合数据,当调用完成处理程序时,收到拟合数据,操作完成后,拟合对象的matchData属性也更新为指向同一个数据对象。
保存拟合数据
如果玩家采取了不可撤销的动作(但控制权尚未传递给另一个玩家),请对更新的拟合数据进行编码,并通过调用saveCurrentTurnWithMatchData:completionHandler:方法将其发送到Game Center。
推进拟合状态
最终,当前玩家采取的行动要么结束拟合,要么需要另一个参与者采取行动。
 通常,您的游戏会更新拟合对象的message属性,对拟合数据进行编码,并确定谁将在拟合中下一步行动。
 通常,这意味着按照拟合中所有参与者下一步行动的顺序对其列表进行编码。
 您这样做的原因是,有时玩家会在不放弃的情况下退出拟合;他们只是停止玩游戏。
 您不希望拟合结束,因为它永远停留在等待缺席的玩家。
 相反,您对参与者列表进行编码,以便如果一个参与者放弃一个回合,拟合将前进到另一个参与者。
 使用endTurnWithNextParticipants:turnTimeout:matchData:completionHandler:方法将控制权实际传递给该玩家列表。
当参与者离开拟合时设置拟合结果
随着拟合的进行,参与者可能会离开拟合。
 例如,您的游戏逻辑可能会确定参与者已从拟合中删除。
如果本地玩家从拟合中辞职,并且也是匹配的当前玩家,您的游戏必须调用拟合对象的participantQuitInTurnWithOutcome:nextParticipants:turnTimeout:matchData:completionHandler:方法。
 该方法类似于endTurnWithNextParticipants:turnTimeout:matchData:completionHandler:方法,因为它将控制权交给了另一个参与者。
 因此,您需要更新拟合数据并提供参与者列表。
 然而,您的游戏也提供了一个拟合结果对于刚刚退出拟合的玩家-本质上,一个数值,表明该玩家离开拟合的原因。
 该玩家的参与者对象被更新为包括这个新的拟合状态。
 一旦参与者退出拟合,您的游戏可能不会使该玩家成为当前玩家。
Game Kit 提供了一些标准值,您可以用来设置比赛结果。请参阅设置比赛结果。
例如,在图 9-4 中,鲍勃刚刚被淘汰出局。游戏选择设置结果为 “GKTurnBasedMatchOutcomeFourth”,并让 Mary 成为新的当前玩家。鲍勃仍然可以观看比赛,但不能采取任何行动。
图9-4 Bob已从拟合中消除
 
有时,当玩家不是当前玩家时,他们可能会退出游戏。
 为了解决这个问题,你的游戏调用拟合对象的participantQuitOutOfTurnWithOutcome:withCompletionHandler:方法。
 你提供拟合结果,但不提供新的拟合数据或参与者列表。
结束拟合
最终,你的游戏逻辑将决定拟合结束。
 在结束拟合之前,拟合中的所有参与者都必须有一个有效拟合的结果。
 你的游戏调用拟合对象的endMatchInTurnWithMatchData:completionHandler:方法来正式结束拟合。
 这种方法类似于更新拟合状态的其他方法,但在这种情况下,你不提供参与者列表,因为在此拟合中不允许进一步的操作。
 你更新保存的拟合数据来描述拟合是如何结束的。
 玩家仍然可以从匹配用户界面中选择这个拟合;你的游戏应该显示拟合结局,但不允许玩家采取行动。

响应拟合通知
玩家可以收到关于回合制比赛的通知。
 例如,玩家收到的最常见通知是当该玩家成为拟合的当前玩家时(并且预计会采取行动)。
 在所有情况下,玩家接受通知后,您的游戏将被调用来处理事件。
 它应该处理事件并显示特定于您的游戏的适当用户界面。
您的游戏通过安装事件处理器来接收基于回合的拟合事件。
 与其他类似的处理程序一样,您几乎总是在本地玩家通过身份验证后立即安装此处理程序。
 如果您的游戏是专门为处理事件而启动的,则会立即调用事件处理器。
为了响应事件,您的事件处理器实现了GKTurnBasedEventHandlerDelegate协议定义的方法。
 表9-6列出了事件。
 通过消息推送接收到的事件启动您的游戏或将您的游戏带到前台(如果需要),然后传递给游戏。
 前台事件仅在接收到事件时您的游戏在前台运行时才会传递。
当收到一个事件时,你的游戏应该暂停它正在做的事情,并显示一个用户界面,允许玩家决定是否加载通知的拟合进行显示。
 即使这个拟合已经加载,你也应该重新加载匹配的数据,因为它可能已经被事件更新了。
Table 9-6 Turn-based event handler methods
| 事件 | 事件类型 | 事件处理器方法 | 
|---|---|---|
| 玩家收到邀请 | Push | handleInviteFromGameCenter: | 
| 就轮到玩家了 | Push | handleTurnEventForMatch:didBecomeActive: | 
| 一场拟合结束了 | Push | handleMatchEnded: | 
| 轮到别人了 | Foreground | handleTurnEventForMatch:didBecomeActive: | 
| 有玩家更新了拟合数据 | Foreground | handleTurnEventForMatch:didBecomeActive: | 
| 玩家收到交换请求 | Push | player:receivedExchangeRequest:forMatch: | 
| 玩家取消交换请求 | Push | player:receivedExchangeCancellation:forMatch: | 
| 所有玩家在响应交换时都已响应或超时 | Push | player:receivedExchangeReplies:forCompletedExchange:forMatch: | 
默认情况下,当您的游戏收到通知时,会在游戏图标中添加徽章。
 将GKGameCenterBadgingDisabled添加到您的Info.plist以更改此行为。
 有关详细信息,请参阅Cocoa密钥。
7、将交换添加到基于回合的拟合
将交换添加到您的游戏中,以扩展您的游戏可以做的事情。
 交换允许当前玩家以外的玩家影响当前回合。
 无论是通过与另一个玩家交易物品还是直接影响当前玩家的回合,交换都为您设计游戏的方式提供了更大的灵活性。
交易所解剖
在基本的回合制拟合中,游戏的控制权从一个玩家转移到另一个玩家,遵循应用程序定义的规则。
 只有当前玩家可以影响拟合,所有其他玩家都被降级为观察拟合。
 这对于某些类型的游戏非常有效,但随着游戏转弯变得更加复杂,需要同时互动的多个玩家并不少见。
 当每个玩家必须执行一个小动作才能完成一个转弯时,回合制匹配会很快陷入困境。
 交换为您提供了一种创建复杂转弯的方法,而不会减慢拟合的速度。
使用交换,您可以设计您的游戏,以便当前玩家以外的玩家可以在回合中采取行动。
 交换是交换发起者和一个或多个交换接收者之间的交互。
 交换使用以下工作流程:
- 发起者向接收者发送交换请求。
- 通知收件人他们已收到交换请求。
- 接收者对交换请求采取行动,并将他们的行动发送回发起者。
- 将完成的交换通知发起者。
- 当前玩家会收到交换完成的通知,并更新拟合数据。
发送交换请求
根据你如何设计你的游戏,任何玩家都可以发起交换请求,无论他们是否是当前玩家。
 例如,在玛丽的回合中,弗雷德决定他们要与凯利交换卡片。
 弗雷德选择他们要交易给凯利的卡片以及他们想要的回报。
 然后他们将交换请求发送给凯利。
 请求发送后,向玛丽发送更新,以便更新拟合。
 见图9-5。
图9-5通过交易所初始化交易

当玩家发送交换请求时,需要几条信息才能成功发送请求。
 收集这些信息没有特定的顺序。
 例如,你的游戏可能会在询问交易哪些卡之前询问发送者想和谁交易,或者发送者可以选择给的卡,然后指定谁将收到卡。
- 发件人选择向谁发送交换请求。
- 请求的性质必须确定。
 玩家是要求交换卡片,请求帮助攻击另一个玩家,还是其他什么?
- 必须设置收件人必须响应交换请求的时间。
 您可以允许发件人选择此选项或设置硬时间限制。
您可以发送的数据量被限制在最大1k。
 数据必须足够全面,以使接收玩家的游戏能够以在您的游戏上下文中有意义的方式行动并向玩家呈现数据。
 当发送交换请求时,使用sendExchangeToParticipants:data:localizableMessageKey:arguments:timeout:completionHandler:方法,确保当前玩家也接收到请求数据,以便更新当前拟合。
在任何接收者响应交换请求之前,发送者可以取消该请求。
 当发送者使用cancelWithLocalizableMessageKey:arguments:completionHandler:方法取消交换时,将向所有接收者发送消息推送,告诉他们交换已被取消。
响应交换请求
创建交换请求后,收件人数组中的玩家会收到消息推送,打开游戏会呈现交换消息,收件人可以选择响应交换请求,也可以让其超时。
 图9-6显示凯利接受弗雷德的交易并响应他们的交换请求。
 交换完成后,交换结果被发送给当前玩家。
 交换在当前玩家回合结束时进行对账。
图9-6通过交易所接受交易

接收方使用replyWithLocalizableMessageKey:arguments:data:completionHandler:方法响应交换请求,并将任何相关数据发送回原始发送方。
 您的游戏还需要将交换结果发送给当前玩家,以便他们更新拟合。
2024-06-18(二)



















