Airplay 教程: 一个 Apple TV 多人竞答游戏(2)

这个方法并未完成,仍然还要加入代码。

将下列代码加到上述代码之后:


// Setup window in external screen

self.mirroredWindow = [[UIWindow alloc] initWithFrame:self.mirroredScreen.bounds];

self.mirroredWindow.hidden = NO;

self.mirroredWindow.layer.contentsGravity = kCAGravityResizeAspect;

self.mirroredWindow.screen = self.mirroredScreen;

self.mirroredScreenView = [[SKView alloc] initWithFrame:self.mirroredScreen.bounds];

// Create and configure the scene.

self.mirroredScene = [ATAirPlayScene sceneWithSize:self.mirroredScreenView.bounds.size];

self.mirroredScene.scaleMode = SKSceneScaleModeAspectFill;

// Present the scene.

[self.mirroredScreenView presentScene:self.mirroredScene];

[self.mirroredWindow addSubview:self.mirroredScreenView];

上述代码表明了向新的 screen 中显示内容是多么的容易。首先,用第2个screen 的大小创建了一个 UIWindow。默认 window 是隐藏的,我们必须将 hidden 属性设为 NO 以便显示它。

根据 CALayer 类参考,contentsGravity“用于制定layer 内容的摆放方式及缩放比例”。在 screenModeDidChange: 方法中,当 screen 发生变化,我们通过关闭然后打开来重置window,因此这里必须将 contentsGravity 设置为 aspect fill。然后将传入的 screen 设置为 window 的screen。

接下来,创建一个 window 同样大小的 SKView。SKView 是Sprite Kit 框架中的 UIView。如果你创建的项目未使用 Sprite Kit,你可以用 UIView 代替 SKView。

最终,创建一个  ATAirPlayScene,让 Sprite Kit 显示这个 scene,然后将视图加入 window。

现在还有一个 disableMirroringOnCurrentScreen方法未实现。

在 disableMirroringOnCurrentScreen:方法中加入代码:


[self.mirroredScreenView removeFromSuperview];

self.mirroredScreenView = nil;

self.mirroredScreen = nil;

self.mirroredScene = nil;

self.mirroredWindow = nil;

[self.scene enableStartGameButto?n:NO];

这个方法负责释放相关属性。 enableStartGameButton: 方法使“开始”按钮 disable,这是游戏逻辑的一部分,你暂时不用管它。

“开始” 按钮只会在设备连有第二显示器且玩家不止一个时可用。一旦第二显示器丢失,你需要禁用这个按钮。

最后一步是调用 setupOutputScreen 方法。在viewDidLoad 方法最后加入:


[self setupOutputScreen];

编译运行项目,运行界面如下所示:

在模拟器菜单中,选择“硬件->电视输出->640x480”,然后一个新的电视模拟窗口会打开。

这时,一个模拟器的Bug 会导致程序崩溃。不用担心,这只会在模拟器上发生,在真实的 AppleTV 或显示器上则不会发生。不用退出模拟器,再次运行程序,你会看到两个都显示了:

如果你想在 AppleTV 上看看效果,在真实设备上运行程序,然后从控制中心的AirPlay 菜单中选择 AppleTV。

连接其他玩家

现在是最有意思的部分:让更多的设备连接进来,一起游戏。

GameKit 主要通过 GKSessionn 类实现 p2p 通信。根据这篇文档所述,“GKSession对象负责通过蓝牙或者 Wifi 来发现和连接邻近的 iOS 设备。”

类似其他大多数的通讯协议,GKSession 也存在“服务器”和“客户端”的概念。在文档中,“Session可以扮演成服务器(广播一个 session ID),主动查找其他客户端(广播该 session ID);也可以同时扮演服务器和客户端”。

在这个游戏中,因为只有一个人连接外接显示器,你的设备将以服务器的方式启动session,同时该设备应当连接到外接显示器。如果设备未连接外接显示器,则将启动客户端 session 并开始搜索服务器。

为什么不使用最简单的 p2p 网络?如果每个设备都是 p——即同时扮演服务器和客户端——那么连接设备的管理以及游戏控制会使游戏变得极其复杂。使用单服务器模式则会简单得多。

