作者:
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上以便将场景中的内容保存到图片中。
图片捕捉代码:
- void _CaptureFrame()
- {
- recordInfo.enabled = true;
- RenderTexture.active = camera.targetTexture;
- Texture2D texture2d = new Texture2D(camera.targetTexture.width, camera.targetTexture.height, TextureFormat.RGB24, false);
- texture2d.ReadPixels(new Rect(0,0,camera.targetTexture.width, camera.targetTexture.height), 0, 0);
- texture2d.Apply();
-
- filename.Length = 0;
- filename.AppendFormat(Application.persistentDataPath+"/watchanimations/animation-{0}.png", saveindex);
- byte[] data = texture2d.EncodeToPNG();
- System.IO.File.WriteAllBytes(filename.ToString(), data);
- recordInfo.text = (maxfiles - (saveindex%maxfiles)).ToString();
- saveindex++;
- }
复制代码
在主循环中,按照一秒30帧的速度来捕捉,一共捕捉60帧
- void Update () {
- //save render texture to png
- escapedTime += Time.deltaTime;
- if(escapedTime >= 0.0334f)
- {
- escapedTime = 0.0f;
- if(NeedRecord() && saveindex
- {
- _CaptureFrame();
- }
- else if(saveindex>=maxfiles*(frames+1))
- {
- waitingTime += Time.deltaTime;
- recordInfo.enabled = false;
- if(waitingTime>=1.0f)
- {
- System.IO.StreamWriter sw = System.IO.File.CreateText(recordIndicator);
- sw.WriteLine(frames.ToString());
- sw.Flush();
- sw.Close();
- newAnimation = false;
- frames++;
- saveindex = maxfiles*frames;
- waitingTime = 0.0f;
- }
- }
- }
- }
复制代码
这样在程序的Documents目录下就准备有我们的图片了。
当WatchKit App有需求的时候,Extension就会向iOS App,我们的游戏发起请求:
- -(void) _RequestData {
- NSDictionary *request = @{@"request":@"PIC"};
- [InterfaceController openParentApplication:request reply:^(NSDictionary *replyInfo, NSError *error) {
-
- if (error)
- {
- NSLog(@"%@", error);
- }
- else
- {
- NSData* data = [replyInfo objectForKey:@"PIC"];
- if (data != nil)
- {
-
- NSArray* picarray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
- NSMutableArray* imagearray = [NSMutableArray arrayWithCapacity:[picarray count]];
- for (int i=0; i<[picarray count]; i++) {
- UIImage* image = [UIImage imageWithData:[picarray objectAtIndex:i]];
- [imagearray addObject:image];
- }
-
- _Animations = [UIImage animatedImageWithImages:imagearray duration:8.0f];
- //remove all cached images
- //[[WKInterfaceDevice currentDevice] removeAllCachedImages];
-
- [self StartAnimation];
-
- }
-
- }
-
- }];
-
- }
复制代码
游戏收到Extension的请求后会检查Documents目录下是否有新数据,如果有,就将所有的png放到一个数组中返回给Extension
- #define PICCOUNT 60
- - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
-
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
- NSString* indicatorpath = [NSString stringWithFormat:@"%@/watchanimations/recordFinished", [paths objectAtIndex:0]];
- NSError* error = nil;
- if ([[userInfo objectForKey:@"request"] isEqualToString:@"PIC"] && [fileManager fileExistsAtPath:indicatorpath])
- {
-
- //read index number
- NSString* content = [NSString stringWithContentsOfFile:indicatorpath
- encoding:NSUTF8StringEncoding
- error:NULL];
- int picindex = [content intValue];
-
- NSLog(@"containing app received message from watch");
-
- NSMutableArray* marray = [NSMutableArray arrayWithCapacity:PICCOUNT];
- for (int i=picindex*PICCOUNT; i
- NSString* pngfile = [NSString stringWithFormat:@"%@/watchanimations/animation-%d.png", [paths objectAtIndex:0], i];
- UIImage* image = [UIImage imageNamed:pngfile];
- NSData* imagedata = UIImagePNGRepresentation(image);
- [marray addObject:imagedata];
- [fileManager removeItemAtPath:pngfile error:&error];
- }
-
- //UIImage* animations = [UIImage animatedImageWithImages:marray duration:4.0f];
- //NSArray* finalarray = [NSArray arrayWithObjects:animations, nil];
- //NSData* imageData = [NSKeyedArchiver archivedDataWithRootObject:finalarray];
- NSData* imageData = [NSKeyedArchiver archivedDataWithRootObject:marray];
-
- //NSData* imageData = UIImagePNGRepresentation(animations);
- NSDictionary *response = [NSDictionary dictionaryWithObject:imageData forKey:@"PIC"];//@{@"response" : @"Watchkit"};
-
- reply(response);
-
-
- [fileManager removeItemAtPath:indicatorpath error:&error];
-
- }
- else
- {
- reply(nil);
- }
- }
复制代码
在Extension中,收到游戏App发过来的UIImage数组后,依次取出并放入一个UIImage中形成序列帧动画。 然后在通过WKInterfaceImage中的函数设置播放帧动画。
- - (void) StartAnimation
- {
-
- if (_Animations == nil) {
- return;
- }
-
- [self StopAnimation];
- [_WatchImage setImage:_Animations];
- NSRange range = NSMakeRange(0, 59);
- [_WatchImage startAnimatingWithImagesInRange:range duration:4.0f repeatCount:0];
- }
复制代码
作为演示,例子中的机器人一共有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