Core:StarlingCode

来自Starling中文站
跳转到: 导航, 搜索

目录

Starling源码分析

通过阅读Starling的源码,可以让您更深入的了解Starling的运行和扩展机制。

为了避免歧义,在下午中涉及类名的描述中,会用括号标注是普通Flash包下的那个类,还是Starling的类。因为Starling的显示对象命名和传统Flash的几乎一致,需要大家多多注意。

Starling中文站原创文章,转载请注明出处和原链接。

Product-feature-rocket-starling.png

关于Starling类

首先看看starling.core.Starling的构造方法:

public function Starling(rootClass:Class, stage:flash.display.Stage, 
                                 viewPort:Rectangle=null, stage3D:Stage3D=null,
                                 renderMode:String="auto", profile:String="baselineConstrained")

在构造方法中,前两个参数就不解释了,后面几个参数的意义和默认值是:

  • viewPort 体现了视口的概念,所谓视口,就是类似遮罩,给Starling的场景定义一个矩形的可见区域。默认情况下,Starling会根据普通Flash层的stage.stageWidth, stage.stageHeight这两个参数,确定Starling的场景大小和视口大小。
  • stage3D 默认取的是stage.stage3Ds[0],大多数情况下我们用的都是0这个位置的Stage3D实例
  • renderMode和profile则其实是mStage3D.requestContext3D方法所需的两个参数,第一个确定渲染方式(默认值为Context3DRenderMode.AUTO,可选值为Context3DRenderMode.SOFTWARE,后面这个值可以开启软件渲染,方便测试),第二个确定驱动模式,默认值为Context3DProfile.BASELINE_CONSTRAINED,即默认处于“约束模式”之下,约束模式可以更好的兼容老旧设备,但对场景的渲染尺寸和屏幕分辨率有要求,如果您的代码无法正常工作,可以切换回Context3DProfile.BASELINE试一下。
TIP: 和传统Flash类似,您可以设置stage.color(注意是Starling的Stage)来改变场景的默认底色。

在构造方法中,Starling还创建了一个名为mNativeOverlay的Sprite实例(是Flash的Sprite),并把它添加到Flash的Stage中。并且当视口被改变的时候,这个mNativeOverlay的位置也会被改变(x,y始终处于视口的左上角的位置),并且一旦视口的尺寸和Starling的Stage尺寸不一致的时候,mNativeOverlay会被缩放(改变scaleX和scaleY)。它的用途是,如果您创建了一个传统Flash显示对象,显然无法添加到Starling的显示列表中,但您可以通过Starling.nativeOverlay属性获取这个处于传统显示层面的Sprite引用,然后把显示对象添加进去。

但是需要注意,在3D加速内容之上的原生Flash内容在某些(移动)平台上会有性能上的问题。因为这个原因,请注意删除这一层中所有不需要和不再使用的对象。当这一层被置空后Starling会从显示列表中删除原生Flash叠加层。

事件

如果您创建了Starling实例,可以得到两个事件:context3DCreate和rootCreated,前者代表context3D实例被创建,后者代表RootClass的实例被创建。

另外就是交互事件。和传统Flash显示对象不同,Stage3D最终处理的是一堆三角形,这些东西可没有传统Flash显示对象那样的交互响应能力。那么Starling是如何处理交互事件的呢?它是在传统Flash的Stage层进行侦听的,然后通过点击的那个点的坐标,并通过显示区域和遮挡关系判断,来确定点击的是哪个“显示对象”。看一下touchEventTypes这个方法,您就会知道,它同时处理MouseEvent和TouchEvent,这样可以兼顾PC平台和移动平台。

捕获到的交互事件,会统一交给onTouch这个方法进行处理。在这个方法里,会区分事件是Mouse事件还是Touch事件,并分别进行处理。注意在Starling的显示对象中,没有Mouse事件,只有Touch事件,PC上也是如此。所以Starling实际上是将捕获到的传统事件,进行处理和封装之后,统一给我们提供一套Starling自身的事件机制。这一点上,不要和Flash自身的事件机制发生混淆。

还需要注意的一点是,交互事件会有"延迟",因为Starling捕获到原生Flash层的事件之后,不会马上派发给侦听Starling事件的对象,而是先发送内部的“队列”里面,然后根据帧频,进行集中处理。不过一般我们的帧频不会设置的太小,这个延迟对用户基本没有影响。

