现在,客户端需要根据服务器发来的命令进行动作。
在方法 receiveData:fromPeer:inSession:context:末尾加入代码:
if ([commandReceived hasPrefix:kCommandQuestion] && !self.isServer) { NSString *answersString = [commandReceived substringFromIndex:kCommandQuestion.length]; [self.scene startQuestionWithAnswerCount:[answersString integerValue]]; } |
假设永远不会出现9个以上的答案,则最后一个字符就是答案的数目。answersString保存这个字符,你可以将它转换成数字,然后向 startQuestionWithAnswerCount:方法传递这个数值,这样屏幕会显示相同数目的答案按钮。
编译运行项目,先在模拟器上运行,再在真机上运行。当“Start Game”按钮出现时,轻触该按钮。你将看到如下界面:
在真机上,显示了和模拟器一样的内容。根据试题的内容,可能显示的按钮数目也会不同。在模拟器或真机上点击某个按钮,屏幕将显示为如下界面:
除此之外,程序不会有其他反应。因为当你点击选项按钮后, ATMyScene中的逻辑只包含了删除按钮并调用ATViewController方法——但实际上这个方法还是空实现。
打开 ATViewController.m,找到sendAnswer: 方法,加入以下代码:
[self sendToAllPeers:[kCommandAnswer stringByAppendingString: [NSString stringWithFormat:@"%ld", (long)answer]]]; |
代码很简单,发送 “answer”命令及用户选定的答案(索引)。
服务器通过 receiveData:fromPeer:inSession:context:来接收信息。
在这个方法末尾加入:
if ([commandReceived hasPrefix:kCommandAnswer] && self.isServer) { NSString *answerString = [commandReceived substringFromIndex:kCommandAnswer.length]; NSInteger answer = [answerString integerValue]; if (answer == self.currentQuestionAnswer && self.currentQuestionAnswer >= 0) { self.currentQuestionAnswer = -1; NSInteger points = 1 + [self.peersToPoints[peer] integerValue]; if (points > self.maxPoints) { self.maxPoints = points; } self.peersToPoints[peer] = @(points); [self endQuestion:peer]; } else if (++self.currentQuestionAnswersReceived == self.peersToNames.count) { [self endQuestion:nil]; } } |
先判断收到的信息是否是 “answer”命令。如果是并且答案与正确答案系统,将currentQuestionAnswer重置为-1以便下次出题。然后为玩家加分,同时更新当前的最高分记录。最后,调用 endQuestion 方法。
如果答案不正确,并且已经收到的答案与玩家数目一致,则当前提问结束,以nil 为参数调用 endQuestion 方法。
接下来实现 endQuestion:方法。
在 receiveData:fromPeer:inSession:context:方法下面添加方法:
- (void)endQuestion:(NSString *)winnerPeerID { [self sendToAllPeers:kCommandEndQuestion]; NSMutableDictionary *namesToPoints = [[NSMutableDictionary alloc] initWithCapacity:self.peersToNames.count]; for (NSString *peerID in self.peersToNames) { namesToPoints[self.peersToNames[peerID]] = self.peersToPoints[peerID]; } [self.mirroredScene endQuestionWithPoints:namesToPoints winner:winnerPeerID ? self.peersToNames[winnerPeerID] : nil]; [self.scene endQuestion]; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 4 * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [self startQuestion]; }); } |
这个方法先发送消息给客户端,让其结束当前提问;这个待会再讲。然后,创建一个dictionary,用于存储每个玩家和对应的得分并发送给第二显示器,以便显示那个玩家答对题以及当前得分情况。
最后,延迟4秒钟后运行语句块,调用 startQuestion 开下次出题。
客户端需要对 end question 命令进行处理。
在 receiveData:fromPeer:inSession:context:方法末尾加入代码:
if ([commandReceived isEqualToString:kCommandEndQuestion] && !self.isServer) { [self.scene endQuestion]; } |
客户端一旦收到该命令,就会调用 endQuestion 方法,该方法结束本轮提问并隐藏答案按钮。
编译运行程序,先模拟器,后真机。开始游戏,随便回答一些问题。如果答案不正确,你需要等真机和模拟器都答题之后才能开始下一题。
现在看可以看到类似如下的屏幕:
如果模拟器长时间运行,你可能会遇到程序崩溃的情况。这是因为代码并没有处理试题已经问完(即游戏结束)的情况。这是最后一块工作了!
在 startQuestion 方法头部加入以下代码:
if (self.questions.count == 0) { NSMutableString *winner = [[NSMutableString alloc] init]; for (NSString *peerID in self.peersToPoints) { NSInteger points = [self.peersToPoints[peerID] integerValue]; if (points == self.maxPoints) { if (winner.length) { [winner appendFormat:@", %@", self.peersToNames[peerID]]; } else { [winner appendString:self.peersToNames[peerID]]; } } } [self.mirroredScene setGameOver:winner]; return; } |
如果你答案是有题目,该方法会生成赢家的名字(一个或多个赢家)——然后显示在第二显示器上。
编译运行程序,当你玩完整个游戏,你会看到如下界面:
怎么样,你的 CS 测试是否及格?
接下来做什么?
恭喜你——你刚才用 Game Kit 编写了一个使用了外接显示器的多玩家的客户端/服务端游戏!最终完成的项目在此处下载。
现在,你已经具备了使用 GameKit 编写客户/服务器游戏的基础,也知道如何使用扩展屏幕,对于某些游戏来说,这样的好处是不言而喻的。你可以用第二屏幕作为游戏视图,而将设备作为控制终端,或者在设备上显示一些附加信息,就好比HUD(平视显示器)一样。
GameKit 的 P2P 通信为多玩家游戏或 app 打开了一扇大门,你可以看到,通过利用苹果提供的APIs 实现多人控制是如何的用容易。但在 iOS7 中,GKSession 已被 Multipeer Connectivity 框架替代。这两个框架很像,你的GKSession 知识将很平滑地过度到新的 MCSession 及其相关的类上。
希望你喜欢本教程。用 AirPlay 和 GameKit p2p 开发出你想要的东西吧!
如果你有任何问题和建议,请在下面给我留言。
Airplay 教程: 一个 Apple TV 多人竞答游戏(4)