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

作者:  Bowie     时间:  2015-1-22 16:53
标题:  Apple Watch -- 作为游戏开发者的你准备好了么? (下)
本帖最后由 Bowie 于 2015-2-16 12:13 编辑 

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



限制种种

Apple Watch作为iphone的第二屏,有那么点WiiU gamepad中手柄屏幕相对于电视机那样的感觉。 WiiU手中的屏幕可以显示游戏中玩家的装备界面,让玩家在战斗时方便切换。 可以显示小地图,在探索时不至于迷路。 或者在多人游戏中可以作为其中一个玩家独有的屏幕, 从而创造独特的玩法......

看了WiiU对屏幕的使用是不是觉得很激动呢,原来双屏可以有这么多有趣的玩法啊。 嗯, 我只想说...你高兴的太早了! 前文讲到WatchKit App本身并没有运算能力,也就是个没有想法的女神而已。 这还不算完, 在Apple Watch的小小屏幕上,以目前的WatchKit而言,,也不能让你随心所欲的进行控件的排布。 具体来说,所有的控件只能一个接着一个的垂直排列, 加上对Group的运用,可以将多个控件水平排列。 而且这些排布目前只能在storyboard中完成,不能在代码运行的时候动态的添加或者改变其大小。WatchKit 的对于界面做出的种种限制使得一般的App应用都要好好的考虑设计一番,对于没有定式的游戏来说就更加难以满足需求了。


 

那么游戏又能怎样利用Apple Watch呢?

游戏中的通知

随身的手表相对iPhone来说,更适合通知消息的弹出, 游戏可以将原本在iPhone端弹出的通知消息放到Apple Watch上。 让用户快速得到游戏的最新动态。 如果愿意,也可以点击通知启动iPhone上的游戏。 此功能使用简单的Glance或者Actionable Notifications就可以胜任了。

游戏中的游戏

在WatchKit这样有种种限制的设备上开发一个复杂的游戏显然是不行的,但是做为游戏的衍生则完全可行。 有些游戏为了打发长时间无聊的loading等待,会事先加载一个无关痛痒的小互动。 Zelda为了让你拿到心之碎片,也会在游戏中加入有一定难度的小游戏。 

我们可以根据自己的需要,在Apple Watch上放上游戏中的小游戏。 举个例子, 我们的iOS游戏是个很有特色的rpg,你带着你的宠物在魔幻世界闯荡,获得各种奇珍异宝。 在手表上,完全可以做一个宠物养成的扩展。 你可以在手表上和随你战斗宠物发生亲密互动:抚摸宠物,给宠物喂食,给宠物洗澡。这样宠物的体力可以更快的恢复,参加到下一场战斗中。 又或者我们可以在手表上制作一个777开宝箱的游戏,玩家把钱投进老虎机,转出宝贝。


 

WKInterfaceObject 筛选
就目前的AppleKit Framework而言,可以看出苹果一贯的套路:一开始别想得到那么多功能。给我老老实实的从简单的做起。 在Apple Watch上跑3D程序是不可能了。我们能用的,只有Framework里面提供的这些控件而已。

看看上面这些WKInterface类和里面提供的函数吧, 是不是有想哭的感觉?没错! 这些类无法进行式样自定义,无法动态改变大小,无法动态添加或者删除,在游戏中几乎都无法使用。 试问谁会在游戏中放入系统提供的呆板且格格不入的控件呢?拜托,我们又不是在做MUD游戏...

能用控件做的大体也只能是类似于拼字游戏这样的吧....


 


这些控件中,唯一可以大作文章的,就是image,对于image的内容,苹果没有限制,也做不了限制。 而且WKInterfaceImage和WKInterfaceButton还支持Image动画。谢天谢地,这样我们还能利用这些特性做出些像样的东西出来。

还记得我们在前篇文章中说到的三种加载图片的方式嘛:我们可以从WatchKit App本地加载, 从Extension中加载,或者绕个大圈子,从iOS App那里加载。 下图是上篇中的例子,分别对应这三种方式。

 
对于游戏中固定不变的图片,完全可以放到WatchKit App或者Extension中,这样需要使用的时候立刻加载,非常方便。 但是游戏中的内容并不是一成不变的,很多时候,需要能反应出当时玩家在游戏中的状态。