Starling的'跑道机制'

如果您了解Flash动画的渲染机制,就一定会知道Flash Player有一个“跑道机制”,根据设定的帧频,Flash可以在每一秒执行若干次的渲染,来保持画面的更新。而每一帧Flash都会派发EnterFrame事件,很多时候我们也会侦听EnterFrame事件,来实现代码层面的动画控制。

Starling也在侦听Flash的EnterFrame事件,然后在每一帧调用nextFrame方法。这个方法其实就是取一下当前时间(用getTimer),并存储在mLastFrameTimestamp(换算为秒)上,将当前时间和上一帧的时间做减法,计算出时间差,然后将时间差作为参数传递给advanceTime方法。

advanceTime这个方法在Starling的类中很常见,通常情况下这个方法的调用是和EnterFrame事件绑定的,也就是说,根据帧频,不同对象的advanceTime方法,每间隔一个很短的周期,就会被调用一次。而且这个方法会收到一个参数,就是时间差(秒)。

在Starling类中,当前处于活动状态的Starling实例,会不断调用自身的advanceTime方法,在这个方法中,我们可以看到,又调用了3个主要对象的advanceTime方法:

//Starling内部处理事件用
mTouchProcessor.advanceTime(passedTime);
//从Stage开始,处理所有的显示对象
mStage.advanceTime(passedTime);
//处理注册到juggler的动画
mJuggler.advanceTime(passedTime);

下面我们就分析一下这3个主要的调用,看他们完成了哪些工作:

内部处理事件

这个工作是由TouchProcessor类完成的,它的重要方法包括两个:

  • enqueue方法 - 这个方法接受Starling当前活动对象产生的事件(由原生Flash层的事件转换而来),然后将事件的参数(id,位置等等),加入到mQueue这个数组中,排队等候处理。
  • advanceTime方法 - 这个方法完成的操作包括:清理旧的Tap标记(因为Tap操作需要较长的生命周期,所以额外处理),更新已有的Touch对象;将mQueue排队的事件参数进行处理,产生新的Touch对象,并且如果需要的话,让这个Touch事件所对应的对象来派发这个事件。

递归显示对象

在1.2的Starling中针对事件系统做了优化,对Stage类来说也是如此。阅读Stage的源码,您可以看到一个名为mEnterFrameEvent的成员变量。也就是说,Starling在每帧派发EnterFrame事件,会共用一个事件对象,这可以改善性能。然后通过递归,向所有的子级派发这个事件。

处理动画

Starling中的动画由Juggler类来处理。Juggler管理那些实现了IAnimatable接口的对象(比如Tweens和MovieClip)并执行它们。

Juggler.advanceTime方法,负责调用由这个Juggler管理的所有实现IAnimatable接口的对象自身的advanceTime方法,并传递时间差。另外这个方法还负责一些清理工作,清除掉已经为空或失效的对象。

渲染

对于Starling当前活动实例(即Starling.current)来说,执行完advanceTime()方法后,接着就会执行render()这个方法。可以这样理解:advanceTime()改变的是数据和属性,但是只有这个方法执行的话,我们是什么也看不到的。必须调用Stage3D的底层API,把数据映射为画面,我们才能看的到。这就是render()方法的作用。

和advanceTime()方法类似,render()方法也会在Starling体系的很多类中出现,很快您就会对这两个方法印象深刻。

Starling.render()方法的执行过程如下:

首先,更新上文提到过的那个添加到传统Flash显示列表里的Sprite对象

updateNativeOverlay();

下面的代码中涉及到mSupport。mSupport是RenderSupport类的实例。RenderSupport类在Starling体系中也非常重要(尽管我们在外部很少直接调用它)。 Starling的API设计是模仿传统显示列表的,而实际渲染需要的是Stage3D,三角形,纹理等,跟显示列表完全不是一个机制。 那么在Starling中这两套机制是如何结合起来的呢?就是这两个很重要的类:RenderSupport和QuadBatch。 RenderSupport的主要作用是处理矩阵(在2D屏幕渲染需要很多矩阵运算)和维护QuadBatch堆栈,每当状态变更,新建一个QuadBatch。 QuadBatch负责批量处理具备相同状态的四边形,这样可以尽量减少绘制调用次数,提升运行效率。

