class rkuk implements ActionScript

我的ActionScript小路

« 一口气送上12本Flash CS4和ActionScript教程轻松实现3D图片墙 »

TweenLite源码剖析6

休息了几天,接着TweenLite初始化过程开始。第一步就是在用户设置了ease函数的情况下,替换默认的ease。

TweenLite.init过程的第一步 [复制代码]
  1. var p:String, i:int, plugin:*, prioritize:Boolean, siblings:Array;
  2. if (typeof(this.vars.ease) == "function") {
  3.     _ease = this.vars.ease;
  4. }
  5. if (this.vars.easeParams) {
  6.     this.vars.proxiedEase = _ease;
  7.     _ease = easeProxy;
  8. }
  9. this.cachedPT1 = null;
  10. this.propTweenLookup = {};

如果用户指定的ease函数还需要输入一般ease函数所需的四个参数之外的其它参数,那么这些参数应定义在vars.easeParams中,这时TweenLite tool会使用一个easeProxy函数将用户指定的ease函数封装,以保证能够把额外的参数传入到用户指定的ease函数中。

接下来初始化两个变量cachedPT1和propTweenLookup,在后面的初始化过程中会用到这两变量,其中cachedPT1用于引用一个PropTween对象,propTweenLookup用于根据target被操作的属性查找操作其变化的PropTween,但其实在构造函数中已经对propTweenLookup进行过初始化,因此这里的初始化实际是多余的,可以删除掉。

是时候进一步说明TweenLite如何实现操作target的属性了。一个TweenLite可以同时操作一个target的多个属性,例如一个TweenLite对象可以同时操作一个MovieClip的x、y、scaleX,transform.matrix等。实际上TweenLite中会为每个被操作的属性创建一个PropTween对象(使用plugin操作的时候也可能是为某几个属性共同创建一个PropTween),最终被TweenLite操作的每个属性都会对应一个TweenLite为其创建的PropTween对象(这种映射关系就保存在propTweenLookup中),TweenLite就是通过创建这些PropTween对象来操作target的属性的。因此一个TweenLite中可能包含多个PropTween,TweenLite会在创建这些PropTween的时候让它们串成一个链表,而这个链表的开头就由cachedPT1引用(PropTween被串联起来包含在TweenLite中,这样的关系就类似于TweenLite被串联起来包含在root时间线中一样)。PropTween中记录了如下信息:

PropTween的构造函数 [复制代码]
  1. this.target = target;
  2. this.property = property;
  3. this.start = start;
  4. this.change = change;
  5. this.name = name;
  6. this.isPlugin = isPlugin;
  7. if (nextNode) {
  8.     nextNode.prevNode = this;
  9.     this.nextNode = nextNode;
  10. }
  11. this.priority = priority;

其中,target记录了PropTween操作的对象,这个对象可能就是TweenLite操作的对象,也可能是一个plugin,因为TweenLite操作的对象的某些属性,可能需要由plugin来处理,这时PropTween实际是通过操作plugin来实现操作“TweenLite操作对象”的属性的。

property记录了PropTween应该操作上述target的哪个属性,当target就是TweenLite操作的对象时,这个property也就是TweenLite操作的属性;而当target是plugin的时候,这个属性为“changeFactor”,也就是说对于任何plugin来说,PropTween都是操作plugin的changeFactor属性(所以plugin中都要定义这个属性)。

start和change记录的是PropTween操作的property的取值范围,start是target起始状态时对应的property取值,change是target在终点状态时对应的property取值相对于start的变化量。当target就是TweenLite操作的对象时,start和change就对应TweenLite操作的对象的起始状态和终点状态的property取值和变化量。当target是plugin时,那么start恒定等于0,change恒定等于1,这也是对plugin操作中的约定。

name表示当前这个PropTween的名字,如果当前PropTween只操作“TweenLite操作对象”的一个属性,那么name就等于这个属性名字(如果PropTween的target就是TweenLite操作的对象,那么PropTween肯定就只操作一个属性),如果PropTween的target是一个plugin,而且这个plugin会操作“TweenLite操作对象”的多个属性,那么PropTween的名字就是“_MULTIPLE_”。

isPlugin标识了当前PropTween的target是否是一个plugin。

