作为一个flash的开发人员,flash的运行机制是必须了解的。你去面试或者被面试的时候,肯定会问。查了网上的几篇文章,发现都是从某些国外牛人的观点延伸而来。adobe一直没有官方的解释模型。在此,我也根据相关资料简单总结一下。
可变跑道
先从宏观上来描述Flash Player的执行模型。一“帧”可以看做是一个运行周期。在一帧里面,包括了两部分工作:代码执行和渲染。完美的情况下,这两部分的时间是相等的。现实却总是不完美的,有时代码执行的时间多一点,有时渲染的时间多一点,总时间就长了。看下面的图就懂了。
时间片
在flash内部,帧会被切分成“时间片”。这个工作是由一个组件(老外叫它Marshal元帅)控制的,它将时间切割成Flash Player工作的基本时间片。这里我们通常有一个误区,认为时间片和 swf 的帧速率是相关的。这是错误的,这里的时间片是绝对时间的,就是1秒可以分成多少个Flash Player的工作时间单位。在不同的平台上,切分单位是不同的。
每个时间片中,可能按以下顺序执行操作:
- Player事件调度 – 比如Timer事件,鼠标事件,ENTER_FRAME事件,URLLoader事件等
- 用户代码执行 – 监听上一步事件的代码被执行
- RENDER事件调度- 在用户代码执行期间调用 stage.invalidate()会触发这个特殊时间
- 最后的用户代码执行 – 监听上述第三步特殊事件的代码被执行
- Player 更改显示列表
总结
Marshal反复的执行时间片,并在运行中决定下一步操作。第2步用户代码和第4步RENDER事件的执行就是跑道的第一部分-代码执行,第5步就是跑道的第二部分-渲染。这下终于全明白了吧。
答疑
上门已经讲过了,两者没有直接的关系。时间片只是跟绝对时间有关。帧速越高,只能说明每帧的执行时间就越短,也就是每帧的时间片越少。比如下面的图就展示了不同速率的swf执行的时间片
否。一些特殊的事件,比如Event.ENTER_FRAME事件只能在某一帧的初始时间片被触发
假如一帧的时间都小于一个时间片的时间了,这时候肯定是不行了。Flash会自动控制你的帧速,不可能超过当前Flash Player切割时间片的速率
为了保证帧的播放速度仍然接近swf编译时设定的帧速,Marshal会选择丢掉一些时间片
否?Marshal会等待一段时间,再进入渲染阶段。此外,某些函数可能导致提前渲染,例如updateAfterEvent()。在这种情况下,Marshal会认为当前帧已经结束并从下一个时间片起进入下一帧。还有一些会导致强制渲染了,比如代码改变了一个元件的外观,鼠标又刚好掠过时。当然这些在最后一个时间片依然会被重新渲染
渲染时间间隔将变得不固定。举个例子吧帧率20fps的swf运行在50个时间片每秒的平台上。理论上是2.5个时间片播放一帧。Flash Player做了处理,改为每5个时间片播放两帧,swf的渲染频率是 2-3-2-3-2-3
参考资料
下面就是可变跑道的出处
-------------------------------------------------------------------------------------------
flash/actionscript3 性能优化汇总<一>
关于flash的性能优化,涉及到方方面面。有些跟平台相关,有些跟api有关,有些跟算法沾边。这里收集了各种性能优化的tip,来源甚多,包括官方的那份优化文档,网上的技术文档……绝大部分都非本人原创,只是收集而已。不断更新中
性能优化本身是一个吃力不讨好的事情。Premature Optimization is the root of all evil。能不优化尽量不要优化,除非证明是值得的。性能的好坏不是靠感觉,靠的是真实的运行数据。性能问题,一般是架构问题,小修小补不能解决问题。小的投机技巧跟代码的质量相比,是微不足道的。还是那句老话,先让它跑得正确,再跑得快。还有一点也不容忽视,就是代码的可维护性。好吧,转入正题了。
先获取数组的长度,再循环,如果要多次下标访问,可以先用变量保存起来。标准的代码如下
var len:int = arr.length;
for(var i:int = 0;i<len;i++)
{
v = arr[i];
v.a = i;
v.b = i+1;
}
说实话,这样的技巧我是比较不赞同的,代码的可读性被破坏了。看到>>,我们马上要停顿下来,转换成 * ,浪费了大脑的计算。
观点同上,不赞同
Math.floor(1.5) 就 比 int(1.5)慢
Math.ceil(1.5) 就比 int(1.5)+1 慢
有时为了代码的简洁,牺牲一点点性能是值得的
复制方法有很多,逐个拷贝,push,slice 和 concat,经过测试 concat的效率是最高的
Vector是fp10才有的,是强类型的,效率比较高。如果能预先知道数组的大小,构造时提供长度,性能更好,减少了操作过程中动态增加数组大小的消耗
这一点,即使不能提供运行速度,编译速度肯定好一点点。同时也是一个好习惯
能用简单的就简单的,简单了,就自然快了。简单的图形首先Shape,不需要帧的原件选Sprite
同上
有句俗话叫占着茅坑不**,占着也是占用资源的
创建对象的开销是很大的。可以考虑我下面谈及的”对象池”技术
enterframe事件被频繁执行,如果处理事件的运算量很大,必然影响整体的性能
alpha涉及到cpu计算
什么样的问题,用什么样的计算。千万不要大炮打蚊子
一个比较常用的技巧是使用对象池。说起来很简单,就是数组保存创建的好的对象,需要时就去取。用完后,重置状态(注意,不要设为null,否则会被回收),重新插入数组保存。网上有一些开源的,我建议自己实现
不要简单地认为设为null就万事大吉了。只要还有引用,GC就不会回收的。要做的事情很多,删除各种监听的事件,停止动画,移除对象,设置为null。如果是BitmapData,调用dispose,立即会释放内存,但还是要设为null,才会被回收
------------------------------------------------------------------------------------------
flash/actionscript3 性能优化汇总<三>
矢量图的优点时小,但是计算量大,就是为什么很多flash游戏卡的原因了。现在的大部分的网页游戏都是贴位图的方式。CPU比内存要珍贵,CPU一卡就什么都完蛋了
从嵌入图像创建BitmapData实例时,将对所有BitmapData实例使用位图的同一版本,这样可以大幅降低内存了。
因为使用滤镜,在内存会有用两个位图,第一个是初始化的位图,第二个是使用滤镜的位图。当修改滤镜的某个属性时,内存中的两个位图都要根据,还消耗CPU。fp10.1有个新功能,滤镜在30s内没有修改,系统会将它隐藏或者置于屏幕外,释放未使用滤镜的位图。比较好的方便是,用处理好的效果图直接导入
适用于只读文本,占用较少的内存并且呈现效果更好。对于输入文本,最好使用TextField。因为在创建典型行为时,需要的AS代码较少(这个是从官方文档抄来了,代码少,就优化了,待考证:)
事件模型毕竟要消耗一些的,尤其是上面提及的EnterFrame事件。捕获,冒泡等就够消耗了
简单地说,就是as的事件模型,为某些事件提供了一个捕获和冒泡阶段,运行你侦听来自父类InteractiveObject这些事件。例如,你要对很多个相同实例的元件监听鼠标点击事件。传统方法是为每个元件监听鼠标点击事件。可以优化一下让父容器监听鼠标点击事件,可以显著减少事件处理
传统的方法是改变单个像素点setPixel
可以用一个临时变量获取后,重复利用
这个好处是用户不用一直load文件,还有一个最大好处是可以省下不少带宽银子
官方不是随便更新的,肯定是为了修正bug或者提供了更好的东东
还不如直接做个简单的。顺便吐槽一下,既然那么差,为什么官方不花点精力改进一下
可以减少运算量
-----------------------------------------------------------------------------------
swf / actionscript3 执行顺序
当运行一个swf文件时,你知道它的具体执行顺序吗?哪里是起点,初始化过程是怎样的,每一帧执行的时候,又执行了哪些操作。本文将探讨这些。本文大部分内容是参考了《Order of Operations in ActionScript》,结合自己的一点理解,希望能将这个比较深入难懂的话题表述得尽可能简单,易懂。
凭直觉,我们都觉得一个swf的初始点都是从 document class (文档类)的构造函数开始的。实际情况却有些不同。真正的初始点应该是main timeline(主时间轴)第一帧上的元件初始化。这里可以做一个简单的测试。在文档类的构造函数trace,然后再主时间轴上新建一个元件,该元件的构造函数也trace。经过测试,发现主时间轴上的元件的构造函数先调用。
时间轴上对象的创建是bottom up,自底向上,从子到父类,一直到文档类。从这点上看,父类们应该先存在才对啊。所以Flash想出一个比较理想的办法来实现。将初始化过程分成两个阶段。第一个过程初始化对象,包括创建显示列表,类属性的空间分配。第二个过程是真正的创建过程,包括属性的赋值,构造函数的调用,相关事件的触发。下面给出详细的初始化过程:
- 所有的显示对象初始化
- 显示列表创建
- 类的所以属性分配空间
- 显示对象创建,从子类开始,自底向上
- 父类实例变量设置;DisplayObject.name 定义
- 显示列表设置(parent,stage,root,loaderInfo)
- 构造函数调用
- 事件:DisplayObject.added 触发
- 事件:DisplayObject.addedToStage 触发
这种初始化方式有很多好处,比如,你在构造函数里面就可以获得所以显示列表了。你也可以控制所有子类了
有些同学可能对构造函数的调用不太明白,看了下面的构造函数调用顺序就明白了
stage
root(7)
-child1(6)
–grandchild1(5)
–grandchild2(4)
-child2(3)
–grandchild1(2)
–grandchild2(1)
什么时候对象会被销毁?当一个对象没有引用时,就可能被GC清除了。特别说明一下,时间轴上的对象销毁比较特殊。当跳到下一帧,当前帧的所有对象都将被销毁。对象被销毁时,没有类似c++的析构函数,只能通过捕获相关事件。对象的移除时从父类开始的,跟对象的构建相反。但是顺序依然是bottom up。下面是对象销毁的相关操作顺序:
1.显示对象从显示列表中移除,自底向上
2.事件 DisplayObject.removed 触发
3.事件 DisplayObject.removedFromStage 触发。父类开始,自底向上
4.父类实例命名变量设为null
5.显示列表属性设为null(parent,stage,root,loaderInfo)
6.显示对象被GC销毁
假如主时间轴被移除了,销毁的顺序(可以监听removed事件)
stage
–root
—child1(2)
—–grandchild1
—–grandchild2
—child2(1)
—–grandchild1
有一点要注意的,只有最高层的父类从显示列表中移除,所有从显示列表移除的子类都会得到一个removeFromStage事件,从父类开始,然后传播到它的孩子们。
stage
–root (Never removed)
—-child1 (5)
——grandchild1 (7)
——grandchild2 (6)
—-child2 (2)
——grandchild1 (4)
——grandchild2 (3)
—-child3 (1)
Flash中有两种概念的帧,一种是视觉帧,一种是时间度量的帧。视觉帧就是我们在时间轴上的动画元件等。时间度量的帧就是一个swf根据帧速如何执行。
帧的执行顺序如下
1.上一帧不存在,当前帧存在的对象被声明,numChildren更新
2.事件DisplayObject.enterFrame触发(当前帧的对象还是空对象)
3.上一帧不存在,当前帧存在的对象被创建
4.时间DisplayObject.frameConstructed触发(FP10+)
5.帧代码被执行 父类开始,自顶向下
6.事件DisplayObject.exitFrame(FP10+)
7.事件DisplayObject.render触发(假如stage.invalidate()被调用
8.屏幕更新
9.空闲
–i.用户时间执行
–ii.可能重新渲染
10.只在当前帧出现的对象,下一帧不存在的对象被销毁
end
-----------------------------------------------------------------------------
flash重绘机制总结
重绘,是flash性能的一个关键因素。本文将总结flash 重绘的各个方面,包括重绘的定义,计算区域,重绘的时机和如何减少重绘
Flash 会以一定的速率(帧速)刷新屏幕上显示的内容,这个过程就是重绘
如何查看重绘区域?就不用说了吧。右键点击一下就出来了。重绘区域的计算比较复杂些,不是我们简单地认为,有几个元件在重绘,就把这些重绘区域的面积加起来。Flash Player对重绘区域进行了一定处理。在舞台上,最多只有3个重绘区域。多余三个怎么办呢?只能把临近的区域进行合并了。这个算法官方也没有公开。我们只能粗略地认为,如果重绘区域超过3个,FP就把临近的,单独的重绘区域进行合并。该区域的任何元素需要重绘时,整个区域就必须重绘
1.跳帧时,如果跟上一帧有变化的重绘区域需要重绘
2.舞台上的元件的形状,位置 状态(appha,scale)发生改变是
3.DisplayObject的层次关系(childindex)发生变化
4.某些事件引起的重绘,例如在一个buttom上面划过鼠标,在一个buttonMode为true的Sprite/MovieClip按下鼠标
1.移除不需要的动画。将visible设为false,并不能停止重绘。应该是停掉动画(包括子元件的)。最好是removeChild直接移除
2.将显示对象移出当前舞台,是不会再重绘的。甚至窗口最小化都停止重绘。但是这样做只是临时的。因为该元件还在后台播放,会消耗某些资源。也不能及时被GC回收
3.设置DisplayObject的层级关系时候,先做判断,可以有一定概率改进性能
{
myContainer.setChildIndex(myChild, 0);
}
4.当一个Sprite/MovieClip cacheAsBitmap = true,该对象已经被当做一个整体的重绘区域了,当它的某个区域(即使被遮盖了)发生变化,会导致整个Sprite/MovieClip发生重绘。
一个改进是,将该变化区域用scrollRect 进行优化
处理完事件后,调用updateAfterEvent()可以强制重绘
深入理解flash-player重绘
-----------------------------------------------------------------
深度探索actionscript3事件
as3的事件机制很好用了。但要用好,用对,就得详细了解它的事件模型。本文将深度探索as3事件的各个方面
as3的事件系统包括 dispatchers(派发器),listeners(监听器)和event object(事件对象)。它们的关系用一句话概括:dispatchers向已注册的listeners派发event object。具体的关系图可以参考下面的图
as3的事件过程包括三个阶段 捕获,目标和冒泡阶段。下面我举一个很简单的例子来说明。舞台stage上有一个元件mc,mc上有一个子按钮元件btn。现在点击btn,发生了什么事呢?看下图
捕获阶段:鼠标在btn上发出点击事件。首先捕获该事件的是stage,然后向下传递到mc,再到btn
目标阶段:找到鼠标的最底层的目标btn,如果它注册了监听函数,就执行该监听函数了
冒泡阶段:开始冒泡,自底向上。从btn到mc,最后到stage
代码如下
mc.btn.addEventListener(MouseEvent.CLICK,onClickBtn);
function onClickMc(event:MouseEvent) {
trace("点击mc");
}
function onClickBtn(event:MouseEvent) {
trace("点击btn");
}
执行结果:
点击btn
点击mc
我们通常只用了前面两个参数,后面的都是默认参数。下面详细讨论一下
第三个参数 useCapture
“确定侦听器是运行于捕获阶段、目标阶段还是冒泡阶段。如果将 useCapture 设置为 true,则侦听器只在捕获阶段处理事件,而不在目标或冒泡阶段处理事件。如果 useCapture 为 false,则侦听器只在目标或冒泡阶段处理事件。要在所有三个阶段都侦听事件,请调用 addEventListener 两次:一次将 useCapture 设置为 true,一次将 useCapture 设置为 false.”
第四个参数 priority优先级
默认是0.可以设置成任意的整数。当你为同一个事件监听了两个处理函数,设置不同的优先级,就可以决定他们被处理的顺序了
第五个参数 useWeekReference 是否弱引用
决定监听器的引用是否弱引用。如果是弱引用,当listener只剩下这一个弱引用的时候,GC就可以回收了
先来看看它们的定义。target是事件的调用者(event dispatcher)。currentTarget是事件的处理者。
在一般情况下,两者指向的是同一对象。例如我们对某个单独的mc监听鼠标点击事件。在MouseEvent.CLICK处理函数中 target和currentTarget是同一个对象
现在来看看不同的情况。有两个元件 m1和m2,m2是m1的子元件。给m1监听鼠标点击事件。
现在点击m1(不包括m2)的部分,处理函数中显示:target和currentTarget均指向父容器m1。
点击m2,处理函数中显示 target指向m2,currentTarget指向m1。
这是为什么呢?因为as3的事件处理默认是采用冒泡机制的。currentTarget应该先指向低层,再向上冒泡。
小结一下:target一般来说都是最里层的对象。currentTarget则是注册监听器的对象,一般是容器
冒泡机制是很有好处了,但是我们有时要阻止冒泡。as3也提供了阻止冒泡的机制
1.stopImmediatePropagation():void
防止对事件流中当前节点中和所有后续节点中的事件侦听器进行处理。
2.stopPropagation():void
防止对事件流中当前节点的后续节点中的所有事件侦听器进行处理。
两者的区别在于是否阻止当前节点?处理到当前节点了,还要阻止当前节点?头脑有些转不过弯吧。当你为同一个对象的同一个事件监听处理时,这个功能就有用了。处理了第一个监听函数,调用stopImmediatePropagation()。其他的监听函数就无法收到事件了
很简单,继承Event就行啦。如果可能,顺便重新clone和toString函数,因为派发事件的事件的时候会自动调用
也很简单,继承EventDispatcher就行了,或者实现IEventDispatcher接口
不是所有的事件都有这三个阶段。如Timer、URLLoader,它们的事件对象将直接派送给目标对象(target).它们只包含目标阶段而没有捕获阶段和冒泡阶段。因为她们根本不需要显示列表
http://goday.blogbus.com/logs/14062836.html
----------------------------------------------------------------------------------
flash小游戏开发
网页游戏开发中,尤其是sns类型的游戏,经常要开发小游戏。这个说难也不难,说简单也不简单。不难,随便找个新人,培训一两周as3,就可以做个小游戏上手了。不容易,要开发得好,代码可读性强,容易维护,却不易。我去看了本人所在项目的源代码,发现小游戏的代码质量相对于其他基础系统的代码质量要差一些。查阅了相关资料,书籍,发现前人已经终结出了一个个开发框架,可以套用的。
先举一个特别简单的例子吧。打飞机游戏(想到其他地方,思想不纯洁的同学先去面壁:)。功能特别简单,通过控制键盘的方向键,操控飞机的运行方向。按space键,发射子弹,如果子弹打中空中飘浮的障碍,就将其击破。这个游戏是典型的键盘操作游戏,物体的运行是根据时间来决定的。可以参考以下的步骤设计:
1.定义变量,arrowUp,arrowDown,arrowLeft,arrowRight
2.初始化一批障碍物,角度随机,速度随机
3.舞台监听 键盘按下事件 和 键盘松开事件。在监听事件处理函数中记录按下的键值,相应的变量设为true,松开时设为true。如果按下空白键,就新建一个子弹对象,具有初始速度dx,dy,旋转角度跟飞机的相同
4.在enterFrame中进行以下操作
4.1.计算距离上次的时间差,
4.2.移动飞机
4.3.移动子弹
4.4.移动障碍物
4.5.判断是否发生碰撞(通常比较偷懒的办法是,所有要检测的对象都统一存储在数组中,遍历检测碰撞)
5.游戏结束
看了上面的介绍是不是觉得思路比较清晰了。其实大部分小游戏都可以抽象成几大类。每一类都有原型可以参考。利用原型开发,可以大大提高开发效率,代码质量也大大提高。这里提供一本经典的书籍参考《ActionScript 3.0 Game Programming University》,学好里面的范例,就基本入门了。
后记,写这本blog的时候,重新研读了pureMVC源码,发现一些的小游戏也可以用pureMVC来开发,解耦性更好。准备另写一篇blog描述一下思路。