nextFrame()这个方法的作用就是重置mSupport的数据状态为初始状态

mSupport.nextFrame();

根据Stage尺寸,设置正面二维渲染的矩阵映射,在后面的运算中相当于一个常量矩阵

mSupport.setOrthographicProjection(mStage.stageWidth, mStage.stageHeight);

在进入下面的代码之前,我们先了解一下mSupport.drawCount这个属性。如果打开Starling.showStats属性,您会在Starling的左上角区域看到实时的帧频和绘制次数显示。那个绘制次数,实际上就是mSupport.drawCount的值(如果开启Starling.showStats,显示的值是mSupport.drawCount-2,以减去这个状态组件本身占用的绘制次数)。这个绘制次数,直接体现了应用的性能好坏,当绘制次数越少,性能越好,反之性能越差。如何尽量减少绘制次数,是我们在开发中重点考虑的优化方向,而这也是Starling中QuadBatch这个类的存在意义。

当进入下一帧时,随着数据被重置,这个绘制次数也会被清零。然后随着QuadBatch堆栈的长度增长,这个值也在递增。而且您会发现,这个值的递增和mSupport.finishQuadBatch()方法的调用是有关联的,而这个方法的调用又和状态的变更是有关联的。Starling按照画家算法,递归处理显示对象,每当发生一次状态变更(不同的纹理,不同的渲染模式等等),就会调用一次mSupport.finishQuadBatch()方法,而mSupport.drawCount的值也会相应的累加一次。

举个例子,假如我们的应用中,有两个显示对象(类型是Image,而且是不同的纹理)。那么在下面这一行代码执行之前,您如果观察mSupport.drawCount属性,会发现到这里,它的值还是为0. 然后让程序执行这一步代码,这行代码的作用就是调用Starling的Stage的render方法,而Stage是从DisplayObjectContainer继承而来的,它会递归处理所有的子级,调用子级的render方法。而在Image的render方法中,它会调用mSupport.batchQuad()方法,向当前批次中添加一个四边形(很好理解,图片就是一个四边形+纹理)。如果循环添加的过程中,出现了状态变更,则调用mSupport.finishQuadBatch()方法,新建一个QuadBatch继续处理。

mStage.render(mSupport, 1.0);

当上面这行代码执行完毕,您再观察一下mSupport.drawCount这个值。mStage已经递归处理了两个Image对象,那现在这个值,它应该是多少?如果您觉得是2,那就错了。实际上它的值是1。因为遍历到第一个Image的时候,因为当前批次中还没有内容,所以不会触发状态变更;当遍历到第二个Image的时候,因为是不同的纹理,所以会触发一次状态变更,将mSupport.drawCount的值累加。所以是1。

紧接着,会执行下面这一行代码,强制闭合当前的QuadBatch。因为对于最后一个批次来说,当所有的对象处理完毕,就意味着最后一个批次也完成了。

mSupport.finishQuadBatch();

这个时候,mSupport.drawCount的值就等于2了。这会最终反映到显示的界面上。最后两行代码也很好理解:

//如果开启了状态显示,更新绘制次数
if (mStatsDisplay)
    mStatsDisplay.drawCount = mSupport.drawCount;
//将批次中的数据渲染到屏幕
if (!mShareContext)
    mContext.present();
TIP: Starling中两个三角形组成一个四边形,而四边形的4个顶点的索引以及其位置是:
0 1
2 3

显示对象

显示对象大都集中在starling.display这个包下,所以我们现在开始分析这个包下的内容:

这个包的核心是抽象类DisplayObject。您不能直接使用这个类,而应该使用它的子类(或创建自定义的子类)。这个抽象类,模仿传统Flash的显示对象,定义了诸如坐标,长度,宽度,旋转角度,透明度等属性,为了使用方便,还增加了一些有特色的方法,比如removeFromParent()。

