├── .gitignore ├── .idea ├── .name ├── codeStyles │ └── codeStyleConfig.xml ├── flexCompiler.xml ├── misc.xml └── modules.xml ├── LICENSE.md ├── README.md ├── adobe-animate.iml ├── assets ├── background.jpg ├── bunny │ ├── Animation.json │ ├── spritemap.json │ └── spritemap.png ├── mole │ ├── Animation.json │ ├── spritemap.json │ └── spritemap.png ├── ninja-girl │ ├── Animation.json │ ├── spritemap.json │ └── spritemap.png └── simple-animation │ ├── Animation.json │ ├── spritemap.json │ └── spritemap.png ├── media ├── joker.fla ├── mole.fla ├── ninja-girl.fla └── simple-animation.fla ├── src ├── Demo.as ├── StartupMobile.as ├── StartupWeb.as ├── application.xml └── starling │ └── extensions │ └── animate │ ├── AnimAssetManager.as │ ├── Animation.as │ ├── AnimationAtlas.as │ ├── JsonTextureAtlas.as │ ├── LoopMode.as │ ├── MovieBehavior.as │ ├── Symbol.as │ └── SymbolType.as └── tools └── optimize_json ├── Gemfile ├── README.md └── optimize_json.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin-debug/ 3 | bin-release/ 4 | bin/ 5 | out/ 6 | 7 | # Project property files 8 | .settings/ 9 | .flexProperties 10 | .FlexUnitSettings 11 | 12 | # OS X folders 13 | .DS_Store 14 | 15 | # IntelliJ IDEA 16 | *.iws 17 | **/.idea/workspace.xml 18 | **/.idea/tasks.xml 19 | **/.idea/dictionaries 20 | 21 | # Ruby Gems 22 | Gemfile.lock -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | adobe-animate -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/flexCompiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Simplified BSD License 2 | ====================== 3 | 4 | Copyright Gamua GmbH. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are 7 | permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this list of 10 | conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 13 | of conditions and the following disclaimer in the documentation and/or other materials 14 | provided with the distribution. 15 | 16 | 3. Redistributions in binary form via an online application distribution system (like the 17 | Apple App Store) that inhibit execution of condition 2 must include a text equivalent 18 | to the following in the description: “Powered by the Starling Framework”. 19 | The content of the following disclaimer is still in effect. 20 | 21 | THIS SOFTWARE IS PROVIDED BY GAMUA "AS IS" AND ANY EXPRESS OR IMPLIED 22 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 23 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GAMUA OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 29 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | The views and conclusions contained in the software and documentation are those of the 32 | authors and should not be interpreted as representing official policies, either expressed 33 | or implied, of Gamua. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Starling Extension: Animate CC "Texture Atlas" Animations 2 | ========================================================= 3 | 4 | This extension allows to parse and play "Texture Atlas" animations created with *Adobe Animate CC.* 5 | 6 | Different to what the name suggests, the "Texture Atlas" export option of Adobe Animate actually does not create just a texture atlas from an animation (that would be the old "Sprite Sheet" export), but it's an alternative to libraries/tools like "Flump", "Spine", "Dragon Bones", or "GAF". 7 | 8 | More information: 9 | 10 | * Find usage instructions, as well as a gorgeous sample animation from Chris Georgenes (the guy who created the Starling logo bird) here: [Starling Wiki: Adobe Animate Extension](http://wiki.starling-framework.org/extensions/adobe-animate) 11 | * An overview about Adobe's "Texture Atlas" export procedure is available on [Adobe Content Corner](https://blogs.adobe.com/contentcorner/2017/07/03/create-a-texture-atlas-with-animate-cc-for-your-favorite-game-engines/) 12 | 13 | #### Porting to a different game engine? 14 | 15 | If you want to port this extension to a different game engine, feel free to share the included FLA and animation files. Just be sure to keep the respective author's credits intact. 16 | 17 | -------------------------------------------------------------------------------- /adobe-animate.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/assets/background.jpg -------------------------------------------------------------------------------- /assets/bunny/spritemap.json: -------------------------------------------------------------------------------- 1 | {"ATLAS": {"SPRITES":[ 2 | {"SPRITE" : {"name": "0000","x":50,"y":137,"w":24,"h":23,"rotated": false}}, 3 | {"SPRITE" : {"name": "0001","x":2,"y":2,"w":122,"h":112,"rotated": false}}, 4 | {"SPRITE" : {"name": "0002","x":2,"y":137,"w":23,"h":29,"rotated": false}}, 5 | {"SPRITE" : {"name": "0003","x":27,"y":137,"w":21,"h":30,"rotated": false}}, 6 | {"SPRITE" : {"name": "0004","x":126,"y":2,"w":72,"h":89,"rotated": false}}, 7 | {"SPRITE" : {"name": "0005","x":126,"y":93,"w":70,"h":72,"rotated": false}}, 8 | {"SPRITE" : {"name": "0006","x":2,"y":169,"w":8,"h":10,"rotated": false}}, 9 | {"SPRITE" : {"name": "0007","x":12,"y":169,"w":8,"h":10,"rotated": false}}, 10 | {"SPRITE" : {"name": "0008","x":50,"y":162,"w":24,"h":16,"rotated": false}}, 11 | {"SPRITE" : {"name": "0009","x":2,"y":116,"w":122,"h":19,"rotated": false}} 12 | ]}, 13 | "meta": { 14 | "app": "Adobe Animate", 15 | "version": "18.0.0.107", 16 | "image": "spritemap.png", 17 | "format": "RGBA8888", 18 | "size": {"w":256,"h":256}, 19 | "scale": "1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assets/bunny/spritemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/assets/bunny/spritemap.png -------------------------------------------------------------------------------- /assets/mole/spritemap.json: -------------------------------------------------------------------------------- 1 | {"ATLAS": {"SPRITES":[ 2 | {"SPRITE" : {"name": "0000","x":206,"y":2,"w":35,"h":52,"rotated": false}}, 3 | {"SPRITE" : {"name": "0001","x":133,"y":99,"w":53,"h":35,"rotated": false}}, 4 | {"SPRITE" : {"name": "0002","x":206,"y":56,"w":39,"h":42,"rotated": false}}, 5 | {"SPRITE" : {"name": "0003","x":133,"y":136,"w":41,"h":26,"rotated": false}}, 6 | {"SPRITE" : {"name": "0004","x":2,"y":152,"w":31,"h":23,"rotated": false}}, 7 | {"SPRITE" : {"name": "0005","x":78,"y":150,"w":32,"h":27,"rotated": false}}, 8 | {"SPRITE" : {"name": "0006","x":2,"y":96,"w":74,"h":54,"rotated": false}}, 9 | {"SPRITE" : {"name": "0007","x":2,"y":2,"w":130,"h":92,"rotated": false}}, 10 | {"SPRITE" : {"name": "0008","x":112,"y":164,"w":45,"h":15,"rotated": false}}, 11 | {"SPRITE" : {"name": "0009","x":188,"y":134,"w":46,"h":29,"rotated": false}}, 12 | {"SPRITE" : {"name": "0010","x":243,"y":16,"w":4,"h":7,"rotated": false}}, 13 | {"SPRITE" : {"name": "0011","x":243,"y":16,"w":4,"h":7,"rotated": false}}, 14 | {"SPRITE" : {"name": "0012","x":188,"y":100,"w":50,"h":32,"rotated": false}}, 15 | {"SPRITE" : {"name": "0013","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 16 | {"SPRITE" : {"name": "0014","x":134,"y":2,"w":70,"h":95,"rotated": false}}, 17 | {"SPRITE" : {"name": "0015","x":78,"y":96,"w":53,"h":52,"rotated": false}}, 18 | {"SPRITE" : {"name": "0016","x":243,"y":2,"w":11,"h":12,"rotated": false}}, 19 | {"SPRITE" : {"name": "0017","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 20 | {"SPRITE" : {"name": "0018","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 21 | {"SPRITE" : {"name": "0019","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 22 | {"SPRITE" : {"name": "0020","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 23 | {"SPRITE" : {"name": "0021","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 24 | {"SPRITE" : {"name": "0022","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 25 | {"SPRITE" : {"name": "0023","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 26 | {"SPRITE" : {"name": "0024","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 27 | {"SPRITE" : {"name": "0025","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 28 | {"SPRITE" : {"name": "0026","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 29 | {"SPRITE" : {"name": "0027","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 30 | {"SPRITE" : {"name": "0028","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 31 | {"SPRITE" : {"name": "0029","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 32 | {"SPRITE" : {"name": "0030","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 33 | {"SPRITE" : {"name": "0031","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 34 | {"SPRITE" : {"name": "0032","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 35 | {"SPRITE" : {"name": "0033","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 36 | {"SPRITE" : {"name": "0034","x":35,"y":152,"w":25,"h":14,"rotated": false}}, 37 | {"SPRITE" : {"name": "0035","x":35,"y":152,"w":25,"h":14,"rotated": false}} 38 | ]}, 39 | "meta": { 40 | "app": "Adobe Animate", 41 | "version": "18.0.0.107", 42 | "image": "spritemap.png", 43 | "format": "RGBA8888", 44 | "size": {"w":256,"h":256}, 45 | "scale": "1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /assets/mole/spritemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/assets/mole/spritemap.png -------------------------------------------------------------------------------- /assets/ninja-girl/spritemap.json: -------------------------------------------------------------------------------- 1 | {"ATLAS": {"SPRITES":[ 2 | {"SPRITE" : {"name": "0000","x":897,"y":325,"w":120,"h":108,"rotated": false}}, 3 | {"SPRITE" : {"name": "0001","x":939,"y":279,"w":39,"h":38,"rotated": false}}, 4 | {"SPRITE" : {"name": "0002","x":939,"y":153,"w":81,"h":68,"rotated": false}}, 5 | {"SPRITE" : {"name": "0003","x":998,"y":59,"w":16,"h":51,"rotated": false}}, 6 | {"SPRITE" : {"name": "0004","x":998,"y":2,"w":18,"h":55,"rotated": false}}, 7 | {"SPRITE" : {"name": "0005","x":176,"y":412,"w":94,"h":107,"rotated": false}}, 8 | {"SPRITE" : {"name": "0006","x":766,"y":497,"w":40,"h":68,"rotated": false}}, 9 | {"SPRITE" : {"name": "0007","x":272,"y":498,"w":43,"h":62,"rotated": false}}, 10 | {"SPRITE" : {"name": "0008","x":272,"y":412,"w":255,"h":32,"rotated": false}}, 11 | {"SPRITE" : {"name": "0009","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 12 | {"SPRITE" : {"name": "0010","x":272,"y":472,"w":155,"h":24,"rotated": false}}, 13 | {"SPRITE" : {"name": "0011","x":766,"y":497,"w":40,"h":68,"rotated": false}}, 14 | {"SPRITE" : {"name": "0012","x":666,"y":491,"w":52,"h":66,"rotated": false}}, 15 | {"SPRITE" : {"name": "0013","x":770,"y":434,"w":66,"h":61,"rotated": false}}, 16 | {"SPRITE" : {"name": "0014","x":455,"y":339,"w":74,"h":60,"rotated": false}}, 17 | {"SPRITE" : {"name": "0015","x":529,"y":434,"w":78,"h":56,"rotated": false}}, 18 | {"SPRITE" : {"name": "0016","x":939,"y":223,"w":83,"h":54,"rotated": false}}, 19 | {"SPRITE" : {"name": "0017","x":609,"y":434,"w":82,"h":53,"rotated": false}}, 20 | {"SPRITE" : {"name": "0018","x":693,"y":434,"w":75,"h":55,"rotated": false}}, 21 | {"SPRITE" : {"name": "0019","x":838,"y":435,"w":70,"h":57,"rotated": false}}, 22 | {"SPRITE" : {"name": "0020","x":910,"y":435,"w":64,"h":59,"rotated": false}}, 23 | {"SPRITE" : {"name": "0021","x":429,"y":446,"w":60,"h":61,"rotated": false}}, 24 | {"SPRITE" : {"name": "0022","x":609,"y":489,"w":55,"h":63,"rotated": false}}, 25 | {"SPRITE" : {"name": "0023","x":491,"y":492,"w":52,"h":64,"rotated": false}}, 26 | {"SPRITE" : {"name": "0024","x":545,"y":492,"w":49,"h":66,"rotated": false}}, 27 | {"SPRITE" : {"name": "0025","x":976,"y":435,"w":46,"h":67,"rotated": false}}, 28 | {"SPRITE" : {"name": "0026","x":720,"y":491,"w":44,"h":68,"rotated": false}}, 29 | {"SPRITE" : {"name": "0027","x":838,"y":494,"w":42,"h":68,"rotated": false}}, 30 | {"SPRITE" : {"name": "0028","x":882,"y":496,"w":41,"h":69,"rotated": false}}, 31 | {"SPRITE" : {"name": "0029","x":925,"y":496,"w":40,"h":69,"rotated": false}}, 32 | {"SPRITE" : {"name": "0030","x":766,"y":497,"w":40,"h":68,"rotated": false}}, 33 | {"SPRITE" : {"name": "0031","x":546,"y":153,"w":391,"h":170,"rotated": false}}, 34 | {"SPRITE" : {"name": "0032","x":2,"y":339,"w":172,"h":161,"rotated": false}}, 35 | {"SPRITE" : {"name": "0033","x":2,"y":2,"w":542,"h":335,"rotated": false}}, 36 | {"SPRITE" : {"name": "0034","x":176,"y":339,"w":277,"h":71,"rotated": false}}, 37 | {"SPRITE" : {"name": "0035","x":546,"y":325,"w":349,"h":107,"rotated": false}}, 38 | {"SPRITE" : {"name": "0036","x":546,"y":2,"w":450,"h":149,"rotated": false}}, 39 | {"SPRITE" : {"name": "0037","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 40 | {"SPRITE" : {"name": "0038","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 41 | {"SPRITE" : {"name": "0039","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 42 | {"SPRITE" : {"name": "0040","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 43 | {"SPRITE" : {"name": "0041","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 44 | {"SPRITE" : {"name": "0042","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 45 | {"SPRITE" : {"name": "0043","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 46 | {"SPRITE" : {"name": "0044","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 47 | {"SPRITE" : {"name": "0045","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 48 | {"SPRITE" : {"name": "0046","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 49 | {"SPRITE" : {"name": "0047","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 50 | {"SPRITE" : {"name": "0048","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 51 | {"SPRITE" : {"name": "0049","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 52 | {"SPRITE" : {"name": "0050","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 53 | {"SPRITE" : {"name": "0051","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 54 | {"SPRITE" : {"name": "0052","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 55 | {"SPRITE" : {"name": "0053","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 56 | {"SPRITE" : {"name": "0054","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 57 | {"SPRITE" : {"name": "0055","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 58 | {"SPRITE" : {"name": "0056","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 59 | {"SPRITE" : {"name": "0057","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 60 | {"SPRITE" : {"name": "0058","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 61 | {"SPRITE" : {"name": "0059","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 62 | {"SPRITE" : {"name": "0060","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 63 | {"SPRITE" : {"name": "0061","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 64 | {"SPRITE" : {"name": "0062","x":272,"y":446,"w":155,"h":24,"rotated": false}}, 65 | {"SPRITE" : {"name": "0063","x":272,"y":446,"w":155,"h":24,"rotated": false}} 66 | ]}, 67 | "meta": { 68 | "app": "Adobe Animate", 69 | "version": "18.0.0.107", 70 | "image": "spritemap.png", 71 | "format": "RGBA8888", 72 | "size": {"w":1024,"h":1024}, 73 | "scale": "1" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /assets/ninja-girl/spritemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/assets/ninja-girl/spritemap.png -------------------------------------------------------------------------------- /assets/simple-animation/spritemap.json: -------------------------------------------------------------------------------- 1 | {"ATLAS": {"SPRITES":[ 2 | {"SPRITE" : {"name": "0000","x":182,"y":2,"w":197,"h":66,"rotated": false}}, 3 | {"SPRITE" : {"name": "0001","x":269,"y":70,"w":69,"h":75,"rotated": false}}, 4 | {"SPRITE" : {"name": "0005","x":180,"y":182,"w":152,"h":132,"rotated": false}}, 5 | {"SPRITE" : {"name": "0006","x":2,"y":2,"w":178,"h":178,"rotated": false}}, 6 | {"SPRITE" : {"name": "0007","x":2,"y":182,"w":176,"h":173,"rotated": false}}, 7 | {"SPRITE" : {"name": "0008","x":182,"y":70,"w":85,"h":85,"rotated": false}} 8 | ]}, 9 | "meta": { 10 | "app": "Adobe Animate", 11 | "version": "18.0.0.107", 12 | "image": "spritemap.png", 13 | "format": "RGBA8888", 14 | "size": {"w":512,"h":512}, 15 | "scale": "1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /assets/simple-animation/spritemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/assets/simple-animation/spritemap.png -------------------------------------------------------------------------------- /media/joker.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/media/joker.fla -------------------------------------------------------------------------------- /media/mole.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/media/mole.fla -------------------------------------------------------------------------------- /media/ninja-girl.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/media/ninja-girl.fla -------------------------------------------------------------------------------- /media/simple-animation.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gamua/Starling-Extension-Adobe-Animate/83174a6e63d198801a465904dc00d4487bb97506/media/simple-animation.fla -------------------------------------------------------------------------------- /src/Demo.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.ui.Keyboard; 4 | 5 | import starling.core.Starling; 6 | import starling.display.Image; 7 | import starling.display.Sprite; 8 | import starling.events.Event; 9 | import starling.events.KeyboardEvent; 10 | import starling.extensions.animate.Animation; 11 | import starling.extensions.animate.AnimAssetManager; 12 | 13 | public class Demo extends Sprite 14 | { 15 | private var _ninja:Animation; 16 | private var _bunny:Animation; 17 | private var _walking:Boolean; 18 | 19 | public function Demo() 20 | { } 21 | 22 | public function start(assets:AnimAssetManager):void 23 | { 24 | stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); 25 | stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); 26 | 27 | var background:Image = new Image(assets.getTexture("background")); 28 | background.alignPivot(); 29 | background.x = stage.stageWidth / 2; 30 | background.y = stage.stageHeight / 2; 31 | addChild(background); 32 | 33 | _ninja = assets.createAnimation("ninja-girl"); 34 | _ninja.x = background.x; 35 | _ninja.y = background.y + background.height * 0.2; 36 | _ninja.frameRate = 24; 37 | _ninja.scale = 0.75; 38 | addChild(_ninja); 39 | Starling.juggler.add(_ninja); 40 | 41 | _ninja.addFrameAction(_ninja.getNextLabel("idle"), gotoIdleOrWalk); 42 | _ninja.addFrameAction(_ninja.getNextLabel("crouch"), gotoIdleOrWalk); 43 | _ninja.addFrameAction(_ninja.getNextLabel("attack"), gotoIdleOrWalk); 44 | _ninja.addFrameAction(_ninja.getNextLabel("walk"), gotoIdleOrWalk); 45 | 46 | _bunny = assets.createAnimation("bunny"); 47 | _bunny.addEventListener(Event.COMPLETE, switchBunnyDirection); 48 | _bunny.x = background.x; 49 | addChild(_bunny); 50 | Starling.juggler.add(_bunny); 51 | 52 | switchBunnyDirection(); // init bunny position 53 | } 54 | 55 | private function gotoIdleOrWalk():void 56 | { 57 | var targetLabel:String = _walking ? "walk" : "idle"; 58 | 59 | if (_ninja.currentLabel != targetLabel) 60 | _ninja.gotoFrame(targetLabel); 61 | } 62 | 63 | private function onKeyDown(e:Event, keyCode:uint):void 64 | { 65 | var currentLabel:String = _ninja.currentLabel; 66 | 67 | if (keyCode == Keyboard.RIGHT) 68 | { 69 | _walking = true; 70 | _ninja.scaleX = Math.abs(_ninja.scaleX); 71 | gotoIdleOrWalk(); 72 | } 73 | else if (keyCode == Keyboard.LEFT) 74 | { 75 | _walking = true; 76 | _ninja.scaleX = -Math.abs(_ninja.scaleX); 77 | gotoIdleOrWalk(); 78 | } 79 | else if (keyCode == Keyboard.DOWN && currentLabel != "crouch") 80 | _ninja.gotoFrame("crouch"); 81 | else if (keyCode == Keyboard.UP && currentLabel != "attack") 82 | _ninja.gotoFrame("attack"); 83 | else if (keyCode == Keyboard.X) 84 | Starling.context.dispose(); 85 | else if (keyCode == Keyboard.P) 86 | _ninja.isPlaying ? _ninja.pause() : _ninja.play(); 87 | else if (keyCode == Keyboard.S) 88 | _ninja.stop(); 89 | } 90 | 91 | private function onKeyUp(e:Event, keyCode:uint):void 92 | { 93 | if (keyCode == Keyboard.RIGHT || keyCode == Keyboard.LEFT) 94 | { 95 | _walking = false; 96 | gotoIdleOrWalk(); 97 | } 98 | } 99 | 100 | private function switchBunnyDirection():void 101 | { 102 | var centerY:Number = stage.stageHeight / 2; 103 | 104 | if (_bunny.scaleX > 0) 105 | { 106 | _bunny.y = centerY + 22; 107 | _bunny.scaleY = 0.35; 108 | _bunny.scaleX = -0.35; 109 | addChildAt(_bunny, 1); // behind ninja 110 | } 111 | else 112 | { 113 | _bunny.y = centerY + 100; 114 | _bunny.scale = 0.5; 115 | addChild(_bunny); // in front of ninja 116 | } 117 | } 118 | 119 | // this is a simple (dead ugly) test animation used to experiment with features 120 | public function startAlt(assets:AnimAssetManager):void 121 | { 122 | _ninja = assets.createAnimation("simple-animation"); 123 | _ninja.x = 300; 124 | _ninja.y = 600; 125 | _ninja.frameRate = 24; 126 | addChild(_ninja); 127 | 128 | Starling.juggler.add(_ninja); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/StartupMobile.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.Sprite; 4 | import flash.filesystem.File; 5 | import flash.geom.Rectangle; 6 | 7 | import starling.core.Starling; 8 | import starling.events.Event; 9 | import starling.extensions.animate.AnimAssetManager; 10 | 11 | [SWF(width="320", height="480", frameRate="60", backgroundColor="#ffffff")] 12 | public class StartupMobile extends Sprite 13 | { 14 | private var _starling:Starling; 15 | 16 | public function StartupMobile() 17 | { 18 | var viewPort:Rectangle = new Rectangle(0, 0, 19 | stage.fullScreenWidth, stage.fullScreenHeight); 20 | 21 | _starling = new Starling(Demo, stage, viewPort); 22 | _starling.skipUnchangedFrames = true; 23 | _starling.addEventListener(Event.ROOT_CREATED, loadAssets); 24 | _starling.start(); 25 | } 26 | 27 | private function loadAssets():void 28 | { 29 | var demo:Demo = _starling.root as Demo; 30 | var appDir:File = File.applicationDirectory; 31 | var assets:AnimAssetManager = new AnimAssetManager(); 32 | assets.enqueue(appDir.resolvePath("assets/ninja-girl/")); 33 | assets.enqueue(appDir.resolvePath("assets/bunny/")); 34 | assets.enqueue(appDir.resolvePath("assets/background.jpg")); 35 | assets.loadQueue(demo.start); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/StartupWeb.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.Sprite; 4 | 5 | import starling.core.Starling; 6 | import starling.events.Event; 7 | import starling.extensions.animate.AnimAssetManager; 8 | 9 | [SWF(width="500", height="500", frameRate="60", backgroundColor="#eeeeee")] 10 | public class StartupWeb extends Sprite 11 | { 12 | private var _starling:Starling; 13 | 14 | public function StartupWeb() 15 | { 16 | _starling = new Starling(Demo, stage); 17 | _starling.skipUnchangedFrames = true; 18 | _starling.addEventListener(Event.ROOT_CREATED, loadAssets); 19 | _starling.start(); 20 | } 21 | 22 | private function loadAssets():void 23 | { 24 | var demo:Demo = _starling.root as Demo; 25 | var assets:AnimAssetManager = new AnimAssetManager(); 26 | assets.enqueue(EmbeddedAssets); 27 | assets.loadQueue(demo.start); 28 | } 29 | } 30 | } 31 | 32 | class EmbeddedAssets 33 | { 34 | // It's important to follow these naming conventions when embedding "Animate CC" animations. 35 | // 36 | // file name: [name]/Animation.json -> member name: [name]_animation 37 | // file name: [name]/spritemap.json -> member name: [name]_spritemap 38 | // file name: [name]/spritemap.png -> member name: [name] 39 | 40 | [Embed(source="../assets/ninja-girl/Animation.json", mimeType="application/octet-stream")] 41 | public static const ninja_girl_animation:Class; 42 | 43 | [Embed(source="../assets/ninja-girl/spritemap.json", mimeType="application/octet-stream")] 44 | public static const ninja_girl_spritemap:Class; 45 | 46 | [Embed(source="../assets/ninja-girl/spritemap.png")] 47 | public static const ninja_girl:Class; 48 | 49 | [Embed(source="../assets/bunny/Animation.json", mimeType="application/octet-stream")] 50 | public static const bunny_animation:Class; 51 | 52 | [Embed(source="../assets/bunny/spritemap.json", mimeType="application/octet-stream")] 53 | public static const bunny_spritemap:Class; 54 | 55 | [Embed(source="../assets/bunny/spritemap.png")] 56 | public static const bunny:Class; 57 | 58 | [Embed(source="../assets/background.jpg")] 59 | public static const background:Class; 60 | } 61 | -------------------------------------------------------------------------------- /src/application.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.gamua.animate 5 | animate-demo 6 | animate-demo 7 | 0.0.1 8 | 9 | 10 | SWF file name is set automatically at compile time 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | true 23 | 24 | 25 | direct 26 | 27 | 28 | true 29 | 30 | 31 | high 32 | 33 | 34 | 35 | 67 | 68 | 69 | 70 | 71 | 72 | UIDeviceFamily 74 | 75 | 76 | 1 77 | 78 | 2 79 | 80 | 81 | 82 | 83 | 84 | ]]> 85 | 86 | 87 | 88 | high 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 98 | 99 | 100 | ]]> 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/starling/extensions/animate/AnimAssetManager.as: -------------------------------------------------------------------------------- 1 | package starling.extensions.animate 2 | { 3 | import starling.assets.AssetManager; 4 | 5 | /** An AssetManager subclass that adds support for the "AnimationAtlas" asset type. */ 6 | public class AnimAssetManager extends AssetManager 7 | { 8 | // helper objects 9 | private static var sNames:Vector. = new []; 10 | 11 | public function AnimAssetManager() 12 | { 13 | registerFactory(new AnimationAtlasFactory(), 10); 14 | } 15 | 16 | override public function addAsset(name:String, asset:Object, type:String = null):void 17 | { 18 | if (type == null && asset is AnimationAtlas) 19 | type = AnimationAtlas.ASSET_TYPE; 20 | 21 | super.addAsset(name, asset, type); 22 | } 23 | 24 | /** Returns an animation atlas with a certain name, or null if it's not found. */ 25 | public function getAnimationAtlas(name:String):AnimationAtlas 26 | { 27 | return getAsset(AnimationAtlas.ASSET_TYPE, name) as AnimationAtlas; 28 | } 29 | 30 | /** Returns all animation atlas names that start with a certain string, sorted alphabetically. 31 | * If you pass an out-vector, the names will be added to that vector. */ 32 | public function getAnimationAtlasNames(prefix:String="", out:Vector.=null):Vector. 33 | { 34 | return getAssetNames(AnimationAtlas.ASSET_TYPE, prefix, true, out); 35 | } 36 | 37 | public function createAnimation(name:String):Animation 38 | { 39 | var atlasNames:Vector. = getAnimationAtlasNames("", sNames); 40 | var animation:Animation = null; 41 | 42 | for each (var atlasName:String in atlasNames) 43 | { 44 | var atlas:AnimationAtlas = getAnimationAtlas(atlasName); 45 | if (atlas.hasAnimation(name)) 46 | { 47 | animation = atlas.createAnimation(name); 48 | break; 49 | } 50 | } 51 | 52 | if (animation == null && atlasNames.indexOf(name) != -1) 53 | animation = getAnimationAtlas(name).createAnimation(); 54 | 55 | sNames.length = 0; 56 | return animation; 57 | } 58 | 59 | override protected function getNameFromUrl(url:String):String 60 | { 61 | var defaultName:String = super.getNameFromUrl(url); 62 | var defaultExt:String = super.getExtensionFromUrl(url); 63 | 64 | if (defaultName.match(/spritemap\d?/) && (defaultExt == "png" || defaultExt == "atf")) 65 | return AnimationAtlasFactory.getName(url, defaultName, false); 66 | else 67 | return defaultName; 68 | } 69 | } 70 | } 71 | 72 | import starling.assets.AssetFactoryHelper; 73 | import starling.assets.AssetManager; 74 | import starling.assets.AssetReference; 75 | import starling.assets.JsonFactory; 76 | import starling.extensions.animate.AnimationAtlas; 77 | import starling.extensions.animate.JsonTextureAtlas; 78 | import starling.textures.Texture; 79 | import starling.textures.TextureAtlas; 80 | 81 | class AnimationAtlasFactory extends JsonFactory 82 | { 83 | public static const ANIMATION_SUFFIX:String = "_animation"; 84 | public static const SPRITEMAP_SUFFIX:String = "_spritemap"; 85 | 86 | override public function create(asset:AssetReference, helper:AssetFactoryHelper, 87 | onComplete:Function, onError:Function):void 88 | { 89 | super.create(asset, helper, onObjectComplete, onError); 90 | 91 | function onObjectComplete(name:String, json:Object):void 92 | { 93 | var baseName:String = getName(asset.url, name, false); 94 | var fullName:String = getName(asset.url, name, true); 95 | 96 | if (json.ATLAS && json.meta) 97 | { 98 | helper.addPostProcessor(function(assets:AssetManager):void 99 | { 100 | var texture:Texture = assets.getTexture(baseName); 101 | if (texture == null) onError("Missing texture " + baseName); 102 | else assets.addAsset(baseName, new JsonTextureAtlas(texture, json)); 103 | }, 100); 104 | } 105 | else if ((json.ANIMATION && json.SYMBOL_DICTIONARY) || (json.AN && json.SD)) 106 | { 107 | helper.addPostProcessor(function(assets:AssetManager):void 108 | { 109 | var atlas:TextureAtlas = assets.getTextureAtlas(baseName); 110 | if (atlas == null) onError("Missing texture atlas " + baseName); 111 | else assets.addAsset(baseName, 112 | new AnimationAtlas(json, atlas), AnimationAtlas.ASSET_TYPE); 113 | }); 114 | } 115 | 116 | onComplete(fullName, json); 117 | } 118 | } 119 | 120 | internal static function getName(url:String, stdName:String, addSuffix:Boolean):String 121 | { 122 | var separator:String = "/"; 123 | 124 | // embedded classes are stripped of the suffix here 125 | if (url == null) 126 | { 127 | if (addSuffix) return stdName; // should already include suffix 128 | else 129 | { 130 | stdName = stdName.replace(AnimationAtlasFactory.ANIMATION_SUFFIX, ""); 131 | stdName = stdName.replace(AnimationAtlasFactory.SPRITEMAP_SUFFIX, ""); 132 | } 133 | } 134 | 135 | if ((stdName == "Animation" || stdName.match(/spritemap\d*/)) && url.indexOf(separator) != -1) 136 | { 137 | var elements:Array = url.split(separator); 138 | var folderName:String = elements[elements.length - 2]; 139 | var suffix:String = stdName == "Animation" ? 140 | AnimationAtlasFactory.ANIMATION_SUFFIX : AnimationAtlasFactory.SPRITEMAP_SUFFIX; 141 | 142 | if (addSuffix) return folderName + suffix; 143 | else return folderName; 144 | } 145 | 146 | return stdName; 147 | } 148 | } 149 | 150 | -------------------------------------------------------------------------------- /src/starling/extensions/animate/Animation.as: -------------------------------------------------------------------------------- 1 | package starling.extensions.animate 2 | { 3 | import starling.animation.IAnimatable; 4 | import starling.display.DisplayObjectContainer; 5 | import starling.events.Event; 6 | 7 | /** An Animation is similar to Flash's "MovieClip" class, as it displays the content of an 8 | * Animate CC timeline. Like all Starling animatables, it needs to be added to a Juggler 9 | * in order to start playing. */ 10 | public class Animation extends DisplayObjectContainer implements IAnimatable 11 | { 12 | private var _symbol:Symbol; 13 | private var _behavior:MovieBehavior; 14 | private var _cumulatedTime:Number = 0.0; 15 | 16 | public function Animation(symbolName:String, atlas:AnimationAtlas) 17 | { 18 | _symbol = new Symbol(atlas.getSymbolData(symbolName), atlas); 19 | _symbol.update(); 20 | addChild(_symbol); 21 | 22 | _behavior = new MovieBehavior(this, onFrameChanged, atlas.frameRate); 23 | _behavior.numFrames = _symbol.numFrames; 24 | _behavior.addEventListener(Event.COMPLETE, onComplete); 25 | play(); 26 | } 27 | 28 | private function onComplete():void 29 | { 30 | dispatchEventWith(Event.COMPLETE); 31 | } 32 | 33 | private function onFrameChanged(frameIndex:int):void 34 | { 35 | _symbol.currentFrame = frameIndex; 36 | } 37 | 38 | /** Starts moving the playhead. */ 39 | public function play():void 40 | { 41 | _behavior.play(); 42 | } 43 | 44 | /** Pauses playback, while keeping the playhead at its current position. */ 45 | public function pause():void 46 | { 47 | _behavior.pause(); 48 | } 49 | 50 | /** Stops playback and resets "currentFrame" to zero. */ 51 | public function stop():void 52 | { 53 | _behavior.stop(); 54 | } 55 | 56 | /** Immediately moves the playhead to the given index or label. */ 57 | public function gotoFrame(indexOrLabel:*):void 58 | { 59 | currentFrame = indexOrLabel is String ? 60 | _symbol.getFrame(indexOrLabel as String) : int(indexOrLabel); 61 | } 62 | 63 | /** The given function is executed when the playhead leaves the frame with the given 64 | * index or label. "action" can be declared in one of the following ways: 65 | * 66 | * 67 | * function(target:DisplayObject, frameID:int):void; 68 | * function(target:DisplayObject):void; 69 | * function():void; 70 | * 71 | */ 72 | public function addFrameAction(indexOrLabel:*, action:Function):void 73 | { 74 | var frameIndex:int = indexOrLabel is String ? 75 | _symbol.getFrame(indexOrLabel as String) : int(indexOrLabel); 76 | 77 | _behavior.addFrameAction(frameIndex, action); 78 | } 79 | 80 | /** Removes a specific frame action at the given index or label. */ 81 | public function removeFrameAction(indexOrLabel:*, action:Function):void 82 | { 83 | var frameIndex:int = indexOrLabel is String ? 84 | _symbol.getFrame(indexOrLabel as String) : int(indexOrLabel); 85 | 86 | _behavior.removeFrameAction(frameIndex, action); 87 | } 88 | 89 | /** Removes all frame actions at the given index or label. */ 90 | public function removeFrameActions(indexOrLabel:*):void 91 | { 92 | var frameIndex:int = indexOrLabel is String ? 93 | _symbol.getFrame(indexOrLabel as String) : int(indexOrLabel); 94 | 95 | _behavior.removeFrameActions(frameIndex); 96 | } 97 | 98 | /** Advances the playhead by the given time in seconds. */ 99 | public function advanceTime(time:Number):void 100 | { 101 | var frameRate:Number = _behavior.frameRate; 102 | var prevTime:Number = _cumulatedTime; 103 | 104 | _behavior.advanceTime(time); 105 | _cumulatedTime += time; 106 | 107 | if (int(prevTime * frameRate) != int(_cumulatedTime * frameRate)) 108 | _symbol.nextFrame_MovieClips(); 109 | } 110 | 111 | /** Returns the next label after the given one. Useful for looping: e.g. to loop between 112 | * the label "walk" and the next label, add a frame action to "getNextLabel('walk')" 113 | * that jumps back to the "walk" label. 114 | */ 115 | public function getNextLabel(afterLabel:String=null):String 116 | { 117 | return _symbol.getNextLabel(afterLabel); 118 | } 119 | 120 | /** Returns the index of the frame with the given label, 121 | * or '-1' if that label is not found. */ 122 | public function getFrame(label:String):int 123 | { 124 | return _symbol.getFrame(label); 125 | } 126 | 127 | /** The current label in which the playhead is located in the timeline of the Animation 128 | * instance. If the current frame has no label, currentLabel is set to the name of the 129 | * previous frame that includes a label. If the current frame and previous frames do 130 | * not include a label, currentLabel returns null. 131 | */ 132 | public function get currentLabel():String { return _symbol.currentLabel; } 133 | 134 | /** The index of the frame in which the playhead is located on the timeline. 135 | * Note that the very first frame has the index '0'. */ 136 | public function get currentFrame():int { return _behavior.currentFrame; } 137 | public function set currentFrame(value:int):void { _behavior.currentFrame = value; } 138 | 139 | /** The time passed since the first frame of the timeline. */ 140 | public function get currentTime():Number { return _behavior.currentTime; } 141 | public function set currentTime(value:Number):void { _behavior.currentTime = value; } 142 | 143 | /** The frame rate with which the animation is advancing. */ 144 | public function get frameRate():Number { return _behavior.frameRate; } 145 | public function set frameRate(value:Number):void { _behavior.frameRate = value; } 146 | 147 | /** Indicates if the clip automatically rewinds to the first frame after reaching the 148 | * last. @default true */ 149 | public function get loop():Boolean { return _behavior.loop; } 150 | public function set loop(value:Boolean):void { _behavior.loop = value; } 151 | 152 | /** The total number of frames in the animation's timeline. */ 153 | public function get numFrames():int { return _behavior.numFrames; } 154 | 155 | /** Indicates if the animation is currently being played. */ 156 | public function get isPlaying():Boolean { return _behavior.isPlaying; } 157 | 158 | /** The total length of the animation in seconds. */ 159 | public function get totalTime():Number { return _behavior.totalTime; } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/starling/extensions/animate/AnimationAtlas.as: -------------------------------------------------------------------------------- 1 | package starling.extensions.animate 2 | { 3 | import flash.utils.Dictionary; 4 | 5 | import starling.display.Image; 6 | import starling.textures.Texture; 7 | import starling.textures.TextureAtlas; 8 | 9 | /** An AnimationAtlas stores the data of all the animations found in one set of files 10 | * created by Adobe Animate's "export texture atlas" feature. 11 | * 12 | *