接着上面的例子, 你在游戏中的宠物可以有很多,形态各异,如果游戏做的复杂些,你甚至可以给你的宠物换武器,换衣服等装备,穿出你宠物独一无二的样子来。 如何让这些也能如实的反应到WatchKit App中呢? 又或者,作为一款在iPhone上的3D游戏,你想让玩家可以在手表上360度全方位的检视刚刚获得的宝贝。 这个时候,静态图片就不能满足需求了,你要做的是:让主程序(在这里是游戏程序)根据玩家的数据动态的生成图片,然后在放到WatchKit App上显示。 

技术探讨, WatchKit和Unity的结合
鉴于Unity的流行,这个例子也是基于Unity之上,但是其思路却可以适用于任何其他游戏工具引擎。

具体的工作流程: 当WatchKit App有需要的时候,通过其代理老爹,也就是Extension向iOS App发起请求,iOS App根据当时玩家的数据,生成相应的数据(在例子中是一系列png图片,形成帧序列),然后传给Extension, Extension再通过蓝牙同步到WatchKit App中进行显示。


 
在Unity中,我们创建一个RenderTexture并附在一个单独的Camera上以便将场景中的内容保存到图片中。

图片捕捉代码:

  1. void _CaptureFrame()
  2.     {
  3.         recordInfo.enabled = true;
  4.         RenderTexture.active = camera.targetTexture;
  5.         Texture2D texture2d = new Texture2D(camera.targetTexture.width, camera.targetTexture.height, TextureFormat.RGB24, false);
  6.         texture2d.ReadPixels(new Rect(0,0,camera.targetTexture.width, camera.targetTexture.height), 0, 0);
  7.         texture2d.Apply();
  8.         
  9.         filename.Length = 0;
  10.         filename.AppendFormat(Application.persistentDataPath+"/watchanimations/animation-{0}.png", saveindex);
  11.         byte[] data = texture2d.EncodeToPNG();

  12.         System.IO.File.WriteAllBytes(filename.ToString(), data);
  13.         recordInfo.text = (maxfiles - (saveindex%maxfiles)).ToString();
  14.         saveindex++;

  15.     }

复制代码


