基于Flash3D的粒子系统实现

因为项目需要,自己动手实现了一个粒子系统,同时为其编写了配套的粒子编辑器。由于自己对这一块并不是很熟悉,于是前前后后推翻重做了好多版,花费了大量的时间。所谓折腾使人进步,随着对粒子系统的编写、重构、优化,我自己对3D渲染的各方面也有了更深入的理解。随着时间的推进,粒子系统的设计也已经慢慢稳定下来,并经受了实际项目的考验。我想是时候记录一下自己在粒子系统这块探索的过程。

f

继续阅读

Flash骨骼动画渲染优化

这里的优化思路或许也不仅限于flash。优化说到底就是个发现问题 -解决问题的过程,首先使用性能分析工具找到瓶颈,然后再对症下药。

第一步:

在原有的设计中,我们的每根骨骼都是一个Object3D,会挂接在渲染树上,参与绘制流程。这样的方便之处在于我们可以很方便地把特效或者武器等物件挂接在某根骨骼上,只需要找到对应的骨骼调用addChild()就可以了。然而,这带来的比较大的性能开销:
1. 每根骨骼都要参与可见性判断、裁剪等过程。
2. 每根骨骼都要参与矩阵级联运算。

优化思路也很简单:
1. 把骨骼从渲染树上拆解出来,单独成为一套更新体系,省去了一系列可见性判断。
2. 原本每根骨骼的矩阵是相对于父骨骼的,现在会在解析模型的阶段将所有骨骼的矩阵预处理成相对于根骨骼的,避免了每次渲染时的矩阵级联运算。据说相对于根骨骼的矩阵在做插值时会遇到问题,但项目开发到现在,依旧没有出现肉眼能观察到的问题,所以这样是可行的。
3. 至于特效和武器的挂接问题,需要另外写机制实现,但总体效率会好过优化前。

继续阅读

优化Flash中的3D模型加载

最近在做一个公司的Flash3D页游项目,遇到了这个问题,前前后后断断续续也优化了一段时间,觉得还是有必要记录一下一些优化的心得。

Flash中加载资源一个最大的问题在于难以使用另外的线程加载资源。诚然Flash有Worker线程,但Worker存在以下几个问题:
1. 使用Worker要求客户的FlashPlayer播放器版本不能过低。
2. 不同Worker之间传递数据手段非常少,缺乏共享内存。使用ByteArray共享数据的话需要先把数据序列化成AMF格式,无论序列化还是解析都是一个耗时的操作。
3. Worker实际上是另一个swf文件,增加程序复杂度。
4. 需要使用FlashBuilder4.7才能开发worker,而4.7一堆bug。

既然只能在渲染线程中加载和解析模型,那么我们就只能力求加载速度足够快。同时,作为页游,模型文件的体积也要足够小。

继续阅读

仅将SWF文件用作资源打包

使用Flash开发网页游戏少不了与各种美术资源打交道。对于静态资源的那就是各种图片,对于会动的资源可以考虑直接做成swf。制作成swf的美术资源又可以分为两种:一种是直接将关键帧罗列在主时间轴上,那么当程序使用Loader类加载完swf文件后,就可以直接addChild在显示列表上;另一种是将美术资源封装成影片剪辑(MovieClip),并为AS3导出类,这样在程序中可以用类似new XXX的方式将美术资源创建出来。

弊端
然而,这种使用swf的方式有一个非常大的弊端,就是会重复创建位图,导致内存浪费。比如,你使用Flash CS5制作了一个swf文件,在主时间轴上放置了一个图片。那么,当你每次使用Loader加载这个swf文件的时候,实际上会创建出很多个位图实例,非常占用内存。Flash自身有一个BitmapData类是用来处理这种情况的,具体用法就是多个DisplayObject共用一个BitmapData,做到位图资源共享,减少内存开销。但是,你需要手动指定哪几个DisplayObject需要共用BitmapData。像上述这种情况,两次加载同一个swf文件,Flash并不会自动为我们共享使用其中的位图资源。

无标题 多次装载一个含有桌面壁纸图片的swf文件后,内存的使用情况

Flash的这种做法可以理解,毕竟Adobe认为绝大多数Flash中使用的美术资源都是矢量图而不是位图。但是,对于页游来说,基本上所有的美术资源都是位图序列帧动画。比如一个场景中有10个模型相同的玩家角色,如果存在10份序列帧动画的位图副本,那是巨大的浪费。
继续阅读

[转译]Flash10中的泛型类型名

kid:之前在解析一个flash官方的.swf文件的时候遇到了一个莫名其妙的问题,我在解析Multiname时遇到了一个未在官方文档中定义的标签0x1D。不过前几天毕业超忙,所以就耽搁下来了。今天重新Google,发现了解决方案,顺手转过来并翻译了一下,原文在此:http://laan.doswf.com/genericname-in-flash-10/


随着DoSWF的开发,我在解析abc数据的时候遇到了一个问题。有时候,会出现一个类型为0x1D(29)的Multiname。然而在adobe的官方文档avm2overview.pdf中,却没有对这种Multiname有所描述。因此,DoSWF将会在遇到这种类型的Multiname时抛出一个错误。

然而今天,当我在Google上搜索关键词:multiname 0x1d的时候,我找到了一个非常有用的页面:http://bugs.adobe.com/jira/browse/FP-1474:

现有的AVM2文档并没有包含flash player 10中的更新,特别是:

  • 类型定义为0x1D的multiname(我觉着这是和Vector类型有关的东东?)
  • 关于Vector类型的文档(如果与上述的无关):对它的引用(with type)以及它是如何被声明的(without type)
  • 用以访问bytearray的指令