Just like a TextureAtlas references a list of textures, an AnimationAtlas references 13 | * a list of animations.

14 | */ 15 | public class AnimationAtlas 16 | { 17 | public static const ASSET_TYPE:String = "animationAtlas"; 18 | 19 | private var _atlas:TextureAtlas; 20 | private var _symbolData:Dictionary; 21 | private var _symbolPool:Dictionary; 22 | private var _imagePool:Array; 23 | private var _frameRate:Number; 24 | private var _defaultSymbolName:String; 25 | 26 | private static const STD_MATRIX3D_DATA:Object = { 27 | "m00": 1, "m01": 0, "m02": 0, "m03": 0, 28 | "m10": 0, "m11": 1, "m12": 0, "m13": 0, 29 | "m20": 0, "m21": 0, "m22": 1, "m23": 0, 30 | "m30": 0, "m31": 0, "m32": 0, "m33": 1 31 | }; 32 | 33 | public function AnimationAtlas(data:Object, atlas:TextureAtlas) 34 | { 35 | if (data == null) throw new ArgumentError("data must not be null"); 36 | if (atlas == null) throw new ArgumentError("atlas must not be null"); 37 | 38 | data = normalizeJsonKeys(data); 39 | parseData(data); 40 | 41 | _atlas = atlas; 42 | _symbolPool = new Dictionary(); 43 | _imagePool = []; 44 | } 45 | 46 | /** Indicates if the atlas contains an animation with the given name. */ 47 | public function hasAnimation(name:String):Boolean 48 | { 49 | return hasSymbol(name); 50 | } 51 | 52 | /** Creates a new instance of the animation with the given name. If you don't provide 53 | * a name, the default symbol name will be used (i.e. the symbol's main timeline). */ 54 | public function createAnimation(name:String=null):Animation 55 | { 56 | name ||= _defaultSymbolName; 57 | if (!hasSymbol(name)) throw new ArgumentError("Animation not found: " + name); 58 | return new Animation(name, this); 59 | } 60 | 61 | /** Returns a list of all the animation names contained in this atlas. */ 62 | public function getAnimationNames(prefix:String="", out:Vector.=null):Vector. 63 | { 64 | out ||= new Vector.(); 65 | 66 | for (var name:String in _symbolData) 67 | if (name != Symbol.BITMAP_SYMBOL_NAME && name.indexOf(prefix) == 0) 68 | out[out.length] = name; 69 | 70 | out.sort(Array.CASEINSENSITIVE); 71 | return out; 72 | } 73 | 74 | // pooling 75 | 76 | internal function getTexture(name:String):Texture 77 | { 78 | return _atlas.getTexture(name); 79 | } 80 | 81 | internal function getImage(texture:Texture):Image 82 | { 83 | if (_imagePool.length == 0) return new Image(texture); 84 | else 85 | { 86 | var image:Image = _imagePool.pop() as Image; 87 | image.texture = texture; 88 | image.readjustSize(); 89 | return image; 90 | } 91 | } 92 | 93 | internal function putImage(image:Image):void 94 | { 95 | _imagePool[_imagePool.length] = image; 96 | } 97 | 98 | internal function hasSymbol(name:String):Boolean 99 | { 100 | return name in _symbolData; 101 | } 102 | 103 | internal function getSymbol(name:String):Symbol 104 | { 105 | var pool:Array = getSymbolPool(name); 106 | if (pool.length == 0) return new Symbol(getSymbolData(name), this); 107 | else return pool.pop(); 108 | } 109 | 110 | internal function putSymbol(symbol:Symbol):void 111 | { 112 | symbol.reset(); 113 | var pool:Array = getSymbolPool(symbol.symbolName); 114 | pool[pool.length] = symbol; 115 | symbol.currentFrame = 0; 116 | } 117 | 118 | internal function getSymbolData(name:String):Object 119 | { 120 | return _symbolData[name]; 121 | } 122 | 123 | // helpers 124 | 125 | private function parseData(data:Object):void 126 | { 127 | var metaData:Object = data.metadata; 128 | 129 | if (metaData && metaData.frameRate > 0) 130 | _frameRate = int(metaData.frameRate); 131 | else 132 | _frameRate = 24; 133 | 134 | _symbolData = new Dictionary(); 135 | 136 | // the actual symbol dictionary 137 | for each (var symbolData:Object in data.symbolDictionary.symbols) 138 | _symbolData[symbolData.symbolName] = preprocessSymbolData(symbolData); 139 | 140 | // the main animation 141 | var defaultSymbolData:Object = preprocessSymbolData(data.animation); 142 | _defaultSymbolName = defaultSymbolData.symbolName; 143 | _symbolData[_defaultSymbolName] = defaultSymbolData; 144 | 145 | // a purely internal symbol for bitmaps - simplifies their handling 146 | _symbolData[Symbol.BITMAP_SYMBOL_NAME] = { 147 | symbolName: Symbol.BITMAP_SYMBOL_NAME, 148 | timeline: { layers: [] } 149 | }; 150 | } 151 | 152 | private static function preprocessSymbolData(symbolData:Object):Object 153 | { 154 | var timeLineData:Object = symbolData.timeline; 155 | var layerDates:Array = timeLineData.layers; 156 | 157 | // In Animate CC, layers are sorted front to back. 158 | // In Starling, it's the other way round - so we simply reverse the layer data. 159 | 160 | if (!timeLineData.sortedForRender) 161 | { 162 | timeLineData.sortedForRender = true; 163 | layerDates.reverse(); 164 | } 165 | 166 | // We replace all "ATLAS_SPRITE_instance" elements with symbols of the same contents. 167 | // That way, we are always only dealing with symbols. 168 | 169 | var numLayers:int = layerDates.length; 170 | 171 | for (var l:int=0; l; 13 | private var _frameDuration:Number; 14 | private var _currentTime:Number; 15 | private var _currentFrame:int; 16 | private var _loop:Boolean; 17 | private var _playing:Boolean; 18 | private var _wasStopped:Boolean; 19 | private var _target:DisplayObject; 20 | private var _onFrameChanged:Function; 21 | 22 | private static const E:Number = 0.00001; 23 | 24 | /** Creates a new movie behavior for the given target. Whenever the frame changes, 25 | * the callback will be executed. */ 26 | public function MovieBehavior(target:DisplayObject, onFrameChanged:Function, 27 | frameRate:Number=24) 28 | { 29 | if (frameRate <= 0) throw new ArgumentError("Invalid frame rate"); 30 | if (target == null) throw new ArgumentError("Target cannot be null"); 31 | if (onFrameChanged == null) throw new ArgumentError("Callback cannot be null"); 32 | 33 | _target = target; 34 | _onFrameChanged = onFrameChanged; 35 | _frameDuration = 1.0 / frameRate; 36 | _frames = new []; 37 | _loop = true; 38 | _playing = true; 39 | _currentTime = 0.0; 40 | _currentFrame = 0; 41 | _wasStopped = true; 42 | } 43 | 44 | // playback methods 45 | 46 | /** Starts playback. Beware that the clip has to be added to a juggler, too! */ 47 | public function play():void 48 | { 49 | _playing = true; 50 | } 51 | 52 | /** Pauses playback. */ 53 | public function pause():void 54 | { 55 | _playing = false; 56 | } 57 | 58 | /** Stops playback, resetting "currentFrame" to zero. */ 59 | public function stop():void 60 | { 61 | _playing = false; 62 | _wasStopped = true; 63 | currentFrame = 0; 64 | } 65 | 66 | // frame actions 67 | 68 | public function addFrameAction(index:int, action:Function):void 69 | { 70 | getFrameAt(index).addAction(action); 71 | } 72 | 73 | public function removeFrameAction(index:int, action:Function):void 74 | { 75 | getFrameAt(index).removeAction(action); 76 | } 77 | 78 | public function removeFrameActions(index:int):void 79 | { 80 | getFrameAt(index).removeActions(); 81 | } 82 | 83 | private function getFrameAt(index:int):MovieFrame 84 | { 85 | if (index < 0 || index >= numFrames) throw new ArgumentError("Invalid frame index"); 86 | return _frames[index]; 87 | } 88 | 89 | // IAnimatable 90 | 91 | /** @inheritDoc */ 92 | public function advanceTime(passedTime:Number):void 93 | { 94 | if (!_playing) return; 95 | 96 | // The tricky part in this method is that whenever a callback is executed 97 | // (a frame action or a 'COMPLETE' event handler), that callback might modify the movie. 98 | // Thus, we have to start over with the remaining time whenever that happens. 99 | 100 | var frame:MovieFrame = _frames[_currentFrame]; 101 | var totalTime:Number = this.totalTime; 102 | 103 | if (_wasStopped) 104 | { 105 | // if the clip was stopped and started again, 106 | // actions of this frame need to be repeated. 107 | 108 | _wasStopped = false; 109 | 110 | if (frame.numActions) 111 | { 112 | frame.executeActions(_target, _currentFrame); 113 | advanceTime(passedTime); 114 | return; 115 | } 116 | } 117 | 118 | if (_currentTime >= totalTime) 119 | { 120 | if (_loop) 121 | { 122 | _currentTime = 0.0; 123 | _currentFrame = 0; 124 | _onFrameChanged(0); 125 | frame = _frames[0]; 126 | 127 | if (frame.numActions) 128 | { 129 | frame.executeActions(_target, _currentFrame); 130 | advanceTime(passedTime); 131 | return; 132 | } 133 | } 134 | else return; 135 | } 136 | 137 | var finalFrame:int = _frames.length - 1; 138 | var frameStartTime:Number = _currentFrame * _frameDuration; 139 | var dispatchCompleteEvent:Boolean = false; 140 | var previousFrame:int = _currentFrame; 141 | var restTimeInFrame:Number; 142 | var numActions:int; 143 | 144 | while (_currentTime + passedTime >= frameStartTime + _frameDuration) 145 | { 146 | restTimeInFrame = _frameDuration - _currentTime + frameStartTime; 147 | passedTime -= restTimeInFrame; 148 | _currentTime = frameStartTime + _frameDuration; 149 | 150 | if (_currentFrame == finalFrame) 151 | { 152 | _currentTime = totalTime; // prevent floating point problem 153 | 154 | if (hasEventListener(Event.COMPLETE)) 155 | { 156 | dispatchCompleteEvent = true; 157 | } 158 | else if (_loop) 159 | { 160 | _currentTime = 0; 161 | _currentFrame = 0; 162 | frameStartTime = 0; 163 | } 164 | else return; 165 | } 166 | else 167 | { 168 | _currentFrame += 1; 169 | frameStartTime += _frameDuration; 170 | } 171 | 172 | frame = _frames[_currentFrame]; 173 | numActions = frame.numActions; 174 | 175 | if (dispatchCompleteEvent) 176 | { 177 | _onFrameChanged(_currentFrame); 178 | dispatchEventWith(Event.COMPLETE); 179 | advanceTime(passedTime); 180 | return; 181 | } 182 | else if (numActions) 183 | { 184 | _onFrameChanged(_currentFrame); 185 | frame.executeActions(_target, _currentFrame); 186 | advanceTime(passedTime); 187 | return; 188 | } 189 | } 190 | 191 | if (previousFrame != _currentFrame) 192 | _onFrameChanged(_currentFrame); 193 | 194 | _currentTime += passedTime; 195 | } 196 | 197 | // properties 198 | 199 | /** The total number of frames. */ 200 | public function get numFrames():int { return _frames.length; } 201 | public function set numFrames(value:int):void 202 | { 203 | for (var i:int=numFrames; ifalse when the end 256 | * is reached. */ 257 | public function get isPlaying():Boolean 258 | { 259 | if (_playing) 260 | return _loop || _currentTime < totalTime; 261 | else 262 | return false; 263 | } 264 | 265 | /** Indicates if a (non-looping) movie has come to its end. */ 266 | public function get isComplete():Boolean 267 | { 268 | return !_loop && _currentTime >= totalTime; 269 | } 270 | } 271 | } 272 | 273 | import starling.display.DisplayObject; 274 | 275 | class MovieFrame 276 | { 277 | private var _actions:Vector.; 278 | 279 | public function MovieFrame() 280 | { } 281 | 282 | public function addAction(action:Function):void 283 | { 284 | if (action == null) throw new ArgumentError("action cannot be null"); 285 | if (_actions == null) _actions = new []; 286 | if (_actions.indexOf(action) == -1) _actions[_actions.length] = action; 287 | } 288 | 289 | public function removeAction(action:Function):void 290 | { 291 | if (_actions) 292 | { 293 | var index:int = _actions.indexOf(action); 294 | if (index >= 0) _actions.removeAt(index); 295 | } 296 | } 297 | 298 | public function removeActions():void 299 | { 300 | if (_actions) _actions.length = 0; 301 | } 302 | 303 | public function executeActions(target:DisplayObject, frameID:int):void 304 | { 305 | if (_actions) 306 | { 307 | for (var i:int=0, len:int=_actions.length; i i ? layer.getChildAt(i) as Symbol : null; 101 | var newSymbol:Symbol = null; 102 | var symbolName:String = elementData.symbolName; 103 | 104 | if (!_atlas.hasSymbol(symbolName)) 105 | symbolName = BITMAP_SYMBOL_NAME; 106 | 107 | if (oldSymbol && oldSymbol._symbolName == symbolName) 108 | newSymbol = oldSymbol; 109 | else 110 | { 111 | if (oldSymbol) 112 | { 113 | oldSymbol.removeFromParent(); 114 | _atlas.putSymbol(oldSymbol); 115 | } 116 | 117 | newSymbol = _atlas.getSymbol(symbolName); 118 | layer.addChildAt(newSymbol, i); 119 | } 120 | 121 | newSymbol.setTransformationMatrix(elementData.matrix3D); 122 | newSymbol.setBitmap(elementData.bitmap); 123 | newSymbol.setColor(elementData.color); 124 | newSymbol.setLoop(elementData.loop); 125 | newSymbol.setType(elementData.symbolType); 126 | 127 | if (newSymbol.type == SymbolType.GRAPHIC) 128 | { 129 | var firstFrame:int = elementData.firstFrame; 130 | var frameAge:int = _currentFrame - frameData.index; 131 | 132 | if (newSymbol.loopMode == LoopMode.SINGLE_FRAME) 133 | newSymbol.currentFrame = firstFrame; 134 | else if (newSymbol.loopMode == LoopMode.LOOP) 135 | newSymbol.currentFrame = (firstFrame + frameAge) % newSymbol._numFrames; 136 | else 137 | newSymbol.currentFrame = firstFrame + frameAge; 138 | } 139 | } 140 | 141 | var numObsoleteSymbols:int = layer.numChildren - numElements; 142 | 143 | for (i=0; i= 0) ? data.alphaMultiplier : 1.0; 214 | } 215 | else 216 | { 217 | alpha = 1.0; 218 | } 219 | } 220 | 221 | private function setLoop(data:String):void 222 | { 223 | if (data) _loopMode = LoopMode.parse(data); 224 | else _loopMode = LoopMode.LOOP; 225 | } 226 | 227 | private function setType(data:String):void 228 | { 229 | if (data) _type = SymbolType.parse(data); 230 | } 231 | 232 | private function getNumFrames():int 233 | { 234 | var numFrames:int = 0; 235 | 236 | for (var i:int=0; i<_numLayers; ++i) 237 | { 238 | var frameDates:Array = getLayerData(i).frames as Array; 239 | var numFrameDates:int = frameDates ? frameDates.length : 0; 240 | var layerNumFrames:int = numFrameDates ? frameDates[0].index : 0; 241 | 242 | for (var j:int=0; j numFrames) 246 | numFrames = layerNumFrames; 247 | } 248 | 249 | return numFrames || 1; 250 | } 251 | 252 | private function getFrameLabels():Array 253 | { 254 | var labels:Array = []; 255 | 256 | for (var i:int=0; i<_numLayers; ++i) 257 | { 258 | var frameDates:Array = getLayerData(i).frames as Array; 259 | var numFrameDates:int = frameDates ? frameDates.length : 0; 260 | 261 | for (var j:int=0; j startFrame) return label.name; 287 | } 288 | 289 | return _frameLabels ? _frameLabels[0].name : null; // wrap around 290 | } 291 | 292 | public function get currentLabel():String 293 | { 294 | var numLabels:int = _frameLabels.length; 295 | var highestLabel:FrameLabel = numLabels ? _frameLabels[0] : null; 296 | 297 | for (var i:int=1; i frameIndex) 367 | return frame; 368 | } 369 | 370 | return null; 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /src/starling/extensions/animate/SymbolType.as: -------------------------------------------------------------------------------- 1 | package starling.extensions.animate 2 | { 3 | import starling.errors.AbstractClassError; 4 | 5 | internal class SymbolType 6 | { 7 | /** @private */ 8 | public function SymbolType() { throw new AbstractClassError(); } 9 | 10 | public static const GRAPHIC:String = "graphic"; 11 | public static const MOVIE_CLIP:String = "movieclip"; 12 | public static const BUTTON:String = "button"; 13 | 14 | public static function isValid(value:String):Boolean 15 | { 16 | return value == GRAPHIC || value == MOVIE_CLIP || value == BUTTON; 17 | } 18 | 19 | public static function parse(value:String):String 20 | { 21 | switch (value) 22 | { 23 | case "G": 24 | case GRAPHIC: return GRAPHIC; 25 | case "MC": 26 | case MOVIE_CLIP: return MOVIE_CLIP; 27 | case "B": 28 | case BUTTON: return BUTTON; 29 | default: throw new ArgumentError("Invalid symbol type: " + value); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tools/optimize_json/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://www.rubygems.org" 2 | 3 | gem 'rubyzip', '>=1.2' 4 | -------------------------------------------------------------------------------- /tools/optimize_json/README.md: -------------------------------------------------------------------------------- 1 | # JSON-Optimizer 2 | 3 | This Ruby script takes an `Animation.json` file from _Adobe Animate's_ "Texture Atlas" export and optimizes it in the following ways: 4 | 5 | * By removing all white-space. _Adobe Animate's_ output is "prettified", which makes it easy to read, but blows up the file size enormously. 6 | * By removing `DecomposedMatrix` elements. This data is redundant and not required by the extension. 7 | * By combining identical, empty frames via their `duration` property. This can take up a lot of space on empty layers. 8 | * Optionally, by zipping the file. When combined with the [ZipLoader](https://wiki.starling-framework.org/extensions/zipped-assets), the _AnimAssetManager_ can load those zip-files directly at run-time. 9 | 10 | ### Installation 11 | 12 | You need to have [Ruby](https://www.ruby-lang.org) and [RubyGems](https://rubygems.org) installed on your system. You also need the `rubyzip` gem; like any RubyGem, it can be installed like this: 13 | 14 | gem install rubyzip 15 | 16 | ### Usage 17 | 18 | When that's done, you can call the script like this: 19 | 20 | cd tools/optimize_json 21 | ruby optimize_json.rb path/to/Animation.json 22 | 23 | This will optimize `Animation.json` in place; you can also leave the input file unchanged and let the optimized version be stored under a different path or name: 24 | 25 | ruby optimize_json.rb path/to/Animation.json path/to/out.json 26 | 27 | You can get a list of options by running the script without parameters: 28 | 29 | ``` 30 | ruby optimize_json.rb 31 | Usage: optimize_json.rb [options] input_file [output_file] 32 | 33 | Common options: 34 | -z, --zip Zip output file 35 | -p, --prettify Prettify output file 36 | -h, --help Show this message 37 | ``` 38 | 39 | For example, to zip the output file, use the following command: 40 | 41 | ruby optimize_json.rb --zip Animation.json 42 | 43 | This will add the file `Animation.json.zip` to the same folder. 44 | 45 | ### Results 46 | 47 | As for the results the tool can achieve, here's what happens with the "Ninja Girl" animation file. 48 | 49 | - Original output from Adobe: 4.1 MB (zipped: 99kB) 50 | - Optimized with "optimize_json": 476 kB (zipped: 32 kB) 51 | -------------------------------------------------------------------------------- /tools/optimize_json/optimize_json.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'optparse' 5 | require 'ostruct' 6 | require 'json' 7 | require 'zip' 8 | 9 | script_name = File.basename(__FILE__) 10 | 11 | options = OpenStruct.new 12 | parser = OptionParser.new do |opt| 13 | opt.banner = "Usage: #{script_name} [options] input_file [output_file]" 14 | opt.separator "" 15 | opt.separator "Common options:" 16 | opt.on('-z', '--zip', "Zip output file") { |o| options.zip = true } 17 | opt.on('-p', '--prettify', "Prettify output file") { |o| options.prettify = true } 18 | opt.on('-h', '--help', "Show this message") { puts opt; exit } 19 | end 20 | parser.parse! 21 | 22 | if ARGV.count < 1 23 | puts parser 24 | exit 25 | end 26 | 27 | input_file = ARGV[0] 28 | output_file = if ARGV.count > 1 then ARGV[1] else input_file end 29 | 30 | if !File.file?(input_file) 31 | puts "File not found: #{input_file}" 32 | exit 33 | end 34 | 35 | class String 36 | def add_path(suffix) 37 | return self + "/" + suffix.to_s 38 | end 39 | end 40 | 41 | def process(object, path) 42 | 43 | if object.instance_of? Array 44 | clone = [] 45 | object.each_index do |i| 46 | clone[i] = process(object[i], path.add_path(i)) 47 | end 48 | 49 | if path.end_with? "Frames" and clone.count > 1 50 | compressed_frames = [clone[0]] 51 | (1...clone.count).each do |frame_index| 52 | prev_frame = compressed_frames.last 53 | next_frame = clone[frame_index] 54 | if (prev_frame['elements'].count == 0 and next_frame['elements'].count == 0 and !next_frame.has_key?('name')) 55 | prev_frame['duration'] += next_frame['duration'] 56 | else 57 | compressed_frames << next_frame 58 | end 59 | end 60 | clone = compressed_frames 61 | end 62 | 63 | elsif object.instance_of? Hash 64 | clone = {} 65 | object.each_key do |k| 66 | if k != "DecomposedMatrix" 67 | clone[k] = process(object[k], path.add_path(k)) 68 | end 69 | end 70 | else 71 | clone = object 72 | end 73 | 74 | return clone 75 | end 76 | 77 | animation = JSON.parse(File.read(input_file)) 78 | 79 | result = process(animation, "") 80 | json = options.prettify ? JSON.pretty_generate(result) : result.to_json 81 | 82 | if options.zip 83 | output_file = "#{output_file}.zip" if !output_file.downcase.end_with?('.zip') 84 | Zip::File.open(output_file, Zip::File::CREATE) do |zipfile| 85 | zipfile.get_output_stream('Animation.json') { |f| f << json } 86 | end 87 | else 88 | File.open(output_file, 'w') { |f| f << json } 89 | end 90 | --------------------------------------------------------------------------------