PropTween中也通过prevNode和nextNode建立链表,TweenLite中创建PropTween的时候,后创建的PropTween会排在链表的前面。但如果target是plugin的时候,plugin可能会有不同的优先级,这个优先级会记录在PropTween的priority中,当TweenLite把所有PropTween建立完成后,会根据这个优先级对所有PropTween进行排序。

对PropTween的说明目前看起来还很抽象,接下来继续从TweenLite的初始化中看看PropTween是怎么操作的:

创建PropTween的过程 [复制代码]
  1. for (p in this.vars) {
  2.     if (p in _reservedProps && !(p == "timeScale" && this.target is TweenCore)) {
  3.     //Ignore
  4.     } else if (p in plugins && (plugin = new (plugins[p] as Class)()).onInitTween(this.target, this.vars[p], this)) {
  5.         this.cachedPT1 = new PropTween(plugin,
  6.                                         "changeFactor",
  7.                                         0,
  8.                                         1,
  9.                                         (plugin.overwriteProps.length == 1) ? plugin.overwriteProps[0] : "_MULTIPLE_",
  10.                                         true,
  11.                                         this.cachedPT1);
  12.                                        
  13.         if (this.cachedPT1.name == "_MULTIPLE_") {
  14.             i = plugin.overwriteProps.length;
  15.             while (i--) {
  16.                 this.propTweenLookup[plugin.overwriteProps[i]] = this.cachedPT1;
  17.             }
  18.         } else {
  19.             this.propTweenLookup[this.cachedPT1.name] = this.cachedPT1;
  20.         }
  21.         if (plugin.priority) {
  22.             this.cachedPT1.priority = plugin.priority;
  23.             prioritize = true;
  24.         }
  25.         if (plugin.onDisable || plugin.onEnable) {
  26.             _notifyPluginsOfEnabled = true;
  27.         }
  28.         _hasPlugins = true;
  29.        
  30.     } else {
  31.         this.cachedPT1 = new PropTween(this.target,
  32.                                         p,
  33.                                         Number(this.target[p]),
  34.                                         (typeof(this.vars[p]) == "number") ? Number(this.vars[p]) - this.target[p] : Number(this.vars[p]),
  35.                                         p,
  36.                                         false,
  37.                                         this.cachedPT1);
  38.         this.propTweenLookup[p] = this.cachedPT1;
  39.     }              
  40. }

首先会用一个for循环来遍历所有创建TweenLite时的vars中的属性,这些属性有的是用于设置TweenLite本身的,例如delay、immediateRender、onStart等;有的是指定TweenLite用plugin来操作target(首先需要注册这个plugin),例如vars中定义scale的话就表示用ScalePlugin来处理;还有一些是直接指定target被操作的属性,例如target是一个MovieClip时,定义在vars中的x、y、width等就是指示TweenLite通过PropTween直接操作这些属性。既然有vars中有三种用途的属性,那么在这个for循环中当然就会一一把它们识别出来,保证这些属性被正确处理。

在for循环中,首先识别设置TweenLite的属性,这些属性就是所谓的TweenLite tool保留的关键字,被储存在TweenLite._reversedProps数组中,当发现vars上定义的某个属性存在关键字数组中,那么就不会为这个属性创建PropTween,表示这个属性是设置TweenLite本身的,而不是要进行变换的,这些属性通常是在其TweenLite的构造函数中等地方就已经使用过了,所以这里也不加处理。

其次是识别vars中定义的属性是否需要某个plugin来操作。TweenLite有一个静态属性plugins,当某个plugin被注册后(参见TweenPlugin.activate),那么被注册的plugin所能处理的属性就会保存到TweenLite.plugins中。这里就会检测vars中定义的属性是否存在于TweenPlugin.plugins中,如果存在那么就会调用plugin的onInitTween方法初始化这个plugin(因此每个plugin必须实现这个方法),如果初始化话成功的话,那么就会创建一个PropTween来包含这个plugin,对target的操作过程就通过这个PropTween调用plugin操作来实现。

