天天看點

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的特性。 下面我們就通過一個例子一步一步的來說明。

目前可以在Apple Watch上運作的内容可以分成三個類型: Glances, Actionable Notifications和Watch App

先來說說簡單的Glance和Actionable Notifications

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

Glance簡單的說就是隻讀資訊,說它是隻讀,是因為你不能和Glance有任何的互動,各位在學校裡都有夢想的女神不? 可遠觀而不可亵玩焉,這個就是Glance,給了你資訊就好了,沒有其他。

Actionable Notifications 相比Glance來說多了一些很簡單的互動, 比如HomeKit通知你說家裡的燈忘記關了,附帶一個選項:關燈。  好比女神有天突然跑過來叫你, 某某某,幫我把這堆厚重的教材拿到教室發給同學們好嗎?心裡那個美啊.....,然後屁颠屁颠的就去了。

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

最後是WatchKit App

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

WatchKit Apps就不一樣了,既然叫App,那自然可以做很多事情:顯示文字,展現圖檔,可以有清單選擇,有按鈕輸入回報等等。終于追到了心目中的女神能不開心嘛,想盡辦法一起互動啊, 逛街,買東西,吃飯,看通宵電影(你懂的....)

Glances和Actionable Notifications比較簡單,這裡就不說了。 下面的例子着重介紹如何建立WatchKit Apps。

如果Xcode還沒有更新,請到開發者網站上下載下傳最新的Beta版本,我這裡使用的是Xcode 6.2 Beta3,裡面有iOS 8.2 Beta SDK和相應的模拟器。

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

建立一個新的項目,選擇iOS Application, 至于 template無所謂, 這裡選擇的是Game

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

項目工程出現,然後在菜單中選擇File→New→Target

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

就可以看到WatchKit App選項了

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

添加完成之後我們可以看到工程裡多了兩個項目:WatchKit Extension和WatchKit App。 等等...iOS App和WatchKit App: 你和你的女神一對甜蜜的戀人間為啥會有一個第三者? Extension是何許人也?

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

為了弄清裡面的關系,我們需要着重說明下WatchKit的結構體系。

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

在增加了Watch Apps的Target之後,這個Extension就會包含在iOS App裡面,使用者下載下傳安裝iOS App的時候,會一并把Extension安裝到iPhone裡面。 而Extension中又包含了WatchKit App, 這部分内容會被自動安裝到Apple Watch中。 也就是說一個我們釋出的程式最後兵分兩路,部分駐留在iPhone上,而隻負責顯示和輸入的部分放在了Apple Watch上。

當程式運作起來以後,WatchKit App大部分時間是和Extension進行互動。他們之間的互動,是要通過WatchKit進行的,我們的工作,其中很重要的一部分就是如何使用WatchKit讓他們之間好好溝通,達到互動的目的。

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

注意上面兩張圖檔,在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 -- 作為遊戲開發者的你準備好了麼? (上)

對Extension和WatchKit App也重複上面的步驟

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

其次:在Capabilities中,打開App Groups選項,增加一個Group并選中

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

對于Extension也做同樣的操作并選中剛才建立的那個Group。如果在此過程中出現紅色感歎号就點選“Fix issue”,直到全部正确為止。

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

完成了設定之後我們就可以設計WatchKit App的界面了, 找到Interface.storyborad,加入需要的元素

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

我們在這裡放上一個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 -- 作為遊戲開發者的你準備好了麼? (上)

如果隻看到了iPhone模拟器而沒看到Watch的,請切換到模拟器程式,在其菜單中檢查Hardware是否選中了擴充顯示裝置(話說我初次玩WatchKit在這裡卡了很久,總以為自己項目不對,蘋果你就不能自動檢測項目然後自動彈出Apple Watch模拟器麼)

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

最後結果如下:

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

如果你也能看到這個結果,恭喜你,已經有一個可以運作的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. - (void) ShowPicFromExtension
  30. {
  31. }
  32. - (void) ShowPicFromiOSApp
  33. {
  34. }
  35. - (void) SetButtonTitle:(int)titleIndex
  36. {
  37.     [_watchButton setTitle:[_buttonTitles objectAtIndex:titleIndex]];
  38. }
  39. - (void) IncreaseIndex
  40. {
  41.     _titleIndex++;
  42.     _titleIndex %= [_buttonTitles count];
  43. }
  44. - (IBAction)WatchButtonClicked {
  45.     switch (_titleIndex) {
  46.         case 0:
  47.             [self ShowPicFromWatchKitApp];
  48.             break;
  49.         case 1:
  50.             [self ShowPicFromExtension];
  51.             break;
  52.         case 2:
  53.             [self ShowPicFromiOSApp];
  54.             break;
  55.         default:
  56.             break;
  57.     }
  58.     [self IncreaseIndex];
  59.     [self SetButtonTitle:_titleIndex];
  60. }
  61. @end

