Apple Watch -- 作为游戏开发者的你准备好了么? (上)

http://inpla.net/thread-8145-1-1.html



Apple Watch -- 作为游戏开发者的你准备好了么? (上)


对于去年(2014年)苹果的产品线来说,最具创新的或许应该算得上Apple Watch了,在此之前各个网站和分析师的猜测就已经漫天飞舞。 关注点无不外乎是在手表和iTV这两个产品上。 2014年9月9日上午10点(北京时间9月10日凌晨1点),苹果2014年秋季新品发布会在总部所在地——加州库比蒂诺当地的Flint表演艺术中心举行,会上苹果CEO宣布发布全新的产品:Apple Watch。 上市时间是2015年预计第一或者第二季度。 紧接着在14年的11月份,苹果在开发者网站上给出了Apple Watch的SDK: WatchKit。 转眼到了2015年, 为了配合Apple Watch相应的iOS测试版已经更新到了8.2, XCode更新到了 6.2 Beta3。 Apple Watch离我们已经越来越近了, 目前的Apple Watch是如何运作的? 上面的程序要如何开发? 对于游戏开发者而言,又能在上面实现哪些和游戏相关的功能? 这里就从软件开发的角度而言,说说Apple Watch的那些事儿。


硬件

切入正题之前,我们先来看看参数。

Apple Watch有两种尺寸:38mm和42mm,对应的分辨率是 272*340, 312*390。 宽高比 4比5。 作为智能手表的初代产品,跑上来就玩两个分辨率会不会让某些完美主义的程序员头痛不已呢...

处理器官方的叫法是 Apple S1,对于这款处理器目前信息还是相当的匮乏,只知道苹果将其描述成SiP(System in Package), 里面集成了运算处理模块,内存模块,存储模块,无线模块,传感模块,IO模块——几乎就是把所有的东西集成到了一起。

 

软件开发
作为软件开发者,我们最关心的自然还是开发环境和例子程序以便快速了解Apple Watch的特性。 下面我们就通过一个例子一步一步的来说明。

目前可以在Apple Watch上运行的内容可以分成三个类型: Glances, Actionable Notifications和Watch App

先来说说简单的Glance和Actionable Notifications


Glance简单的说就是只读信息,说它是只读,是因为你不能和Glance有任何的交互,各位在学校里都有梦想的女神不? 可远观而不可亵玩焉,这个就是Glance,给了你信息就好了,没有其他。



Actionable Notifications 相比Glance来说多了一些很简单的互动, 比如HomeKit通知你说家里的灯忘记关了,附带一个选项:关灯。  好比女神有天突然跑过来叫你, 某某某,帮我把这堆厚重的教材拿到教室发给同学们好吗?心里那个美啊.....,然后屁颠屁颠的就去了。
Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第1张图片


最后是WatchKit App

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第2张图片
WatchKit Apps就不一样了,既然叫App,那自然可以做很多事情:显示文字,展现图片,可以有列表选择,有按钮输入反馈等等。终于追到了心目中的女神能不开心嘛,想尽办法一起互动啊, 逛街,买东西,吃饭,看通宵电影(你懂的....)


Glances和Actionable Notifications比较简单,这里就不说了。
下面的例子着重介绍如何建立WatchKit Apps。

如果Xcode还没有升级,请到开发者网站上下载最新的Beta版本,我这里使用的是Xcode 6.2 Beta3,里面有iOS 8.2 Beta SDK和相应的模拟器。

创建一个新的项目,选择iOS Application, 至于 template无所谓, 这里选择的是Game

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第3张图片

项目工程出现,然后在菜单中选择File→New→Target

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第4张图片
就可以看到WatchKit App选项了

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第5张图片
添加完成之后我们可以看到工程里多了两个项目:WatchKit Extension和WatchKit App。 等等...iOS App和WatchKit App: 你和你的女神一对甜蜜的恋人间为啥会有一个第三者? Extension是何许人也?

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第6张图片

为了弄清里面的关系,我们需要着重说明下WatchKit的结构体系。

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第7张图片
在增加了Watch Apps的Target之后,这个Extension就会包含在iOS App里面,用户下载安装iOS App的时候,会一并把Extension安装到iPhone里面。 而Extension中又包含了WatchKit App, 这部分内容会被自动安装到Apple Watch中。 也就是说一个我们发布的程序最后兵分两路,部分驻留在iPhone上,而只负责显示和输入的部分放在了Apple Watch上。