最后如果vars中定义的属性既不是设置TweenLite的,也没有注册plugin来处理,那么TweenLite tool就会认为这个属性是直接属于target本身的,就会创建一个PropTween来直接操作target的这个属性。这里就遇到一种错误的可能,就是vars中定义的属性既不是设置TweenLite的,也没有plugin来处理,而target本身其实也没有定义这个属性,但这时TweenLite tool并不知道target其实是没有这个属性的,它仍然会创建PropTween来直接操作target,这样的结果就是抛出异常,因为target没有这个属性可以被操作。前面在说TweenCore的构造函数时曾说到如果在vars中定义reversed属性的话,通常就会遇到这样的错误。

这里还需要对创建PropTween操作plugin和创建PropTween直接操作target的过程再做一下说明:
当vars中定义的属性需要plugin来处理时,TweenLite创建的PropTween会操作plugin的changeFactor属性来实现最终对目标的操作。实际上所有使用的plugin都继承自TweenPlugin类,这个类中定义了changeFactor setter方法,但是未做任何处理,实际使用的plugin应重载这个方法来实现对target的属性的处理,这个方法接受一个参数,对于plugin来说接收到的这个参数的范围通常是[0,1]区间内的值,但当使用一些特殊的ease例如Elastic时可能超出这个范围,不过无论怎样, 0就表示plugin操作的属性为对象在没有被TweenLite操作前的状态,1就表示属性为vars上设置的状态。这里要注意一下用TweenLite.to和TweenLite.from时,plugin.changeFactor=0对于to方法就对应Tween起始状态,而对于from方法plugin.changeFactor=0则对应于Tween完成后的状态;再看plugin.changeFactor=1时,对于to方法就对应Tween终点状态,也就是vars设置的状态,而对于from方法plugin.changeFactor=0则对应于Tween的开始的状态,同样也是vars设置的状态。因此只要记住不论通过什么方法创建TweenLite,plugin.changeFactor=1时的属性值都对应vars设置的状态就行了。另外,一个plugin的操作可能同时会影响对象的多个属性(例如ScalePlugin会同时影响对象的width,height,scaleX和scaleY)。TweenLite为了能够实现在Tween的过程中能够取消对指定属性的操作(通过killVars方法),所以会把影响每个属性的PropTween都做记录,保存在前面所说的propTweenLookup中,这样可以实现根据某个被操作的属性查找到操作其的PropTween。例如一个包含ScalePlugin的PropTween,可以通过propTweenLookup[“width”]、propTweenLookup[“height”]、propTweenLookup[“scaleX”]和propTweenLookup[“scaleY”]都会查找到这个PropTween。上述代码中在创建完包含plugin的PropTween后,就会完成这个记录。

再看看当vars中定义的属性不需要plugin来,而是让PropTween直接处理时的情况,这时PropTween就是直接操作TweenLite的目标对象,因此要保证vars上定义的这个属性在TweenLite的目标对象中确实存在,否则就会抛出异常。同时在这种情况下,PropTween只能操作TweenLite目标对象的纯数字类型的属性(例如x,y,width等,而不能是visible,matrix等)。PropTween中记录这个属性的起点值和终点相对起点的变化值(这与包含plugin的PropTween不同,如上所述包含plugin的PropTween记录的值是0和1)。同样的,直接操作目标对象的PropTween也会根据其操作的属性,把它记录在propTweenLookup中,因为直接操作目标对象的PropTween只能操作一个属性,不像plugin可以操作多个属性,所以直接操作目标对象的PropTween在propTweenLookup中的记录肯定只有一个。

不论创建包含plugin的PropTween还是创建直接操作目标对象的PropTween,它们都是在创建的时候才记录下目标对象属性的起点值和终点值,所以实际Tween的起点和终点要以这个记录为准。需要注意的是PropTween是在TweenLite初始化过程中创建的,而初始化又是在第一次renderTime的时候才执行,因此Tween的起点和终点要以第一次渲染的时候目标对象的状态和TweenLite的设置状态为准,而不能以TweenLite创建时刻的目标对象状态为准,因为在TweenLite创建和TweenLite第一次渲染之间,目标对象的状态可能会发生变化。此外,代码中可以看到TweenLite初始化中创建PropTween的时候,会让所有PropTween形成链表(见PropTween构造函数中nextNode参数),在不涉及priority时,后创建的PropTween会在链表的最前面,在渲染的时候会先渲染。