实际上,有人已经遇到了相同的问题,并向adobe提了bug。这非常有趣。最后,这个问题并不是一个bug。不过adobe的工程师依旧对这个问题进行了回复:

0x1D可以被认为是一个泛型multiname,它有以下的声明:

[Kind] [TypeDefinition] [ParamCount] [Param1] [Param2] [ParamN]

其中:
[TypeDefinition] 是一个U30类型的对multiname表的索引
[ParamCount] 是一个U8(U30?)类型的参数个数
[ParamX] 是一个U30类型的对multiname表的索引

显然AS3当前并没有广泛地支持泛型(当前仅有Vector.<*>),因此ParamCount目前总是1(对于Vector.<*>来说)。

更多的信息可以参考这里:http://blog.richardszalay.com/2009/02/generics-vector-in-avm2.html

因此,我新增了一个名为GenericName的类用以解析泛型multiname。这很有效。不过另一个问题出现了:一个名为(0x53)的指令无法被解析。这依旧是在flash10中加入的,与Vector有关的新内容。我找到了另一个页面:http://stackoverflow.com/questions/553445/how-do-generics-vector-work-inside-the-avm:

Flash 10中新加入了一个opcode(0x53),我把它称作MakeGenericType。MakeGenericType有着如下的栈声明:

TypeDefinition, ParameterType1, ParameterTypeN -> GenericType

同时拥有一个U8(U30?)类型的参数,用以说明在栈中有多少个参数。通常你会看到MakeGenericType被这样使用:

GetLex [TypeDefinitionMultiname]
GetLex [ParameterTypeMultiname]
MakeGeneric [ParamCount]
Coerce [GenericNameMultiname]
Construct [ConstructorParamCount]

因此如果你像这样……

GetLex __AS3__.vec::Vector
GetLex int
MakeGeneric 1
Coerce __AS3__.vec::Vector.<int>
Construct 0

你将会拥有一个 Vector.<int>的实例。

使用FlexPMD构建AS3语法树

最近由于工作的原因,需要从一个AS3文件中抽取一个类的包名、类名、函数名、属性名等。虽然使用正则表达式可以勉强完成这项工作,不过最靠谱的方法还是使用一个AS3编译器前端帮我完成语法分析。

万能的Google告诉我目前有两个开源项目可以用,一个是metaas, 另一个是FlexPMD。呃呃,这两个项目居然都是用Java写的,好吧……

我首先尝试的是metaas,如你在官网所见,这个项目的最新版本是在……2008年发布的。好吧,虽然旧是旧了点,能用就行。

首先随手写了个简单的HelloWorld程序让它去解析,Good!貌似解析成功,很好。接下来尝试一个稍微复杂点的源码:

package kid.shape.circle
{
    import kid.shape.Shape;

    public class Circle extends Shape
    {
        override public function setpos(x:int, y:int):void
        {
            this.x = x - 10;
            this.y = y - 10;
        }

        override public function draw():void
        {
            super.draw();
            graphics.lineStyle(1);
            graphics.drawCircle(x, y, 20);
        }

    }
}

我满怀期望地运行了解析器,叮咚,Runtime Exception:Unexpected token in line 15:.

不愧是个08年的老项目啊,我估计那个时候super关键字还没有被加入AS3吧。既然是这样那就用FlexPMD吧,怎么说也是Adobe官方的项目,应该比较靠谱。

首先去这里下载FlexPMD,下载完后解压缩,可以看见里面内容是这样的:

QQ截图20130610205830

实际上FlexPMD的真正用途是代码质量检测工具,我们可以用它来检查我们AS3代码中不符合规范的地方。如果你想知道如何使用FlexPMD检测你的AS3代码(这才是FlexPMD的正确用法),你可以看看这篇文章。不过,由于FlexPMD的工作原理是首先为代码生成语法树,再应用规则,因此我们可以简单地将它用作一个编译器前端。

我们真正需要使用到的jar包只有以下4个:

  1. as3-parser-1.2.jar
  2. as3-parser-api-1.2.jar
  3. flex-pmd-files-1.2.jar
  4. as3-plugin-utils-1.2.jar

将这四个jar包加入到我们Java项目的构建路径后,我们就可以这样使用它来生成一颗语法树:

AS3Parser parser = new AS3Parser();
IParserNode root = parser.buildAst(fileName);

之后就可以通过根节点遍历整个语法树了。比如,我上面贴出来的那个文件(Circle.as)生成出来的语法树是这样子的:

compilation-unit
    package
        name kid.shape.circle
        content
            import kid.shape.Shape
            class
                name Circle
                mod-list
                    mod public
                extends Shape
                content
                    function
                        mod-list
                            mod override
                            mod public
                        name setpos
                        parameter-list
                            parameter
                                name-type-init
                                    name x
                                    type int
                            parameter
                                name-type-init
                                    name y
                                    type int
                        type void
                        block
                            dot
                                primary this
                                assign
                                    primary x
                                    op =
                                    add
                                        primary x
                                        op -
                                        primary 10
                            dot
                                primary this
                                assign
                                    primary y
                                    op =
                                    add
                                        primary y
                                        op -
                                        primary 10
                    function
                        mod-list
                            mod override
                            mod public
                        name draw
                        parameter-list null
                        type void
                        block
                            dot
                                primary super
                                call
                                    primary draw
                                    arguments null
                            dot
                                primary graphics
                                call
                                    primary lineStyle
                                    arguments
                                        primary 1
                            dot
                                primary graphics
                                call
                                    primary drawCircle
                                    arguments
                                        primary x
                                        primary y
                                        primary 20
    content null

至此,工作完成,撒花!