当程序运行起来以后,WatchKit App大部分时间是和Extension进行交互。他们之间的交互,是要通过WatchKit进行的,我们的工作,其中很重要的一部分就是如何使用WatchKit让他们之间好好沟通,达到互动的目的。


注意上面两张图片,在iOS App和WatchKit Extension中,可以看到除了Resources之外,还有Code,但是在WatchKit App中却没有!这说明什么?这说明Watch App没有运算能力, 也就是说它本身并不能处理任何事物,做任何决定,这些事情都是Extension代之完成的。 他是不过是Extension的傀儡而已。 好不容易追到女神了,却发现她及其没有主见,啥都要听她爸的, 逛街她爸要跟着去, 买东西吃饭她爸要陪着, 连看场电影她爸也要坐中间把你们隔开。 想死的心都有了吧。


回到例子, 为了让iOS App, Extension, Watch App能够相互正常通信,工程里还需要进行一些额外的设置。
首先:在Target中,先选择iOS App,在General中的Team选项中选择现有的组(非None选项)。

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第8张图片

对Extension和WatchKit App也重复上面的步骤


其次:在Capabilities中,打开App Groups选项,增加一个Group并选中

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第9张图片

对于Extension也做同样的操作并选中刚才创建的那个Group。如果在此过程中出现红色感叹号就点击“Fix issue”,直到全部正确为止。


完成了设置之后我们就可以设计WatchKit App的界面了, 找到Interface.storyborad,加入需要的元素

我们在这里放上一个Label,一个Image和一个Button。 给这些控件添加对应的IBOutlet变量和IBAction函数
下面的代码出现在Extension项目的InterfaceController.h中


  1. //  InterfaceController.h
  2. //  WatchKitTutorial WatchKit Extension
  3. //
  4. //  Created by Bowie Xu on 15/1/14.
  5. //  Copyright (c) 2015年 CoconutIsland. All rights reserved.
  6. //

  7. #import <WatchKit/WatchKit.h>
  8. #import <Foundation/Foundation.h>

  9. @interface InterfaceController : WKInterfaceController
  10. @property (weak, nonatomic) IBOutlet WKInterfaceImage *watchImage;
  11. @property (weak, nonatomic) IBOutlet WKInterfaceButton *watchButton;
  12. @property (strong, nonatomic) NSArray*    buttonTitles;
  13. @property (assign, nonatomic) int       titleIndex;
  14. - (IBAction)WatchButtonClicked;


  15. @end




大家注意到了没有, 这的Image和Button并不是UIImage和UIButton,而是以WKInterface开头的类。 没错,这个就是WatchKit类库了,其中开头的WK就是WatchKit的缩写。

搭好空的框架后我们就可以编译启动看看结果了,点选Watchkit App,目标选iphone5还是iphone6都行,然后运行!!

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第10张图片

如果只看到了iPhone模拟器而没看到Watch的,请切换到模拟器程序,在其菜单中检查Hardware是否选中了扩展显示设备(话说我初次玩WatchKit在这里卡了很久,总以为自己项目不对,苹果你就不能自动检测项目然后自动弹出Apple Watch模拟器么)

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第11张图片