在主循环中,按照一秒30帧的速度来捕捉,一共捕捉60帧

  1. void Update () {
  2.         //save render texture to png
  3.         escapedTime += Time.deltaTime;
  4.         if(escapedTime >= 0.0334f)
  5.         {
  6.             escapedTime = 0.0f;
  7.             if(NeedRecord() && saveindex
  8.             {
  9.                 _CaptureFrame();
  10.             }
  11.             else if(saveindex>=maxfiles*(frames+1))
  12.             {
  13.                 waitingTime += Time.deltaTime;
  14.                 recordInfo.enabled = false;
  15.                 if(waitingTime>=1.0f)
  16.                 {
  17.                     System.IO.StreamWriter sw = System.IO.File.CreateText(recordIndicator);
  18.                     sw.WriteLine(frames.ToString());
  19.                     sw.Flush();
  20.                     sw.Close();
  21.                     newAnimation = false;
  22.                     frames++;
  23.                     saveindex = maxfiles*frames;
  24.                     waitingTime = 0.0f;
  25.                 }
  26.             }

  27.         }

  28.     }

复制代码


这样在程序的Documents目录下就准备有我们的图片了。


当WatchKit App有需求的时候,Extension就会向iOS App,我们的游戏发起请求:

  1. -(void) _RequestData {
  2.     NSDictionary *request = @{@"request":@"PIC"};
  3.     [InterfaceController openParentApplication:request reply:^(NSDictionary *replyInfo, NSError *error) {
  4.         
  5.         if (error)
  6.         {
  7.             NSLog(@"%@", error);
  8.         }
  9.         else
  10.         {
  11.             NSData* data = [replyInfo objectForKey:@"PIC"];
  12.             if (data != nil)
  13.             {
  14.                 
  15.                 NSArray* picarray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
  16.                 NSMutableArray* imagearray = [NSMutableArray arrayWithCapacity:[picarray count]];
  17.                 for (int i=0; i<[picarray count]; i++) {
  18.                     UIImage* image = [UIImage imageWithData:[picarray objectAtIndex:i]];
  19.                     [imagearray addObject:image];
  20.                 }
  21.                 
  22.                 _Animations = [UIImage animatedImageWithImages:imagearray duration:8.0f];
  23.                 //remove all cached images
  24.                 //[[WKInterfaceDevice currentDevice] removeAllCachedImages];
  25.                 
  26.                 [self StartAnimation];
  27.             
  28.             }
  29.             
  30.         }
  31.         
  32.     }];

  33.     
  34. }

复制代码

游戏收到Extension的请求后会检查Documents目录下是否有新数据,如果有,就将所有的png放到一个数组中返回给Extension


  1. #define PICCOUNT 60
  2. - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
  3.     
  4.     NSFileManager *fileManager = [NSFileManager defaultManager];
  5.     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  6.     NSString* indicatorpath = [NSString stringWithFormat:@"%@/watchanimations/recordFinished",  [paths objectAtIndex:0]];
  7.     NSError* error = nil;
  8.     if ([[userInfo objectForKey:@"request"] isEqualToString:@"PIC"] && [fileManager fileExistsAtPath:indicatorpath])
  9.     {
  10.         
  11.         //read index number
  12.         NSString* content = [NSString stringWithContentsOfFile:indicatorpath
  13.                                                       encoding:NSUTF8StringEncoding
  14.                                                          error:NULL];
  15.         int picindex = [content intValue];
  16.         
  17.         NSLog(@"containing app received message from watch");
  18.         
  19.         NSMutableArray* marray = [NSMutableArray arrayWithCapacity:PICCOUNT];
  20.         for (int i=picindex*PICCOUNT; i
  21.             NSString* pngfile = [NSString stringWithFormat:@"%@/watchanimations/animation-%d.png", [paths objectAtIndex:0], i];
  22.             UIImage* image = [UIImage imageNamed:pngfile];
  23.             NSData* imagedata = UIImagePNGRepresentation(image);
  24.             [marray addObject:imagedata];
  25.             [fileManager removeItemAtPath:pngfile error:&error];
  26.         }
  27.         
  28.         //UIImage* animations = [UIImage animatedImageWithImages:marray duration:4.0f];
  29.         //NSArray* finalarray = [NSArray arrayWithObjects:animations, nil];
  30.         //NSData* imageData = [NSKeyedArchiver archivedDataWithRootObject:finalarray];
  31.         NSData* imageData = [NSKeyedArchiver archivedDataWithRootObject:marray];
  32.         
  33.         //NSData* imageData = UIImagePNGRepresentation(animations);
  34.         NSDictionary *response = [NSDictionary dictionaryWithObject:imageData forKey:@"PIC"];//@{@"response" : @"Watchkit"};
  35.         
  36.         reply(response);
  37.         
  38.         
  39.         [fileManager removeItemAtPath:indicatorpath error:&error];
  40.         
  41.     }
  42.     else
  43.     {
  44.         reply(nil);
  45.     }
  46. }

复制代码

在Extension中,收到游戏App发过来的UIImage数组后,依次取出并放入一个UIImage中形成序列帧动画。 然后在通过WKInterfaceImage中的函数设置播放帧动画。


  1. - (void) StartAnimation
  2. {
  3.     
  4.     if (_Animations == nil) {
  5.         return;
  6.     }
  7.     
  8.     [self StopAnimation];
  9.     [_WatchImage setImage:_Animations];
  10.     NSRange range = NSMakeRange(0, 59);
  11.     [_WatchImage startAnimatingWithImagesInRange:range duration:4.0f repeatCount:0];
  12. }
复制代码


