最近参加了一个卡牌游戏项目的前期准备工作,工作内容是做一个卡牌游戏的战斗场景demo。其中有不少动作特效动画,比如攻击的时候卡牌需要扭动、被攻击时需要斜后方抖动平移等等。cocos2d-x引擎实现这些动画不是问题,无非是Sprite执行MoveBy、RotateTo、EaseIn、EaseOut等动作极其组合,难点在于动作特效的编辑。作为一个程序猿,我是有自知之明的,动作特效编辑不是我擅长的,这种事情最好交给专业的美术去做。
问题在于,美术用什么工具去编辑这些动画?程序如何去读取?
首先想到的是CocoStudio,但最终放弃了,因为这不是CocoStudio擅长的领域,最直接的,CocoStudio无法实现颜色透明度的渐变。
最理想的工具非Flash莫属,说道做动画,首先想到Flash。但是Flash做出来的动画怎么应用到程序呢?总不能将动画分解,然后一段段用程序翻译吧?
偶然间遇到这篇文章 http://baike.baidu.com/link?url=uZwJfQlmQzF6R88DmJdK9gv2q-L6jMwiIgkIz9YdUNukYnkBt7mVuR64m2LDDiWmLbr_Iq3UYLB5TLG_fsCkp_
突然眼前一亮,救星啊,原来FlashCS5以后的fla文件其实是个ZIP包,所有的帧数据都保持在zip包里面的DOMDocument.xml文件里面。而且,保持Flash的时候,你可以选择*.xfl格式,这样其实就是一个文件夹,DOMDocument.xml就躺在文件夹里面等你。
好啦,基本思路就有了,想办法解析DOMDocument.xml文件,将其翻译为cocos2d-x能读懂的。
PS:实现代码我已经发到github了,地址https://github.com/ctbinzi/FlashCS6ForCocos2d-x
在解读DOMDocument.xml之前,我们先了解一下Flash存档的目录结构,如下:
左侧是Flash的lib库结资源目录构,右侧是该Flash项目的存档目录结构,可以发现,lib库下面的每一个资源都对应一个位于存档文件夹下的LIBRARY目录下面的文件,位图资源对应的是图片文件,目录对应的是同名文件夹,MovieClip对应的是xml文件,且该xml文件完整地描述了该MovieClip的详细信息。当然,在存档目录的根目录位置有一个DOMDocument.xml文件,该文件描述的是该Flash舞台及其相关属性。
好了,了解完Flash存档目录结构和Flash资源的关系后,我们可以开始逐一分析他们了。
首先,我们还是从DOMDocument.xml开始入手,我将该xml文档中一些暂时不太关心的内容去除,并在需要我们着重关心的部位加上注释,记录如下
<DOMDocument frameRate="60"> <!-- DOMDocument是该xml文档的根节点,其中我们比较关心的属性只有一个frameRate,该属性值记录的是该Flash的帧频率,FlashCS6的默认值是24--> <folders><!-- 该节点下罗列了我们在LIBRARY里面创建的文件夹,其实我们可以不必关心它 --> <DOMFolderItem name="folder_effect" itemID="537776ce-000001df" isExpanded="true"/> </folders> <media><!-- 该节点下罗列了该Flash所用到的图片及其它从外部到了的多媒体资源信息 --> <DOMBitmapItem name="8.png" itemID="5313e068-000001f8" sourceExternalFilepath="../Actions/LIBRARY/8.png" sourceLastImported="1392726125" externalFileCRC32="1072798625" externalFileSize="34982" originalCompressionType="lossless" quality="50" href="8.png" bitmapDataHRef="M 1 1393811560.dat" frameRight="4200" frameBottom="3840"/> <!-- 该节点描述了该多媒体资源的相关属性,其中我们比较关心的属性如下:--> <!--name 该多媒体资源的名字,即我们在Flash编辑器的库视图里面看到的名字,如果是在文件夹里面的话,其名字会带上文件夹路径--> <!--itemID 唯一ID,Flash里面的所有资源都会被分配一个类似这样的唯一ID--> <!--href 该资源对应存储目录下的文件相对路径,相对LIBRARY文件夹的路径--> <DOMBitmapItem name="effect1.png" itemID="531d709d-000001e6" sourceExternalFilepath="../Actions/LIBRARY/effect1.png" sourceLastImported="1392901873" externalFileCRC32="2070406904" externalFileSize="16942" originalCompressionType="lossless" quality="50" href="effect1.png" bitmapDataHRef="M 2 1394436802.dat" frameRight="3480" frameBottom="3420"/> <DOMBitmapItem name="folder_effect/effect2.png" itemID="531e6ce8-000001ee" sourceExternalFilepath="../Actions/LIBRARY/effect2.png" sourceLastImported="1362735664" externalFileCRC32="1477998738" externalFileSize="36159" originalCompressionType="lossless" quality="50" href="folder_effect/effect2.png" bitmapDataHRef="M 3 1394500443.dat" frameRight="8000" frameBottom="5200"/> </media> <symbols><!-- 该节点下罗列了该Flash所拥有的MovieClip信息,每一个MovieClip对应一个xml文档,存放于LIBRARY目录下面 --> <Include href="1.xml" loadImmediate="false" itemID="5332958a-000001da" lastModified="1395884060"/> <!-- 该节点描述了一个MovieClip的相关属性,其中我们比较关系的属性如下:--> <!--href 该资源对应存储目录下的文件相对路径,相对LIBRARY文件夹的路径--> <!--itemID 唯一ID,Flash里面的所有资源都会被分配一个类似这样的唯一ID--> <Include href="2.xml" loadImmediate="false" itemID="531e636b-000001db" lastModified="1395884063"/> <Include href="Card1.xml" loadImmediate="false" itemID="5313e070-000001fa" lastModified="1395884065"/> <Include href="effect1.xml" itemID="533295a5-000001de" lastModified="1395912879"/> <Include href="effect2.xml" itemID="533295a2-000001dd" lastModified="1395914886"/> <Include href="folder_effect/effect3.xml" itemID="5333f886-00000213" lastModified="1395914979"/> </symbols> ... ... </DOMDocument>
从上述内容我们知道每一个MovieClip在LIBRARY目录下面都有一个对应的xml文档来存储其详细信息,下面,我们还是按照DOMDocument.xml分析方法,分析一个MovieClip文档。
<DOMSymbolItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://ns.adobe.com/xfl/2008/" name="test" itemID="5377877f-000001fc" lastModified="1400342648" lastUniqueIdentifier="1"> <timeline> <DOMTimeline name="test"><!--时间轴,每个MovieClip有且只有一个,不必关心--> <layers> <DOMLayer name="layer1" color="#9933CC" autoNamed="false"> <!-- 层,对应Flash编辑器里面的的层,每个层可以添加多个关键帧,每个关键帧对应一个DOMFrame节点 --> <frames> <DOMFrame index="0" duration="9" tweenType="motion" motionTweenSnap="true" keyMode="22017" acceleration="-100" soundName="test.mp3"> <!-- 记录关键帧信息,关键帧即如上图所示,带有实心点的帧。我们比较关系的属性有如下:--> <!-- index 帧索序号,从0开始--> <!-- duration 该关键帧持续帧数,即从改帧开始到下一个关键帧之间间隔的帧数 --> <!-- tweenType 渐变动画类型,在我们这个项目中只会用到传统补间动画,对应值为motion--> <!-- acceleration 缓动效果,比较有意思的是,该值和我们在Flash编辑器里面设置的值符号刚好相反--> <!-- soundName 声音,其值对应DOMDocument.xml文档里面记录的名字--> <SoundEnvelope> <SoundEnvelopePoint level0="32768" level1="32768"/> <!--声音属性,level0和level1分别对应了左声道和右声道音量值[0~32768]--> </SoundEnvelope> <elements> <!--记录该帧放置内容,我们可以在一个关键帧放入影片剪辑、图片、声音等信息,这些信息都将一一记录如下--> <DOMSymbolInstance libraryItemName="1" centerPoint3DX="-159.95" centerPoint3DY="-52"> <!--每一个被放入该帧的MovieClip都将对应一个该节点,其中我们比较关系的属性是--> <!-- libraryItemName 该属性值记录了该MovieClip的名字,对应DOMDocument.xml文档里面记录的名字--> <matrix> <!--该节点记录了该MovieClip在该帧时的位置信息,通过选择矩阵的方式记录,包括了坐标位置、旋转角度、缩放等信息--> <Matrix a="0.30902099609375" b="-0.9510498046875" c="0.9510498046875" d="0.30902099609375" tx="-77.8" ty="-25.3"/> </matrix> <transformationPoint> <Point y="-86.4"/> </transformationPoint> <color> <!--该节点记录了该MovieClip在该帧时的颜色信息,主要包括argb是个颜色通道的值,Multiplier是百分比值[0~1],Offset是相对值[-255~255]]--> <Color alphaMultiplier="0.4296875" redMultiplier="0.83984375" blueMultiplier="0.87890625" greenMultiplier="0.87109375" alphaOffset="14" redOffset="8" blueOffset="29" greenOffset="15"/> </color> </DOMSymbolInstance> <DOMBitmapInstance libraryItemName="8.png"> <!--每一个被放入该帧的位图都将对应一个该节点,其中我们比较关系的属性是--> <!-- libraryItemName 该属性值记录了该位图的名字,对应DOMDocument.xml文档里面记录的名字--> <matrix> <Matrix tx="-299.95" ty="57.15"/> </matrix> </DOMBitmapInstance> </elements> </DOMFrame> </frames> </DOMLayer> </layers> </DOMTimeline> </timeline> </DOMSymbolItem>
从上述解析文档可以看出,需要我们着重关心的是<DOMFrame>节点属性,以及<SoundEnvelope>、<DOMSymbolInstance>、<DOMBitmapInstance>节点内容。此外,我们需要理解在Flash里面,动画师由关键帧组成的,即关键帧在时间上的延续,以及关键帧到下一个关键帧的渐变过程。
另外,Flash的舞台其实也是一个MovieClip,也就是说,如果我们直接在Flash舞台上放置了元件,或是在舞台的时间轴上添加了关键帧,那么在DOMDocument.xml文档里面的内容也将跟上述解析内容类似,一样对待即可。