最后结果如下:

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第12张图片
如果你也能看到这个结果,恭喜你,已经有一个可以运行的WatchKit App程序了。后面我们添加代码,让其响应按钮,显示图片。InterfaceController.m中添加代码如下:

  1. //  InterfaceController.m
  2. //  WatchKitTutorial WatchKit Extension
  3. //
  4. //  Created by Bowie Xu on 15/1/14.
  5. //  Copyright (c) 2015年 CoconutIsland. All rights reserved.
  6. //

  7. #import "InterfaceController.h"


  8. @interface InterfaceController()

  9. @end


  10. @implementation InterfaceController

  11. - (void)awakeWithContext:(id)context {
  12.     [super awakeWithContext:context];
  13.     _buttonTitles = [NSArray arrayWithObjects:@"From Watch App", @"From Extension", @"From iOS App",nil];
  14.     // Configure interface objects here.
  15.     _titleIndex = 0;
  16.     [self SetButtonTitle:_titleIndex];
  17. }

  18. - (void)willActivate {
  19.     // This method is called when watch view controller is about to be visible to user
  20.     [super willActivate];
  21. }

  22. - (void)didDeactivate {
  23.     // This method is called when watch view controller is no longer visible
  24.     [super didDeactivate];
  25. }

  26. - (void) ShowPicFromWatchKitApp
  27. {
  28.    
  29. }

  30. - (void) ShowPicFromExtension
  31. {
  32.    
  33. }

  34. - (void) ShowPicFromiOSApp
  35. {
  36.    
  37. }

  38. - (void) SetButtonTitle:(int)titleIndex
  39. {
  40.     [_watchButton setTitle:[_buttonTitles objectAtIndex:titleIndex]];
  41. }

  42. - (void) IncreaseIndex
  43. {
  44.     _titleIndex++;
  45.     _titleIndex %= [_buttonTitles count];
  46. }


  47. - (IBAction)WatchButtonClicked {
  48.     switch (_titleIndex) {
  49.         case 0:
  50.             [self ShowPicFromWatchKitApp];
  51.             break;
  52.         case 1:
  53.             [self ShowPicFromExtension];
  54.             break;
  55.         case 2:
  56.             [self ShowPicFromiOSApp];
  57.             break;
  58.         default:
  59.             break;
  60.     }
  61.    
  62.     [self IncreaseIndex];
  63.     [self SetButtonTitle:_titleIndex];
  64. }

  65. @end

复制代码

然后运行程序,在模拟器中点击按钮,可以看到它的title在From WatchApp, From Extension, From iOS App之间切换。到这里你也许会问,我在手表上点击按钮,但是代码却是在Extension中,也就是在iPhone中执行。这之间是怎么联系的呢?答案是:你无需知道! 这中间的一切,全部由WatchKit后台处理的,是不是觉得很爽。

按键搞定,最后我们来看看图片的显示,WatchKit中的图片类可以显示单张静态图片,也可以显示序列帧动画。 根据图片资源所在位置不同,我们有三种加载图片的方法。 分别对应资源在Watch App中,在Extension和在iOS App中。

首先在工程的三个项目中添加图片资源,图片大家随意,在这个例子中,Watch App和Extension我放一张图片,而iOS App中我借用苹果官方的例子,放入360张图片做动画。


先来看看最容易处理的情况:图片资源在WatchKit App中。 对于这种情况, Extension和WatchApp之间只需要传输很少量的数据:传输的是图片资源的名字而已。 在WatchApp中的WatchKit收到这个名字后会在包中寻找同名文件并自动加载显示。 因此相应的代码如下:
  1. - (void) ShowPicFromWatchKitApp
  2. {
  3.     [_watchImage setImageNamed:@"AppleImage.png"];
  4. }


其中的setImageNamed就是直接显示图片了。

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第13张图片


如果图片在Extension中,那流程会稍微复杂些,一行代码变成了两行:
  1. - (void) ShowPicFromExtension
  2. {
  3.     UIImage* image = [UIImage imageNamed:@"AppleRainbow.png"];
  4.     [_watchImage setImage:image];
  5. }

复制代码

代码里出现了UIImage,说明一开始图片的加载和WatchKit是无关的,也就是说是Extension在iPhone运行的结果。然后再设置到WatchKit App中。

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第14张图片
相比前一种方式,这里传输的不是文件名而是Extension中的整个图片资源了。
是不是觉得也还好,代码不复杂吧。 最后我们来个大跃进,看看如何从iOS App中拿到一系列的图片资源并在WatchKit App中播放动画。

要和iOS App通讯,必须调用
  1. + (BOOL)openParentApplication:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo, NSError *error)) reply;    // launches containing iOS application on the phone. userInfo must be non-nil

复制代码

函数, 由Extension发起请求。 注意:这里还是Extension而不是WatchKit App,前面说过WatchKit App是没有任何运算能力的。 在iOS App的AppDelegate中添加
  1. - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply NS_AVAILABLE_IOS(8_2);


进行响应。

Apple Watch -- 作为游戏开发者的你准备好了么? (上)_第15张图片

牵扯到三方,中间的沟通自然多了不少,因此这个也是最耗时的方法。