和传统Flash类似,这里也定义了一些常用的事件,这些事件会被DisplayObject派发。这里看名字大家基本就明白了十有八九了,不再细述。

    /** 当一个显示对象被添加到父级的时候派发。 */
    [Event(name="added", type="starling.events.Event")]
    /** 当一个显示对象被添加到stage(直接的或者间接的)的时候派发。 */
    [Event(name="addedToStage", type="starling.events.Event")]
    /** 当一个显示对象从父级删除的时候派发。 */
    [Event(name="removed", type="starling.events.Event")]
    /** 当一个显示对象从stage删除(直接的或者间接的)的时候派发,此对象不再会被渲染。 */ 
    [Event(name="removedFromStage", type="starling.events.Event")]
    /** 在每一帧派发给stage上的所有显示对象。 */ 
    [Event(name="enterFrame", type="starling.events.EnterFrameEvent")]
    /** 当显示对象被触碰时派发,冒泡事件。 */
    [Event(name="touch", type="starling.events.TouchEvent")]

注意它定义的render()方法,会被它的子类去具体实现。对于每一个显示对象类,render()方法都非常重要,这个方法决定了显示对象会被如何渲染。如果您自定义子类,也需要实现这个方法。

还需要注意getBounds()这个方法,这个方法也需要它的子类去实现。对于每一个显示对象类,getBounds()同样重要,因为很多运算(比如判断事件的目标对象),都需要知道显示对象的可见范围,基于一切皆四边形的设计,这个区域显然也是一个矩形。

然后看看DisplayObjectContainer,这个类定义了一个容器所应该具备的基本特征,包括对子级管理的各种方法。注意DisplayObjectContainer已经覆盖并实现了render()方法,在这个方法里面,容器用递归的方式,处理它的所有子级,调用子级的render方法。

注意其中的width和height,都是getter方式获取,而且是需要实时计算显示区域来获取。这个对性能会有影响,所以尽量不要频繁调用width和height属性,在循环中可以用变量先存储下来。

DisplayObjectContainer还定义了两个很有用的方法:broadcastEvent和broadcastEventWith,通过这两个方法,可以向所有的子级派发事件。

Quad类

然后来看Starling中最简单的一个显示对象类:Quad。Quad的直译就是四边形,这也体现了Starling的设计思路,即认为一切显示对象都是四边形,其它的显示对象都可以由四边形扩展而来。四边形的组成是什么呢?就是4个顶点,加每个顶点的位置,还有顶点的颜色。顶点的颜色决定了四边形最后的显示颜色。Starling中专门用一个类来存储和处理顶点数据,就是VertexData。在Quad这个类中,成员变量mVertexData负责存储这个四边形的顶点。当您创建一个Quad实例的时候,必须通过构造方法,设置四边形的宽高。顶点颜色您可以使用默认,默认4个顶点的颜色都是白色。

Quad实现了getBounds方法,当然里面的区域计算,就是根据顶点的位置来计算的(其实就是最后一个索引是3的顶点的位置)。

Quad对于Render方法的实现也很简单,只是调用support.batchQuad()方法,把自己添加到批次里面。

QuadBatch类

上面说到了Quad,如果我们把所有显示对象都视为Quad,那么Starling应该如何渲染他们呢?

最普通的思路应该是,用循环遍历所有的Quad,对于每一个Quad,取它的顶点数据,纹理,着色器等等,然后调用context3D.setProgram(), context3D.setVertexBufferAt(),context3D.setTextureAt()等方法进行初始化,然后调用context3D.drawTriangles()方法完成这个四边形的绘制。以此类推,完成所有的循环后,Starling会调用一次context3D. present()方法,绘制到屏幕,我们可以看到最终的渲染界面。

这个思路得到的渲染界面应该是没有问题的,但在性能方面却着实堪忧。假如我们有上千个相同状态的四边形,按照这个思路,上述的那些对显卡提交的方法,就要执行上千次。注意是每帧执行上千次。这会带来性能上的巨大浪费。所以Starling引入了“批处理”这个机制,这个机制的实现者就是QuadBatch类。

来看看QuadBatch是如何运作的:

QuadBatch定义了两个方法:addQuad(),和addImage()。注意addImage()内部还是调用了addQuad()。所以其实都是指向了addQuad()方法。它的作用就是,如何把当前QuadBatch看做一个数组的话,将一个新的四边形加入到数组,等候处理。在之前的介绍中我们提到了,在Starling内部,RenderSupport类负责管理和维护QuadBatch。当遍历显示列表的时候,RenderSupport就会将所有的四边形,划分到若干个批次里面(即添加到若干个QuadBatch里面),这样渲染的时候,每个QuadBatch内部的四边形会被集中处理,提交一次。这样您就可以想象,会节省多少提交次数。

