= 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 |
--------------------------------------------------------------------------------