ATViewController.m 的最后加入:


#pragma mark - GKSession master/slave

- (BOOL)isServer {

return self.mirroredScreen != nil;

}

上面的代码通过 mirroredScreen 属性(该属性在收到外接显示器变更通知时改变)来判断设备是否为服务器。

在启动 Session 之前,有几点需要注意。GKSession 使用委托来通知端点(即p )发现及 p2p 通讯。因此,需要实现委托协议以接收所有 Session 事件。

ATViewController 是整个游戏的控制器,因此也是最适合扮演 GKSession 委托的地方。

打开 ATViewController.h 加入:


#import <GameKit/GameKit.h>

找到这行:


@interface ATViewController : UIViewController

声明对 GKSessionDelegate 的实现:


@interface ATViewController : UIViewController <GKSessionDelegate>

回到 ATViewController.m 在后面加入:


#pragma mark - GKSessionDelegate

/* Indicates a state change for the given peer.  */

- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state { }

/* Indicates a connection request was received from another peer.    Accept by calling -acceptConnectionFromPeer:  Deny by calling -denyConnectionFromPeer:  */

- (void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID { }

/* Indicates a connection error occurred with a peer, which includes connection request failures, or disconnects due to timeouts.  */

- (void)session:(GKSession *)session connectionWithPeerFailed:(NSString *)peerID withError:(NSError *)error { }

/* Indicates an error occurred with the session such as failing to make available.  */

- (void)session:(GKSession *)session didFailWithError:(NSError *)error { }

- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context { }

后面再来填充这些方法。现在暂时空实现,避免编译错误。

关于 GKSessionDelegate 的细节,请参考官方文档 GKSessionDelegate 官方文档

在创建 GKSession之前,还需要加几个属性声明,以便存储某些对象。

加入下列属性声明:


@property (nonatomic, strong) GKSession *gkSession;

@property (nonatomic, strong) NSMutableDictionary *peersToNames;

@property (nonatomic, assign) BOOL gameStarted;

第一个属性用于持有 GKSession,第二个用于存储每个端点的 ID 以及对应的广播名称;第三个用于表示游戏是否开始。 这些属性后面都会用到。

在上面的方法之后新增方法:


- (void)startGKSession {

// Just in case we‘re restarting the session as server

self.gkSession.available = NO;

self.gkSession = nil;

// Configure GameKit session.

self.gkSession = [[GKSession alloc] initWithSessionID:@"AirTrivia"                             displayName:[[UIDevice currentDevice] name]                             sessionMode:self.isServer ? GKSessionModeServer : GKSessionModeClient];

[self.gkSession setDataReceiveHandler:self withContext:nil];

self.gkSession.delegate = self;

self.gkSession.available = YES;

self.peersToNames = [[NSMutableDictionary alloc] init];

if (self.isServer)     {

self.peersToNames[self.gkSession.peerID] = self.gkSession.displayName;

}

}

前两行代码用于释放session,因为 session 即将重建。

接着,初始化 session 对象。SessionID 在 app 中是唯一的,以便所有设备(运行各种 GameKit app 的)能够互相识别。

displayName用于告诉 GKSession 如何当前设备和其他设备。你可以随便指定,但使用设备名称是最自然的做法。最后一个参数是 sessionMode,表明当前设备是服务器还是客户端。然后,将self 对象(ATViewController)指定为负责接收所有来自其他端点的数据,同时作为 session 事件的委托对象。接着,设置 session 的available 为 YES,这将导致 Session 开始在 Wifi 或者 Bluetooth 网络中广播,并开始查找端点。

最终,初始化一个 peerToNames 字典,用于记录其他设备。如果当前设备是一个服务器,应当将自己也加到字典中。

现在,可以在 viewDidLoad 方法中调用这个方法了:


[self startGKSession];

当设备在客户端和服务器模式之间切换时,也需要调用这个方法。

在 setupMirroringForScreen:最后一行加入:


[self startGKSession];

现在,关于 GameKit 的配置已经完成,让我们开始填充委托方法。

在 session:peer:didChangeState:中加入:


BOOL refresh = NO;

switch (state)   {

case GKPeerStateAvailable:

if (!self.gameStarted)       {

[self.gkSession connectToPeer:peerID withTimeout:60.0];

}

break;

case GKPeerStateConnected:

if (!self.gameStarted)       {

self.peersToNames[peerID] = [self.gkSession displayNameForPeer:peerID];

refresh = YES;

}

break;

case GKPeerStateDisconnected:

case GKPeerStateUnavailable:

[self.peersToNames removeObjectForKey:peerID];

refresh = YES;

break;

default:

break;

}

if (refresh && !self.gameStarted)   {

[self.mirroredScene refreshPeers:self.peersToNames];

[self.scene enableStartGameButton:self.peersToNames.count >= 2];

}

无论端点改变为什么状态,这个方法都会执行。端点可能的状态包括:

This method executes whenevera peer changes state. Possible states for peers are:

  • GKPeerStateAvailable: 发现新端点,并且该端点 availabel 是 YES。这时,可以调用 GKSession 的 connectToPeer:withTimeout: of GKSession 方法。如果连接成功,则会发来一个状态为 GKPeerStateConnected 的状态改变通知。
  • GKPeerStateConnected: 端点已连接。这时,你需要将端点名称添加到 peerToNames 字典,然后将 refresh 布尔变量标志为 YES。
  • GKPeerStateDisconnected 和 GKPeerStateUnavailable: 端点断开或者不再有效。这时,需要将端点名称从 peerToNames 中移除,然后将 refresh 标志为YES。

最终,如果游戏还未开始,同时 refresh 标志为 YES,将变更过的peerToNames 发送给第二显示窗口的 scene,当有两个以上的玩家连接时,使“开始”按钮可用。

为了建立连接,一个端点需要向另一个端点请求连接——这在上面代码中已经这样做了。然后对端必须接受连接请求,这样双方才能通讯。

在 session:didReceiveConnectionRequestFromPeer:方法中加入以下代码:


if (!self.gameStarted)     {

NSError *error = nil;

[self.gkSession acceptConnectionFromPeer:peerID error:&error];

if (error)         {

NSLog(@"Error accepting connection with %@: %@", peerID, error);

}

}     else     {

[self.gkSession denyConnectionFromPeer:peerID];

}

当其他设备调用 connectToPeer:withTimeout: 方法连接一个设备时,该设备的这个委托方法被调用。如果游戏尚未开始,接受这个连接并打印可能发生的错误。如果游戏已经开始,拒绝该连接。

Airplay 教程: 一个 Apple TV 多人竞答游戏(2)

时间: 2024-10-29 03:58:32

Airplay 教程: 一个 Apple TV 多人竞答游戏(2)的相关文章

Airplay 教程: 一个 Apple TV 多人竞答游戏(1)

原文http://twitter.com/share?url=http%3A%2F%2Fbit.ly%2F1iRy7Gq&via=rwenderlich&text=AirplayTutorial%3A An Apple TV Multiplayer QuizGame&related=gpambrozio&lang=en&count=horizontal&counturl=http%3A%2F%2Fwww.raywenderlich.com%2F57161%2

Airplay 教程: 一个 Apple TV 多人竞答游戏(4)

现在,客户端需要根据服务器发来的命令进行动作. 在方法 receiveData:fromPeer:inSession:context:末尾加入代码: if ([commandReceived hasPrefix:kCommandQuestion] && !self.isServer)   { NSString *answersString = [commandReceived substringFromIndex:kCommandQuestion.length]; [self.scene

Airplay 教程: 一个 Apple TV 多人竞答游戏(3)

一旦设备接受了连接请求,对端立即会收到一个状态为GKPeerStateConnected 的Session 状态改变通知,然后这个设备会加到玩家列表中. 要测试这个 app,你需要运行两个 app 的拷贝:一个是服务器,一个是客户端.最简单的办法是用模拟器作为服务器,而用一台物理设备作为客户端. 如果你没有开发者账号,你将无法在真机上进行调试,这样你可能想在同一个机器上运行两个模拟器.这不是不可以,但就不是那么简单了.如果你想这样做,请参考 stack overflow 上的这个方法. 在模拟器

IC知识竞答,对10题赢10万奖金,谁是半导体界的最强大脑?

欢迎来到『摩尔英雄』大会,知识来挑战,智慧值千金,对,我们就是来撒钱的! 1『摩尔英雄』是什么?摩尔英雄是摩尔精英旗下摩尔直播APP中全新推出的针对半导体人的知识类公平竞答游戏节目,你只需动动手指,选选答案,通过参与答题,就可以赢走现金大奖. 2 参与方式用户下载『摩尔直播』APP,登录即可参与答题.每天中午12:30,『摩尔英雄』大会开始,答对10道题,即可平分奖金,奖金可以直接提现到你的微信账户. 3 关于竞答题目『摩尔英雄』大会的所有竞答题目都只与半导体相关,涉及有关半导体的历史.技术.新

苹果将通过新Apple TV打造电视游戏平台 欲发力家庭游戏(转)

据<纽约时报>报道,9月10日凌晨1时举行的苹果发布会上将会公布新版Apple TV设备,还会推出TV版App Store.新设备以游戏作为主要卖点,图形性能将大幅提升. 苹果2015年秋季发布会即将在北京时间9月10日凌晨1时举行,除了一年一更新的iPhone之外,今年还传出了苹果将会更新Apple TV产品线的消息:得到久违升级的Apple TV将不再只是一个观看视频的机顶盒,还将在电视游戏上发力. 据<纽约时报>报道,接近新Apple TV项目的线人纷纷表示,用iPhone和

其实苹果已配备手柄 若Apple TV成为主机

自苹果宣布iOS 7撑持手柄认证之后,外设厂商纷繁出动推出自个的试水产品.到现在为止咱们看到了MOGA.罗技.雷蛇以及赛睿等iOS 7手柄.信任绝大多数玩家都以为这些手柄只是为iPhone.iPad又或者是iPod touch打造的外设,实际上很多人都疏忽了:Apple TV也有能够从中受惠. 咱们此前现已听过这样的风闻:下一代Apple TV将会搭载一个全新的操作系统与世人见面.不仅如此,新一代Apple TV将会具有自个的App Store--正如当前的iPhone和iPad相同.除此之外,

[转]从 Apple TV 看电视的进化

电视被许多人吐槽为 “几十年没变过的东西”,因此苹果也被寄予厚望能改变这件事物.可惜的是,这种期望在空中飘了这么久,苹果也没玩出多少花样,直到这次发布会 Apple TV 才有了一些值得期待的改进. 传统电视 在 PC 和智能手机成为主流设备前,电视大概是我们能拥有的唯一一款大规模流行的科技消费产品.电视的消费群体以 “家庭” 为单位,几乎没什么计算和联网的能力,更多的是一款输出的媒介. 尽管以今天的目光审视起来,传统电视的科技含量少得可怜,但这款设备曾经带给人们的贡献是不可抹杀的.在很多年轻人

你是一个“会说话”的人吗?

人心忧虑,屈而不伸,一句良言,使心欢乐.--箴言12:25 愤 怒的那一个瞬间,智商是零,过一分钟后恢复正常.人的优雅关键在于控制自己情绪,用嘴伤害人,是最愚蠢的一种行为.我们的不自由,通常是因为来自内心的不 良情绪左右了我们.一个能控制住不良情绪的人,比一个能拿下一座城池的人更强大.水深则流缓,语迟则人贵.我们花了两年时间学说话,却要花数十年时间学会 闭嘴.可见:说,是一种能力:不说,是一种智慧. 说话的艺术: 1.急事,慢慢地说. 『你们各人要快快地听,慢慢地说,慢慢地动怒:』(雅各书一19

Apple TV是HomeKit的中枢?

CES2015展会上开发商展示了多款HomeKit设备,包括车库门开启器,智能灯泡插座,智能门锁等等.这些Wi-Fi设备有一个共同的特点:用户外出之时,要想使用iPhone通过Siri控制家中这些HomeKit设备,前提是必须有一台Apple TV. 比如用户在机场候机厅,通过Siri语音命令要求关掉家中浴 室仍然开着的灯泡,那么必须通过家中仍在运行的Apple TV来完成,显然,苹果试图将Apple TV作为HomeKit的中枢产品.不过CES2015展会上两三家开发商私下透露,用户外出仍然可