这样说可能很枯燥,我来举个例子:假如我的显示列表是一个文本框(TextField)和1000个电影剪辑(MovieClip),电影剪辑用了同一个纹理,那么有多少个显示对象?1001个,没错。

然后看看Starling是如何渲染的。首先,假如文本框是第一个显示对象,那么进入帧循环的时候,Stage调用render()方法,首先处理的就是这个文本框,而且是由RenderSupport来处理它。RenderSupport会先创建一个QuadBatch实例,把文本框(视为四边形)加入到QuadBatch里面(通过RenderSupport.batchQuad()方法)。那么现在当前这个QuadBatch有了第一个四边形了,注意第一个加入的对象,决定了这个QuadBatch的状态。后面每次加入一个新的四边形,都要判断是不是符合这个状态,不符合的是不能加入的,必须新建另一个QuadBatch来处理。

目前我们已经有了1个QuadBatch,这个QuadBatch里面有了一个四边形(文本框的)。然后继续:

循环处理下一个对象是第一个MovieClip,通过RenderSupport.batchQuad()方法,首先进行状态判断,得到的结果是,假如加入这个对象,会触发状态变更。所以RenderSupport立刻执行当前QuadBatch(调用它的renderCustom()方法),然后新建另一个QuadBatch,添加第一个MovieClip对象。

这时我们已经有了两个QuadBatch,第一个QuadBatch已被执行然后重置,第二个QuadBatch有了一个MovieClip,并等待继续添加其它的显示对象。

之后以此类推,因为电影剪辑用了同一个纹理,所以不会触发状态变更,剩下所有的MovieClip都会被添加到第二个QuadBatch里面。循环结束,第二个QuadBatch就会被执行。这样直到最后,我们实际上只用到了两个QuadBatch,这就是为什么可以获得性能提升的原因。

QuadBatch是用什么方法实现渲染的呢?是renderCustom()方法。在这个方面里面,QuadBatch将包含的四边形,调用context3D的底层方法集中渲染。

QuadBatch还包含了一个静态方法:compile()。这个方式是为了和容器的flatten()方法配合使用的。如果一个容器调用了flatten()方法,就会将这个容器内部的显示对象数据,处理为一个元素类型是QuadBatch的矢量数组,然后在显示的时候直接调用,这样可以有很高的渲染效率。当然代价是,动画还有事件等等都会被忽略,看起来您的容器就像被冰冻了一样。所以比较适合“静态”的容器。

TIP: RenderSupport对于QuadBatch的创建和维护是基于画家算法来进行的,所以我们显示层级的结构和顺序会影响这个过程。
在上面的例子中,用到了两个QuadBatch,假如我们改变一下显示结构,先放500个电影剪辑,再放一个文本框,
然后再放剩下的500个电影剪辑。那么循环的过程中,会触发两次状态变更,最终就要多使用一个QuadBatch来渲染。这是性能优化方面需要考虑的元素。

Image类

Image类是继承Quad类的,当你创建一个Image,就需要把纹理设置给他,对于Image来说,纹理的尺寸决定了Image的尺寸(如果你修改了纹理,就要调用readjustSize()方法重置尺寸)。默认情况,Image对纹理的UV设定是(0,0),(1,0),(0,1),(1,1),也就是说显示这个纹理的全部。但是您可以通过调用setTexCoords()方法,改变四边形顶点对于的纹理的UV坐标(取值范围0-1),这样可以实现对纹理的一个矩形区域的裁切。比如我们只需要显示纹理右下方1/4的区域,可以这样设置:

img.setTexCoords(0,new Point(0.5,0.5));
img.setTexCoords(1,new Point(1,0.5));
img.setTexCoords(2,new Point(0.5,1));
img.setTexCoords(3,new Point(1,1));
 
//TIPS: Starling中两个三角形组成一个四边形,而四边形的4个顶点的索引以及其位置是:
0 1
2 3