複制代碼

然後運作程式,在模拟器中點選按鈕,可以看到它的title在From WatchApp, From Extension, From iOS App之間切換。到這裡你也許會問,我在手表上點選按鈕,但是代碼卻是在Extension中,也就是在iPhone中執行。這之間是怎麼聯系的呢?答案是:你無需知道! 這中間的一切,全部由WatchKit背景處理的,是不是覺得很爽。

按鍵搞定,最後我們來看看圖檔的顯示,WatchKit中的圖檔類可以顯示單張靜态圖檔,也可以顯示序列幀動畫。 根據圖檔資源所在位置不同,我們有三種加載圖檔的方法。 分别對應資源在Watch App中,在Extension和在iOS App中。

首先在工程的三個項目中添加圖檔資源,圖檔大家随意,在這個例子中,Watch App和Extension我放一張圖檔,而iOS App中我借用蘋果官方的例子,放入360張圖檔做動畫。

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

先來看看最容易處理的情況:圖檔資源在WatchKit App中。 對于這種情況, Extension和WatchApp之間隻需要傳輸很少量的資料:傳輸的是圖檔資源的名字而已。 在WatchApp中的WatchKit收到這個名字後會在包中尋找同名檔案并自動加載顯示。 是以相應的代碼如下:

  1. - (void) ShowPicFromWatchKitApp
  2. {
  3.     [_watchImage setImageNamed:@"AppleImage.png"];
  4. }

其中的setImageNamed就是直接顯示圖檔了。

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

如果圖檔在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 -- 作為遊戲開發者的你準備好了麼? (上)

相比前一種方式,這裡傳輸的不是檔案名而是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 -- 作為遊戲開發者的你準備好了麼? (上)

牽扯到三方,中間的溝通自然多了不少,是以這個也是最耗時的方法。

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.     NSDictionary *request = @{@"request":@"PIC"};
  9.     NSString* key = [NSString stringWithFormat:@"%d", index];
  10.     [_AnimationPics removeObjectForKey:key];
  11.     [InterfaceController openParentApplication:request reply:^(NSDictionary *replyInfo, NSError *error) {
  12.         if (error) {
  13.             NSLog(@"%@", error);
  14.             [_AnimationPics removeObjectForKey:key];
  15.         } else {
  16.             NSData* data = [replyInfo objectForKey:@"PIC"];
  17.             NSArray* picarray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
  18.             [_AnimationPics setObject:picarray forKey:key];
  19.             //start animation
  20.             if (_AnimationTimer == nil) {
  21.                 _AnimationTimer = [NSTimer scheduledTimerWithTimeInterval:0.2f target:self selector:@selector(SetAnimationIndex) userInfo:nil repeats:YES];
  22.             }
  23.         }
  24.     }];
  25. }
  26. iOS App中的響應代碼
  27. #define PICCOUNT 30
  28. - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
  29.     if ([[userInfo objectForKey:@"request"] isEqualToString:@"PIC"]) {
  30.         NSLog(@"containing app received message from watch");
  31.         NSMutableArray* marray = [NSMutableArray arrayWithCapacity:PICCOUNT];
  32.         for (int i=0; i<PICCOUNT; i++) {
  33.             UIImage* image = [UIImage imageNamed:[NSString stringWithFormat:@"glance-%[email protected]", i+_picindex]];
  34.             NSData* imagedata = UIImagePNGRepresentation(image);
  35.             [marray addObject:imagedata];
  36.         }
  37.         _picindex += PICCOUNT;
  38.         _picindex %= 360;
  39.         NSData* imageData = [NSKeyedArchiver archivedDataWithRootObject:marray];
  40.         //NSData* imageData = UIImageJPEGRepresentation(image, 1.0f);
  41.         NSDictionary *response = [NSDictionary dictionaryWithObject:imageData forKey:@"PIC"];//@{@"response" : @"Watchkit"};
  42.         reply(response);
  43.     }
  44. }

不出所料,讓iOS App傳遞祯序列是很耗時間的操作,在例子程式中我嘗試使用了Double Buffer,但是效果也不明顯。

最後程式運作起來是這個樣子滴....

Apple Watch -- 作為遊戲開發者的你準備好了麼? (上)

前面說了這麼多,又是代碼又是圖檔的, 有人要問了,為啥蘋果設計一個這麼蛋疼的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