演义群侠传(一)【游戏性能优化】
应用的优化技术从易到难排列
1.不要使用滤镜【更改方法?】
2.尽可能使用倒序for循环,避免使用do循环和while循环【什么是倒叙】
3.明确的停止使用Timer,以便垃圾回收【√】
4.使用弱引用时间侦听器,当不用的时候移除【更改】
5.尽可能在任何时候严格定义变量类型【√】
6.当不需要鼠标交互的时候明确的禁用鼠标交互【更改】
7.尽可能在任何时候使用回调函数来取代dispatchEvent(继承的)类【需要更改】
8.不需要声音时停止Sound类,以便垃圾回收Sound(继承的)类和SoundChannel(继承的)类【关闭声音的时候】
9.尽量让每一个所需的元素使用最基本的DisplayObject类【辨别MC、shap】
10.Air应用(移动设备)总是使用cacheAsBitmap 和cacheAsBitmapMatrix (前一个是位图缓存,后一个是位图矩阵缓存?我没用过)【没用过】
11.尽可能在任何时候重新使用Object【没明白】
12.Event.ENTER_FRAME循环:使用不同的侦听器和不同的侦听函数应用在尽可能少的DisplayObjects 上【没有明白】
13.用PoolObject(对象池)取代创建和垃圾回收Object【没明白】
14.使用局部位图传输(块传输)【需要】
15.使用阶段的块传输【不明白】
16.使用Stage3D【?为什么】
优化技术后好处由大到小排列
1.使用阶段的块传输(如果有足够的系统内存)
2.使用Stage3D
3.使用局部位图传输(块传输)
4.在移动设备上使用cacheAsBitmap 和cacheAsBitmapMatrix
5.当鼠标交互不需要的时候明确的禁用鼠标交互
6.不要使用滤镜
7.需要的时候使用最基本的DisplayObject类
8.尽量在任何时候重新利用对象
9.Event.ENTER_FRAME循环:使用不同的侦听器和侦听函数,他们最好应用在尽可能少的DisplayObjects 上
10.尽可能使用倒序for循环,避免使用do循环和while循环
11.用PoolObject(对象池)取代创建和垃圾回收Object
12.尽可能在任何时候严格定义变量类型
13.使用弱引用时间侦听器,当不用的时候移除
14.尽可能在任何时候使用回调函数来取代dispatchEvent(继承的)类
15.明确的停止使用Timer,以便垃圾回收
16.不需要声音时停止Sound类,以便垃圾回收Sound(继承的)类和SoundChannel(继承的)类
记住这些优先级排序,然后前进到下个单元,学习如何更新你的Flash工程来更有效率的管理内存。
管理内存
下面列的建议不够详尽,但它包括了那些可以大幅度提升Flash性能的策略内容。
1、使用回调函数 VS dispatEvent
当派发事件的时候会增加内存的使用,因为每个事件必须被创建并且分配内存给它。这种行为是这样解释的:事件也是对象,因此也需要内存。
我试着发送少量事件,发现每个消耗40到128字节。我也发现使用回调函数会使用更少的内存,比使用事件效率更高。(查看在实例文档里的测试文件callback_v_dispatchEvent。)
2、应用滤镜
当你大量应用滤镜时也很消耗内存。根据Adobe帮助文档,使用一个滤镜会消耗双倍内存。在真实Flash Professional CS6的测试环境中,我曾发现使用滤镜的确会增加内存消耗,但是这种消耗不接近双倍内存。(回顾测试范例,在filters文件夹下)
3、为每个元素使用正确类型的现实对象
Shape,Sprite,和MovieClip对象每个都使用不同的内存数量。一个Shape对象需要236字节,Sprite需要412字节,MovieClip需要448字节。
如果你在一个工程里使用上千的显示对象,如果不需要交互的话,你也许需要大量Shape类来拯救你的内存。或者,当不需要时间轴时使用Sprite类。
4、对象池
当你打开你的应用时,要创建各种你会一直使用的对象引用,对象池可以将这些引用保存在数组里。任何时候一个对象需要时,就可以从这个数组里取出使用。
无论何时当一个对象不再需要时,把它再重新放回数组里。
有种常规做法是用Vector来代替Array来存储相同类型的对象。使用Vector也许可以比使用Array快两倍,但是!除非你要做成百上千次的操作,否则你不会注意到两者的差别,因为小于上千次的操作它们一样快。(可以看看array_v_vector 文件夹下的范例文件。)
使用对象池可以获得性能上的好处,同时更主要的收益是让管理内存变得简单。如果你在内存利用方面有无限制增长的问题,用对象池可以很好的解决,它是提高性能、降低内存使用的通用技术。
我看到当测试一个每帧包含许多要垃圾回收和再利用的SWF文件里,使用对象池后帧频快了10%,而内存使用则减少了10%。(可以查看pooling_v_gc 文件夹里的范例。)
5、重用对象
当你要在一个循环里创建许多对象时,最好在循环外先创建一个对象,然后再循环里重复利用它。当然这个方法也不是对所有工程都有效的,但在很多情况下这个技术还是有用的。
在描述位图传输的单元包括一个重用大量对象的例子。你可以在测试文档里看这是怎么实现的。
6、处理声音
有关声音的问题在内存使用方面是非常小儿科的。当播放一段声音时,它不可能被垃圾回收的(可以使用Flash Professional CS6来测试文件)。当声音播放完或一个SoundChannel实例执行停止声音时,Sound类就准备垃圾回收了。(想学更多的话可以看看名为sound_test 文件夹下的范例。)
7、使用Timer
使用Timer时要格外小心。如果没有停止Timer(有两种情况:1.currentCount 属性小于它的循环次数;2.没有调用stop()方法),Timer就不会被垃圾回收,即使你已经移除了侦听器,然后将所有引用设为null。一旦你移除了侦听器,Timer的侦听函数就不被再次调用,但是Timer却仍然消耗内存。
Timer类仅仅使用72字节的内存,所以很可能在一个基于桌面/浏览器的Flash游戏里成为一个很不起眼的问题。但是,当你在移动设备里反复的打开、播放、关闭游戏,然后不断重复启动游戏,你也许就看到这个难以忽略的问题了。
看看这个代码,打开命名为gc_timer_test文件夹下的文件。
8、弱引用侦听器 VS 强引用侦听器
另一种无法预料的测试结果是,你使用MT类没有办法看出使用弱引用侦听器和强引用侦听器的差别。在Flash Professional CS6环境下我的测试里它们都被当做弱引用侦听器来对待。(查看strong_v_weak_listeners 文件夹下的范例。
9、处理cacheAsBitmap 和cacheAsBitmapMatrix
使用DisplayObject的cacheAsBitmap属性可以大幅度提高性能(和内存),只要DisplayObject不经受需要频繁更新位图的改动。换句话说,DisplayObject在某种程度上不改变外观只是改变它在舞台上的位置。如果频繁更新位图,性能会降低。
你可以经常改变位图缓存,仍然可以看到性能上的收益,这取决于几个因素,但不要太惊讶,最重要的因素是,你是如何经常改变位图的。
无论如何,用MT类测试一个指定的工程,然后看看用位图缓存和不用有什么差别。(当决定是否对那些不需要位图改变的显示对象使用位图缓存时要不加思考的就使用!)
如果你有一个显示对象(如影片剪辑),你想使用位图缓存属性,加上这句:
mc.cacheAsBitmap = true;
即使你改变显示对象的大小、倾斜、透明度和或者旋转(但不改变影片剪辑的帧数),然后发布到移动设备,使用位图缓存也是能提升性能的。
尤其是,当把一个工程发布到移动设备时,你可以启用cacheAsBitmap并分配catheAsBitmapMatrix属性,完成后可大幅提升性能,像这样:
mc.cacheAsBitmap = true;
mc.cacheAsBitmapMatrix = new Matrix();
不要使用默认单位矩阵。以后你就会知道有几个原因促使你使用这个属性而不是用默认矩阵。?
10、Stage blitting(我不知道把它翻译为“阶段块传输”还是“舞台块传输”)
这是一个描述数据传输的术语,包括了将使用的位图最终渲染到显示屏幕上。不是将显示对象加到显示列表里,而是把像素“放在”舞台大小的位图里,然后把位图“加到”舞台上。为了更新动画,位图的像素要在一个循环里更新。尤其是在Event.ENTER_FRAME循环里,使用BitmapData类里的copyPixel()方法,将舞台大小的位图里的BitmapData属性,在动画的循环之外替换其他的bitmapData对象。
这个方法比直接把对象放到显示列表里复杂,但它更有效率——如果你有个没法容忍的帧频和需要高帧频的Flash应用,这个方法会非常有用。诚然,除非你想增加帧频,否则你绝对没有理由使用这个方法。
我比较了一个SWF文件,它有10,000个正方形影片剪辑,执行运动和旋转动作,还要穿过经过舞台(可以看blit_test/blit_test_mc.fla范例)。然后我把这个文件做了一些基本的优化(可以看blit_test/blit_test_basic_optimizations.fla文件)和stage blitting(看blit_test/blit_test2文件)。
第一个SWF文件大概为15fps,这是不能容忍的。但是,在应用最难的技术优化比如块传输之前,几个基本的调整就可以轻松提高性能。
首先,我将循环倒序,这样有了一点的性能提升(看下面循环的单元),然后,更重要的是,我使用一些常量取代了一些相同的但要重复计算的变量。这些调整时性能有了稍微大点的提升(约40%),让帧频可以稍微让人接受点了,约21fps。
使用stage blitting编码同样的显示区域,结果帧频变成了54fps,整整提升了350%。
但是,正如我之前说的,这个技术的过程很复杂,包括下面几个方面:
1.初始化需要在每个Event.ENTER_FRAME事件里循环的,要在舞台上显示的位图资源(Bitmap实例,BitmapData实例和Rectangle实例)。
2.创建一个所有要显示更新数据的数组。(这步不是必须的。)
3.创建一个BitmapData对象的数组。如果你的动画在一个影片剪辑的时间轴上,这是你要每帧都存储BitmapData对象的地方(例如,使用一个sprite列表,在范例文件夹里我为每个角度的矩形都创建了BitmapData实例,这个实例可以用AS旋转。)
4.创建Event.ENTER_FRAME事件循环。
5.更新循环里的元素,将第3步里创建数组里相应的像素,复制到在第1步里创建BitmapData实例对应的地方(第2步决定使用data数组)。
想看更多细节,请看blit_test/blit_test2,它还包括额外的注释。
Stage blitting技术的负面,不是复杂的编码,而是也许在创建需要的位图是消耗大量的内存。当为类似iPad之类有很高分辨率(第一、二代1024*768,第三代2048*1536),相对低的内存(RAM)和容量(一、二、三代分别为256MB,512MB和1GB)的设备写应用时,这是个要严肃考虑的问题。
一般来说,你的游戏应消耗不多于一半的可用RAM。这指的是,不仅包括位图而是你游戏里的一切消耗。
11、Partial blitting(有道的翻译是:局部复制。其实我觉得有时候太纠结翻译的中文也不好,知道这项技术的本质就好了)
正如字面含义,局部复制结合了Flash显示列表和把像素复制到BitmapData对象两种方式。特别是,在舞台的每一个显示对象是位图时,把他们加入显示列表,然后像一般的显示对象比如影片剪辑那样操控就行了。把每个对象的动画复制到一个BitmapData对象的数组里。
例如,使用之前有正方形运动选择穿过舞台的文件例子,我把正方形和它们各种旋转,将这些BitmapData对象存放在一个数组里,放在bitmap里加入显示列表,然后在Event.ENTER_FRAME循环里操控这些bitmap就像操控任何显示对象那样(比如之前描述的影片剪辑)。最后,我将bitmap的bitmapData属性分配给对应的数组元素。(看看这是如何实现的,可以复习blit_test/partial_blitting_test.fla文件。)
在我的电脑上,Partial blitting测试(24-26fps)不会像stage blitting一样快。但是这个方法为你启发了思路,因为也许在其他方面partial blitting比stage blitting快。另外,partial blitting比stage blitting好编码。所以呢,如果你用partial blitting技术可以得到效果好的帧频,那么它还可以减少在stage blitting里必须要做的额外工作。(就是如果能用局部复制就可以不用stage blitting了。)
12、有关Event.ENTER_FRAME 循环
在一个实例上,创建多个Event.ENTER_FRAME侦听器,回调多个函数,要比一个实例上创建一个侦听器回调一个函数,这个函数再调用其他函数,要稍微快那么一点点。(好绕口啊~~~~~~)
但是,这有个不同的情况:在多个对象上分别侦听Event.ENTER_FRAME,和一个对象上侦听一个相比较,使用一个对象侦听一个是多个对象拥有各自侦听器性能的大约两倍。(可以看enterframe_test_one_v_many_loops_with_different_movieclips 文件夹下的例子。)
13、理解For循环,while循环和do循环
在Flash里,for的倒序循环是最快执行的循环。如果在循环里需要存储的都是相同类型的对象,一个保存所有对象引用的,使用Vector的倒序for循环是最快的。
如果你使用int而不是uint来迭代元素,那这三个循环都执行的都挺快。如果你递减循环变量而不是增加,那么三个循环也会一样快。(注意:如果你递减的循环变量i使用的中止条件是i>=0,并且i是uint的话,你可能会触发一个没有结束的循环。)
如果你使用的是变量或常量作为循环结束的标志而不是表达式或对象属性,那么三个循环一样快。因为初始条件仅需要评估一次(而不是每次循环迭代都要判断),在任何循环里循环里,使用判断式或对象属性作为初始条件都没有大的差别。
任何不会影响循环的内容都应该放到循环的外面。这包括在循环外定义对象(看重用对象的单元),有时在循环里使用新的构造函数可以放在循环外面,如果结束条件是个表达式,应该在循环外算出来。
我曾看过这种说法,对每一个有个下个对象引用的对象循环(类似链表),要比一个数组存储所有对象引用的循环快。在我的测试结果显示,这是不对的。
使用数组比先初始化再使用要快和容易。使用Vector而不是数组,当然要更快了。(见for_loop_v_sequential_loop 文件夹下的例子。)
所有的这些建议可能在很多情况下没什么很大差别。但是,如果你的代码要利用一切可以利用的资源,或者你的工程里有数量惊人的迭代,这些细节值得你参考。
14、禁用鼠标交互
影片剪辑和sprite可以和鼠标交互。即使你没有为鼠标交互编任何代码,当这些对象存在时Flash Player会检查鼠标交互。所以你可以禁用一些不需要的交互拯救一点CPU资源。
当你注意到性能问题,鼠标滑过舞台时(或者你的电脑风扇加快转速),这个策略非常有用。禁用鼠标交互可以提升性能还可以让你的电脑风扇安静点。
在测试时,我看到当禁用所有影片剪辑的鼠标事件后,帧频增加了2 1/2倍,这个测试代码在mouse_interactivity 文件夹下。
15、移除事件侦听器
即使最新版本的Flash Player出现了两个功能:当对象被垃圾回收后移除侦听器,和强引用侦听器不再延迟垃圾回收。你仍然要尽可能明确地移除所有的事件侦听器。侦听器越是迅速的移除掉,被占用的CPU资源越少。另外,你可能不知道你装了哪个版本的Flash Player,老版本是没有垃圾回收对象的——即那些对象是弱引用侦听器。不要依赖最新的Flash Player功能,而要踏实优化自己的糟糕代码。