这个原理也体现在其它方面,比如纹理图集。当您把所有的素材集中到一张大图上,Starling只需提交一次纹理,而不同的显示对象,可以通过UV坐标控制,取得纹理的某一个区域作为自己的纹理。所以Starling优化的一个重要原则就是:尽量使用纹理图集和子纹理。

MovieClip类

MovieClip类是从Image类继承而来。您可以认为,MovieClip就是具备多个纹理的Image。当Image根据帧频不断改变自己的纹理显示,就形成了动画。这些纹理是存储在一个数组中,根据数组的长度,和设定的FPS(Starling的MovieClip可以有自己的FPS,默认是12),MovieClip会计算总共有多少帧,播放总时长,每一帧的播放时长和起始时间。

您可以通过addFrame()和addFrameAt()两个方法来动态的添加帧(其实就是在数组中再添加一个纹理)。Starling还增加了一个易用的参数:sound,这样如果在某一帧需要一个声音,就可以指定这个参数。

和传统Flash的电影剪辑类似,Starling的MovieClip也提供了控制方式:play(), pause() , stop()。但注意这些方法,只是标记一下内部变量,MovieClip是由Juggler来驱动的,您必须把它添加到Juggler里面它才能播放。注意MovieClip也定义了advanceTime()方法,这个方法会被谁调用?没错,Juggler会调用它。在这个方法里面,MovieClip根据传入的时间差,和当前的播放帧,找到离预定时间最近的那个帧(因为允许自定义FPS,也就可能会跳帧播放),标记好当前帧mCurrentFrame,并根据当前帧设定好当前纹理之后,就等待render()方法被调用了。渲染的过程同Image一致。

注意,和Flash传统的电影剪辑不同,Starling的MovieClip不是容器,所以不能添加和管理子级显示对象。

Sprite类

同传统Flash的Sprite类似,在Starling体系中,Sprite也是一个轻量级的容器。用法也很类似,不再多说,您要注意的是Sprite定义了flatten()和unflatten()这两个方法,对于显示列表关系复杂,但是不需要一直更新的容器,这两个方法可以帮助您降低性能消耗。

TextField类

TextField是Starling中处理文本显示的类。虽然这个类也是显示对象,不过被放置在了starling.text包下。为了内部使用方便,Starling的TextField继承了DisplayObjectContainer,而不是DisplayObject。但建议您还是把它当做DisplayObject来用,不要用它来做容器哦。

因为Stage3D还不具备处理矢量文本的能力,所以Starling采取了“曲线救国”的策略,用一个传统Flash的TextField来渲染文本,但显示的不是传统Flash文本,而是由截取的图片获取而来的纹理,再用Image显示纹理。下面的代码展示了这个过程:

var bitmapData:BitmapData = new BitmapData(width, height, true, 0x0);
bitmapData.draw(sNativeTextField, new Matrix(1, 0, 0, 1, 0, int(yOffset)-2));
sNativeTextField.text = "";
var texture:Texture = Texture.fromBitmapData(bitmapData, false, false, scale);
//
if (mImage == null) 
 {
       mImage = new Image(texture);
       mImage.touchable = false;
       addChild(mImage);
}

因为使用Flash本身的文本,所以传统TextField所具备的特性,在这里都可以支持。使用位图字体的情况,参见Starling中文站的资料,这里不再探讨。

自定义显示对象

了解Starling显示对象的基本结构后,如果需要,您就可以创建自己的显示对象类。还记得getBounds()和render()这两个方法吧,没错,您基本需要做的就是实现这两个方法。

在Starling WIKI中有一个例子,我添加一些注释,展示在这里,您可以拷贝代码查看运行的效果:

package test
{
	import com.adobe.utils.AGALMiniAssembler;
 
	import flash.display3D.*;
	import flash.geom.*;
 
	import starling.core.RenderSupport;
	import starling.core.Starling;
	import starling.display.DisplayObject;
	import starling.errors.MissingContextError;
	import starling.events.Event;
	import starling.utils.VertexData;
 
	/** 这个一个自定义的显示对象类,效果是多边形,您可以指定边数 */
	public class Polygon extends DisplayObject
	{
		//注册到Starling的渲染这种图形所需的Program3D实例名称
		private static var PROGRAM_NAME:String = "polygon";
 
		/**半径*/
		private var mRadius:Number;
		/**边数*/
		private var mNumEdges:int;
		/**图形颜色*/
		private var mColor:uint;
 
		/**顶点数据*/
		private var mVertexData:VertexData;
		private var mVertexBuffer:VertexBuffer3D;
 
		/**索引数据*/
		private var mIndexData:Vector.<uint>;
		private var mIndexBuffer:IndexBuffer3D;
 
		//下面这两个最终传入AGAL由显卡运算,相当于常量
		/**用于坐标系转换的矩阵*/
		private static var sHelperMatrix:Matrix = new Matrix();
		/**存储透明度的矢量数组*/
		private static var sRenderAlpha:Vector.<Number> = new <Number>[1.0, 1.0, 1.0, 1.0];
 
		/**构造方法,传入半径,边数,和颜色 */
		public function Polygon(radius:Number, numEdges:int=6, color:uint=0xffffff)
		{
			//至少是三角形
			if (numEdges < 3) throw new ArgumentError("Invalid number of edges");
			reset(radius,numEdges,color);
			//防止设备丢失
			Starling.current.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
		}
		/**重置*/
		public function reset(radius:Number, numEdges:int=6, color:uint=0xffffff):void
		{
			mRadius = radius;
			mNumEdges = numEdges;
			mColor = color;
			//设置顶点和着色器
			setupVertices();
			createBuffers();
			registerPrograms();
		}
		/**销毁自己*/
		public override function dispose():void
		{
			Starling.current.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
			if (mVertexBuffer) mVertexBuffer.dispose();
			if (mIndexBuffer)  mIndexBuffer.dispose();
			super.dispose();
		}
		/**设备丢失后的重建*/
		private function onContextCreated(event:Event):void
		{
			// 设备丢失后,我们必须创建新的缓冲区和着色器
			createBuffers();
			registerPrograms();
		}
 
		/** 返回一个矩形,代表显示区域 */
		public override function getBounds(targetSpace:DisplayObject, resultRect:Rectangle=null):Rectangle
		{
			if (resultRect == null) resultRect = new Rectangle();
			var transformationMatrix:Matrix = targetSpace == this ? 
				null : getTransformationMatrix(targetSpace, sHelperMatrix);
			return mVertexData.getBounds(transformationMatrix, 0, -1, resultRect);
		}
 
		/**创建必须的顶点和索引数据,上传给GPU. */ 
		private function setupVertices():void
		{
			var i:int;
			// 创建顶点
			mVertexData = new VertexData(mNumEdges+1);
			mVertexData.setUniformColor(mColor);
			for (i=0; i<mNumEdges; ++i)
			{
				//通过笛卡尔坐标系,得到多边形所需的顶点
				var edge:Point = Point.polar(mRadius, i*2*Math.PI / mNumEdges);
				mVertexData.setPosition(i, edge.x, edge.y);
			}
			mVertexData.setPosition(mNumEdges, 0.0, 0.0); // 中心顶点
			//创建索引来定义三角形
			mIndexData = new <uint>[];
			for (i=0; i<mNumEdges; ++i)
				mIndexData.push(mNumEdges, i, (i+1)%mNumEdges);
		}
 
		/**创建新的顶点和索引缓冲区,并且上传我们的顶点和索引数据到这些缓冲区*/
		private function createBuffers():void
		{
			var context:Context3D = Starling.context;
			if (context == null) throw new MissingContextError();
			if (mVertexBuffer) mVertexBuffer.dispose();
			if (mIndexBuffer)  mIndexBuffer.dispose();
			//创建顶点缓冲区
			mVertexBuffer = context.createVertexBuffer(mVertexData.numVertices, VertexData.ELEMENTS_PER_VERTEX);
			mVertexBuffer.uploadFromVector(mVertexData.rawData, 0, mVertexData.numVertices);
			//创建索引缓冲区
			mIndexBuffer = context.createIndexBuffer(mIndexData.length);
			mIndexBuffer.uploadFromVector(mIndexData, 0, mIndexData.length);
		}
 
		/** 渲染过程 */
		public override function render(support:RenderSupport, alpha:Number):void
		{
			//自定义的显示对象,因为具备不同的状态,所以需要单独建立一个批次来处理
			support.finishQuadBatch();
			//最后一项决定图形的透明度
			sRenderAlpha[0] = sRenderAlpha[1] = sRenderAlpha[2] = 1.0;
			sRenderAlpha[3] = alpha * this.alpha;
			//判断上下文是否存在
			var context:Context3D = Starling.context;
			if (context == null) throw new MissingContextError();
			//应用当前混合模式
			support.applyBlendMode(false);
			//激活着色器,设置所需的缓冲区和常量
			context.setProgram(Starling.current.getProgram(PROGRAM_NAME));
			context.setVertexBufferAt(0, mVertexBuffer, VertexData.POSITION_OFFSET, Context3DVertexBufferFormat.FLOAT_2); 
			context.setVertexBufferAt(1, mVertexBuffer, VertexData.COLOR_OFFSET,    Context3DVertexBufferFormat.FLOAT_4);
			context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, support.mvpMatrix3D, true);            
			context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, sRenderAlpha, 1);
			//绘制三角形
			context.drawTriangles(mIndexBuffer, 0, mNumEdges);
			//重置缓冲区
			context.setVertexBufferAt(0, null);
			context.setVertexBufferAt(1, null);
		}
 
		/** 用AGAL创建顶点着色器和片段着色器 */
		private static function registerPrograms():void
		{
			var target:Starling = Starling.current;
			if (target.hasProgram(PROGRAM_NAME)) return; //着色器如果已经有了就不需要再注册了,防止浪费
			// va0 -> 顶点的位置
			// va1 -> 顶点颜色
			// vc0 -> mvpMatrix (4 vectors, vc0 - vc3)
			// vc4 -> 透明度
			var vertexProgramCode:String =
				"m44 op, va0, vc0 \n" + // 4x4的矩阵运算,结果存储到op中
				"mul v0, va1, vc4 \n";  // 将颜色和透明度相乘,传递给片段着色器
			var fragmentProgramCode:String =
				"mov oc, v0";           // v0移动到片段着色器中
			//顶点着色器
			var vertexProgramAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			vertexProgramAssembler.assemble(Context3DProgramType.VERTEX, vertexProgramCode);
			//片段着色器
			var fragmentProgramAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentProgramAssembler.assemble(Context3DProgramType.FRAGMENT, fragmentProgramCode);
			//注册这个着色器
			target.registerProgram(PROGRAM_NAME, vertexProgramAssembler.agalcode,
				fragmentProgramAssembler.agalcode);
		}
 
		/** 半径 */
		public function get radius():Number { return mRadius; }
		public function set radius(value:Number):void { mRadius = value; setupVertices(); }
 
		/** 边数 */
		public function get numEdges():int { return mNumEdges; }
		public function set numEdges(value:int):void { mNumEdges = value; setupVertices(); }
 
		/** 颜色 */
		public function get color():uint { return mColor; }
		public function set color(value:uint):void { mColor = value; setupVertices(); }
	}
}