Extension中的请求代码
  1. -(void) _RequestData:(NSNumber*) nindex {
  2.     //NSData* indexdata = [NSData dataWithBytes:&_AnimationIndex length:sizeof(_AnimationIndex)];
  3.     //NSDictionary *request = [NSDictionary dictionaryWithObject:indexdata forKey:@"request"];
  4.     int index = [nindex intValue];
  5.     if (index>=MAXKEYS || index<0) {
  6.         return;
  7.     }
  8.    
  9.     NSDictionary *request = @{@"request":@"PIC"};
  10.     NSString* key = [NSString stringWithFormat:@"%d", index];
  11.    
  12.     [_AnimationPics removeObjectForKey:key];
  13.    
  14.     [InterfaceController openParentApplication:request reply:^(NSDictionary *replyInfo, NSError *error) {
  15.         
  16.         if (error) {
  17.             NSLog(@"%@", error);
  18.             [_AnimationPics removeObjectForKey:key];
  19.         } else {
  20.             NSData* data = [replyInfo objectForKey:@"PIC"];
  21.             NSArray* picarray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
  22.             [_AnimationPics setObject:picarray forKey:key];
  23.             
  24.             //start animation
  25.             if (_AnimationTimer == nil) {
  26.                 _AnimationTimer = [NSTimer scheduledTimerWithTimeInterval:0.2f target:self selector:@selector(SetAnimationIndex) userInfo:nil repeats:YES];
  27.             }
  28.             
  29.         }
  30.     }];
  31.    
  32. }

  33. iOS App中的响应代码
  34. #define PICCOUNT 30
  35. - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
  36.    
  37.     if ([[userInfo objectForKey:@"request"] isEqualToString:@"PIC"]) {
  38.         
  39.         NSLog(@"containing app received message from watch");
  40.         
  41.         
  42.         NSMutableArray* marray = [NSMutableArray arrayWithCapacity:PICCOUNT];
  43.         for (int i=0; i<PICCOUNT; i++) {
  44.             UIImage* image = [UIImage imageNamed:[NSString stringWithFormat:@"glance-%[email protected]", i+_picindex]];
  45.             NSData* imagedata = UIImagePNGRepresentation(image);
  46.             [marray addObject:imagedata];
  47.         }
  48.         
  49.         _picindex += PICCOUNT;
  50.         _picindex %= 360;
  51.         
  52.         NSData* imageData = [NSKeyedArchiver archivedDataWithRootObject:marray];
  53.         
  54.         //NSData* imageData = UIImageJPEGRepresentation(image, 1.0f);
  55.         NSDictionary *response = [NSDictionary dictionaryWithObject:imageData forKey:@"PIC"];//@{@"response" : @"Watchkit"};
  56.         
  57.         reply(response);
  58.     }
  59. }

不出所料,让iOS App传递祯序列是很耗时间的操作,在例子程序中我尝试使用了Double Buffer,但是效果也不明显。

最后程序运行起来是这个样子滴....




前面说了这么多,又是代码又是图片的, 有人要问了,为啥苹果设计一个这么蛋疼的Extenion存在? 为啥不让Watch App 和iOS App直接通讯? 我的理解是Extension是为了处理轻事物而存在的,这样iOS可以在后台执行Extension而不会消耗太多的资源。 也正是因为如此,Extension被设计成只能在后台运行很短的一段时间,如果应用程序需要诸如定位之类的长时间的运算,苹果的官方建议是交给iOS App来完成。

到这里Apple Watch和WatchKit的介绍就告一段落了。 下篇我们着重探讨WatchKit在游戏开发中的运用。

最后给出一张 WatchKit和UIKit的参照对比表格:

WatchKit
UIKit
WKInterfaceController
UIViewController
WKUserNotificationInterfaceController
UIApplicationDelegate + UIAlertController
WKInterfaceDevice
UIDevice
WKInterfaceObject
UIView
WKInterfaceButton
UIButton
WKInterfaceDate
UILabel + NSDateFormatter
WKInterfaceGroup
UIScrollView
WKInterfaceImage
UIImageView
WKInterfaceLabel
UILabel
WKInterfaceMap
MKMapView
WKInterfaceSeparator
UITableView.separatorColor / .separatorStyle
WKInterfaceSlider
UIStepper + UISlider
WKInterfaceSwitch
UISwitch
WKInterfaceTable
UITableView
WKInterfaceTimer
UILabel + NSDateFormatter + NSTimer



本文的参考连接:

1:苹果开发者网站: https://developer.apple.com/watchkit/
2:WatchKit-NSHipster: http://nshipster.com/watchkit/

文中的例子程序源码:
https://github.com/CoconutIslandStudio/WatchKitTutorial.git


你可能感兴趣的:(apple,ios,watch,watchKit)