作为演示,例子中的机器人一共有4个不同的动画,每次切换到新动画的时候,都会自动记录60帧的png图片。 当按下Watchkit App中的Sync Data按钮的时候,会通过Extension将最新的动画同步到手表上并播放出来,(演示Gif较大,请耐心等待)


通过这种方式,几乎可以把任何需要显示的内容放到Apple Watch上。

注意:为了尽量的缩小文中Gif的尺寸,我将从按下Sync Data到播放新动画这之间的等待时间去掉了。 按照我初略估计这之间有大概30秒左右的间隔。

Unity中设置的RenderTexture有256*256大小,生成png一张大概在35k左右, 60张png也就大概是2.1M。 如果按照每秒30帧这么粗暴的设定,那也就是2秒的播放时间,但是却要用到将近30秒的时间才能在Apple Watch上显示。把贴图减小到128*128,我们还是暴力计算,时间也相应的缩短到30/4=7.5秒,离2秒也差的太远。(当然目前我们拿不到实机,这个数据只是在模拟器上得出的结论)。 也就是说如果想要实时的从iOS App生成数据并传输到Apple Watch上进行显示的同学可以洗洗睡了。我们所能做的就是事先生成好数据,传输给WatchKit App,并反复使用。 在这里。WatchKit的Framework还给了我们一个很好的API: 

设置Image的Cache,以便以后使用,而且还支持序列帧动画!但是需要注意的是这里只有20M的空间,如果空间满了,你必须手动进行释放,否则函数会失败返回。


总结
让我们看看Apple Watch作为游戏设备来说,到底表现如何

先说限制:
1:控件排布不能随心所欲
2:控件无法动态创建或者改变
3:Apple Watch本身没有运算能力
4:Watch和iOS设备之间用蓝牙传输有速度瓶颈
5:只能获得点击输入信息,无法获得具体点击位置
6:无法发声

可利用功能:
1:播放动态图片,实现帧动画
2:按钮的背景图片也可以播放动画
3:简单的按钮输入,列表选择输入
4:可以有20M的Cache供程序使用

适合展现的游戏内容:
1:游戏中的各种提醒
2:简单的文字游戏,如"民国教育委员会"四选一问答
3:拼字游戏
4:电子宠物类游戏,简单的宠物互动
5:777老虎机或开宝箱游戏,只需要适当时机点点点,戳戳戳.


Apple Watch将来时
其实光就苹果公布的Apple Watch技术参数来看,我们就会发现还有很多的功能在现有的Framework中并没有提供。 Digital Crown的输入我们无法使用。

革命性的Force Touch数据我们不能获取

更有甚着,Apple Watch上的心跳数据和Siri我们也无缘享用。

但相信随着时间的推移,这些功能都将开放给开发者,让我们能做更多有趣的事情。说不定等第二代,第三代Apple Watch产品推出的时候,其性能已经强大到可以在上面直接执行代码,成为独立设备。到那个时候,说不定还可以直接移植一个精简版的Mono上去,用Unity直接开发Apple Watch原生程序呢。

虽然说有这样那样的限制,但是只要精心设计,还是可以以简单有趣的形式提供游戏在手腕上的扩展。拉近游戏和玩家之间的距离。 苹果公司已经开放了第一版的WatchKit SDK, 对于Apple Watch这个女神,主动接纳或者是保持观望,作为游戏开发者的你,准备好了吗?

本文中的例子程序可以在https://github.com/CoconutIslandStudio/UniWatch.git
下载

注意:由于Github的限制,项目中的一个大于100M的文件(libiPhone-lib.a)无法上传。

这个文件可以在Unity生成的iOS项目目录中找到
或者由后面的连接下载 http://pan.baidu.com/s/1qWuPUo8

然后放在项目中的UnityWatch/UnityWatch/Libraries/目录中即可



参考连接:
http://www.patentlyapple.com/patently-apple/2014/10/apple-patents-reveal-work-on-displays-with-two-dimensional-touch-sensors-and-a-universal-dock.html

你可能感兴趣的:(Apple Watch -- 作为游戏开发者的你准备好了么?)