自定义动画类

您可以像MovieClip那样,自己创建一个实现IAnimation接口的动画类,然后交给Juggler来驱动。我在上面那个多边形类的基础上稍加改进,加入动画功能:

package test
{
	import starling.animation.IAnimatable;
 
	/**简单动画类的例子,实现多边形动画切换*/
	public class PolygonMovie extends Polygon implements IAnimatable
	{
		public function PolygonMovie(radius:Number, numEdges:int=6, color:uint=0xffffff)
		{
			super(radius, numEdges, color);
		}
		/**时间驱动*/
		public function advanceTime(time:Number):void
		{
			reset(radius+1,numEdges+1,color);
			if(numEdges>100)
			{
				numEdges = 3;
				radius = 60;
			}
		}
	}
}

用法,别忘了添加到Juggler里面:

var polygon:PolygonMovie = new PolygonMovie(50, 6, Color.RED);
polygon.x = 300;
polygon.y = 300;
addChild(polygon);
Starling.juggler.add(polygon);

运行这段代码,您会看到边数越来越多,半径越来越大的图形,逐渐变成了圆,然后回归。


动画处理

coming soon...


作者: 郭少瑞

个人工具
名字空间

变换
操作
导航
Starling中文资料
Starling原创教程
论坛
友链
工具箱