随着Adobe Flash技术向iOS, Android, BlackBerry这三大移动平台的进军,必然将涌现出大量的基于Flash Player和AIR的手机和平板应用。然而移动设备的硬件限制,对Flash的运行效率产生了很大的挑战,所以如何优化代码成为Flash移动开发的核心问题。另外、基于触摸的全新交互方式和移动设备上独有的系统环境,也让移动平台上的Flash开发多了许多机会和功能点。今天我就给大家简单介绍一下我在近期总结的一些经验和技巧。
GPU渲染
移动设备的CPU和电脑上的CPU相差甚远,所以要运行大量动画(尤其是矢量动画),则最好要使用GPU来渲染。Flash Profession CS5在针对Android或者iOS项目的发布设置中可以选择是否使用GPU硬件加速。
不要大量使用位图缓冲
很多人认为使用位图(包括cacheAsBitmap) 要比使用矢量图要好,实际情况并非如此,一味单纯地使用图片也有弊端。论据之一是位图在缩放时显示不佳,我相信大家都有这样的体会;另外,如果位图元素被用在了移动、缩放、旋转等动画之内,那么它的运行效率不是提高反而下降了不少。而且在在使用GPU加速情况下,使用位图缓冲会让画面锯齿增加,远不如矢量图的清晰度高。但是如果用GPU渲染内容较多的动态文本,则必须使用位图缓冲。
大量文本的渲染
正如上点所说,GPU对处理动态矢量文本是非常之慢,原因就是GPU对矢量图采用了镶嵌渲染(tessellate),所谓镶嵌渲染就是将矢量图分为若干个小三角形,根据它们的顶点信息分别绘制再镶嵌成整体的形状(如同Molehill的顶点着色器),镶嵌渲染对矢量图的裁剪数量是依据其本身的形状,如果矢量图的拐点较多,那么就会裁剪出较多的三角形。因为动态文本含有大量的拐点,所以会产生很多的三角形镶嵌计算,而如果文本含有大量内容则会带来灾难性的结果。所以对大量文本需要使用位图缓冲来处理,使用cacheAsBitmap = true,让GPU按照文本的宽高来均匀地裁剪三角形,这样会使运行速度非常流畅。在做其他矢量图形的时候同样需要注意这一点,尽量避免弯曲而且狭长的图形,这让GPU绘制很多三角形从而拉低运行效率。
(如上图,"镶"字的偏旁部分所示的是对矢量图镶嵌,"嵌"字示意的是对位图镶嵌)
矢量图的帧滞后现象
这是我在制作手机游戏过程中发现的一个现象,前提是使用GPU加速,如果在第二帧要在舞台上首次显示一个复杂的矢量场景,那么在第一帧会有一个很明显的停顿,这也是由于GPU的镶嵌造成的。在我的例子中,前一个场景在做淡出效果,淡出到M帧应该是纯黑的画面,同时在M帧显示一个新的场景。由于GPU镶嵌造成的运算,使得前一个场景在M-1帧停顿了大约0.2-0.5秒,这时候画面并非纯黑,所以很明显感觉到停顿。我最后的处理方式是将前一个场景的退出和后一个场景的进入改为异步连接,在中间使用一个timer来等待100毫秒,这样肉眼就丝毫感觉不到停顿地存在。
不要使用特殊效果
对移动设备来说,无论使用CPU还是GPU,在某些效果地渲染上都尚未达到理想的结果。比如在动画上方盖一个很大的透明渐变层,会导致帧频极具下降至10以下;如果采用了滤镜,哪怕只有简单的模糊滤镜,都会让帧频下降到个位数,GPU下甚至根本就不支持滤镜的渲染。GPU同样不支持的还有部分layer,alpha,erase,overlay,hardlight,lighten,darken这些图形叠加效果以及着色器PixelBender。
设备键盘的使用
和电脑上获取键盘事件的原理一样,我们可以给应用程序添加一个侦听器来捕获KeyboardEvent,从而定义移动设备上的按键事件。从AIR2.5以后,我们可以使用Keyboard里的一些针对移动设备的key code(如Keyboard.BACK,Keyboard.MENU,Keyboard.SEARCH),如下面的例子:
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);function onKeyDown(pEvent:KeyboardEvent):void{ if(pEvent.keyCode == Keyboard.BACK){ //do something here }}
另外有一点可能比较有用,我们可以利用Event的preventDefault方法来阻止一些键盘事件的默认行为。比如如果我们不希望按了返回键之后跳出当前应用程序到桌面,我们可以在上面的例子里加一行代码:
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);function onKeyDown(pEvent:KeyboardEvent):void{ if(pEvent.keyCode == Keyboard.BACK){ pEvent.preventDefault(); //do some checking here }}
跨平台应用程序的判断
对于一个打算发布在多个平台的应用程序,我们当然希望使用相同的代码,避免为每个平台单独开发。桌面应用和移动应用在交互方式上是不一样的,桌面上更多的是采用鼠标操作,而移动上更多的是触摸和手势。这两种方式的API是不一样的,鼠标使用的是MouseEvent,触摸和手势则用TouchEvent和GestureEvent。所以在定义交互的时候我们需要判断当前运行的平台是什么样的交互模式:
if(Capabilities.touchscreenType == TouchscreenType.FINGER){ //定义触摸和手势交互}else{ //定义鼠标交互}
除了交互方式的不同,移动设备上的AIR与桌面端AIR一样,有对原生系统的访问API比如NativeApplication类。举一个例子,在浏览器中运行的应用程序不用去考虑关闭应用程序,但是AIR则经常会设计一个自我关闭的功能。这个功能需要用到NativeApplication.exit()方法,可是NativeApplication类只存在于AIR运行时而非Flash Player中,所以如果在跨平台的代码中直接导入NativeApplication则会在编译浏览器版本时报错,即使使用AIR的发布功能将SWF发布成功,那么在浏览器中运行这个SWF也会报未知对象的错误。
我的解决方法是使用反射来解决编译时的导入问题,而使用Exception来解决运行时的错误:
try{ //AIR版本 var myClass:Class = getDefinitionByName("flash.desktop.NativeApplication") as Class; nativeApplication = myClass.nativeApplication; nativeApplication.addEventListener(Event.DEACTIVATE, deactivateHandler); nativeApplication.addEventListener(Event.ACTIVATE, activateHandler); nativeApplication.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);}catch(e:Error){ //浏览器版本}
状态保存
Android和iOS平台与电脑平台一样,支持SharedObject本地存储功能,所以可以在所有平台使用一样的代码来对应用程序状态(比如游戏进度、应用的偏好设置)进行保存和读取。如果你对SharedObject的保存和读取不是很清楚,下面的代码可以帮助你了解。
访问本地SharedObject数据
//blabla是自定义的字符串,本地存储文件会以它来命名。var so:SharedObject = SharedObject.getLocal("blabla");//so.data是SharedObject的只读属性,可以作为容器存放需要在本地存储的数据变量,(注意,本地存储的数据有大小限制,最多为100KB)so.data.savedata = "What a nice function!!";//生成本地存储文件so.flush();
如果要清除特定的本地存储文件,可以用下面的代码来实现:
var so:SharedObject = SharedObject.getLocal("blabla");so.clear();
应用程序状态判断
举一个例子,如果你正在手机上玩一个有背景音乐的游戏,突然被来电打断,那么你当然不希望在通话的时候同时会听到游戏的背景音乐。或者你按了Back键回到了桌面,那么游戏也是理所应当被静音的。这就需要对应用程序的运行状态比如Activated, Deactivated添加侦听:
nativeApplication.addEventListener(Event.DEACTIVATE, deactivateHandler);nativeApplication.addEventListener(Event.ACTIVATE, activateHandler);function activateHandler(pEvent:Event):void{}function deactivateHandler(pEvent:Event):void{}
另外还有一些增加用户体验上的经验,比如如何处理被触摸控制的物体到达屏幕边界时的弹性缓冲运动,都可以用基本的ActionScript方法来进行处理和运算,这里不做详细介绍。
补充两点:
1.避免使用Timer,
2.在一些静态图片的时候可以降低帧率