PropTween的创建过程算是介绍完了,我想TweenLite怎么实现操作target的过程也逐渐明了起来。休息一下,我穿插的说说目前TweenLite tool中PropTween的这个创建过程中存在一个小bug,会影响killVars的效果。因为在把操作某个属性的PropTween存入propTweenLookup的时候,并没有检查是否已有PropTween操作这个属性,也没有相应的处理,这样的结果就是如果同时存在两个PropTween操作同一个属性的话,那么就无法根据这个属性从propTweenLookup查找到先创建的那个PropTween,而这个PropTween又是存在于渲染的链表当中的,因此killVars无法取消这个没有做记录的PropTween对这个属性的操作。这种情况会发生在同一个TweenLite使用的多个plugin之间存在重复操作的属性,或者plugin操作的属性与PropTween直接操作的属性重复时,例如这样创建一个Tween:

发现bug的一个测试用例 [复制代码]
  1. TweenLite.to(mc,5,{width:100,scale:2});

TweenLite会为vars中的width创建一个直接操作mc.width的PropTween,同时TweenLite也会scale创建一个包含ScalePlugin的PropTween,而ScalePlugin也会操作mc.width,这样从propTweenLookup[“width”]查找时只能找到直接操作mc.width的PropTween或者包含ScalePlugin的PropTween(具体查找到哪个要看哪个PropTween后创建,通过改变vars中width:100和scale:2的顺序可影响查找的结果),当执行TweenLite.killVars({width:1})之后,也只能删除被查找到的那个PropTween对width的操作,另一个PropTween对width的操作仍然存在,因此killVars的效果未达到。

  • quote 1.zszen
  • http://zszen.com
  • TweenLite.to(mc,.3,{rotation:45});
    旋转在重复的时候好像有问题,如果度数是相同的好像有个bug
    TweenLite.to(mc,5,{frame:xxx});
    问题更大, 不能随时重复覆盖, 否则不会对mc的frame有任何好处
    rkuk 于 2009-12-7 19:06:58 回复
    不太明白你所指的bug是什么,看来你对TweenLite也挺有兴趣的呵呵,可以一起探讨:)
  • 2009-12-7 1:05:41 回复该留言
  • quote 2.zszen
  • http://zszen.com
  • 只是感兴趣而已, 还没疯狂的去看它代码. 只知道它是用多态mvc架构搭建的, 对于这类引擎, 基本研究了也就是看看两三句... 囧

    不过确实有一些不合人意的bug, 比如我在as2的时候设置frame的跳转
    最简单的就是对一组按钮的效果的前进和回放

    我会在触及over的时候先clear一下所有按钮,再frameto这个按钮
    当触及out时候就直接一个clear即可

    as3里面如果覆盖真的是很麻烦, 导致frame根本不会听我的话, 当时也没试试max.to
    其实我知道效率不高 但是很多时候是没时间考虑最优化, 尤其是顶层实现

    现在问个专业的
    ease这里面复杂的判断inAndOut都会有一个判断
    tweenlite用的是if (t < d/2)
    tweener用的是if ((t/=d/2) < 1)
    tweener的outAndIn用的是跟tweenlite一样
    这个判断是什么
    我发现outandin和inandout都是对in和out的混合使用,而且发现任意的in和out,甚至任意的in和in或是out和out都能混合

    给我邮件发封信 我们以后可以邮件沟通
    rkuk 于 2009-12-9 18:10:12 回复
    还没有仔细看各种ease,不过当时有easeInOut觉得很好奇,不知道怎么能够在Tween中既easeIn又easeOut,后来大概看了才知道原来in和out不是同时发生的。而是在Tween的前半段过程用in,在后半段用out,所以这里就有了t < d/2这个判断,当这个条件成立的时候采用easeIn,不成立的时候采用easeOut。

    由 rkuk 于 2009-12-9 18:11:47 最后编辑
  • 2009-12-8 23:54:11 回复该留言
  • quote 4.zszen
  • http://zszen.com
  • 感觉算法之类的差不多就可以了, 在最终的程序创作过程一般不会考虑这类优化, 除非是内核级或是供应开源类之类的.
  • 2009-12-27 0:55:37 回复该留言

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

日历

最新评论及回复

最近发表

Copyright © 2008 www.rkuk.org, All Rights Reserved.

Powered By Z-Blog 1.8 Arwen Build 90619