diff --git a/src/flambe/swf/MovieSprite.hx b/src/flambe/swf/MovieSprite.hx index 611ff7c7..bd22a01f 100644 --- a/src/flambe/swf/MovieSprite.hx +++ b/src/flambe/swf/MovieSprite.hx @@ -7,8 +7,10 @@ package flambe.swf; import flambe.animation.AnimatedFloat; import flambe.display.Sprite; import flambe.math.FMath; + import flambe.swf.MovieSymbol; import flambe.util.Signal0; +import flambe.util.Signal1.Signal1; using flambe.util.Arrays; using flambe.util.BitSets; @@ -31,29 +33,40 @@ class MovieSprite extends Sprite * that. */ public var speed (default, null) :AnimatedFloat; + + /** get/set the current frame (playhead position) **/ + public var frame(get, set):Float; + /* The total number of frames in this MovieSprite */ + public var totalFrames(get_totalFrames, never):Int; + /** Whether this movie is currently paused. */ public var paused (get, set) :Bool; /** Emitted when this movie loops back to the beginning. */ public var looped (get, null) :Signal0; - + + /** Emitted when the playhead passes a label keyframe on the 'labels' layer. */ + public var labelPassed(default, null):Signal1; + public function new (symbol :MovieSymbol) { super(); this.symbol = symbol; - - speed = new AnimatedFloat(1); - + + speed = new AnimatedFloat(1); + labelPassed = new Signal1(); + _animators = Arrays.create(symbol.layers.length); for (ii in 0..._animators.length) { var layer = symbol.layers[ii]; - _animators[ii] = new LayerAnimator(layer); + _animators[ii] = new LayerAnimator(layer); } - - _frame = 0; + + _frame = -1; _position = 0; - goto(1); + _lastLabelFrame = -1; + goto(0); } /** @@ -74,8 +87,18 @@ class MovieSprite extends Sprite } return null; } - - override public function onAdded () + + + /** + * Get the frame index for a frame-label keyframe + * @param name + * @return The frame index, or -1 if no label exists for the given name + */ + public inline function findLabel(name:String):Float { + return symbol.findLabel(name); + } + + override public function onAdded () { super.onAdded(); @@ -83,7 +106,7 @@ class MovieSprite extends Sprite owner.addChild(animator.content); } } - + override public function onRemoved () { super.onRemoved(); @@ -97,17 +120,16 @@ class MovieSprite extends Sprite override public function onUpdate (dt :Float) { - super.onUpdate(dt); + super.onUpdate(dt); speed.update(dt); switch (_flags & (Sprite.MOVIESPRITE_PAUSED | Sprite.MOVIESPRITE_SKIP_NEXT)) { case 0: // Neither paused nor skipping set, advance time - _position += speed._*dt; + _position += speed._ * dt; if (_position > symbol.duration) { _position = _position % symbol.duration; - if (_looped != null) { _looped.emit(); } @@ -117,10 +139,10 @@ class MovieSprite extends Sprite _flags = _flags.remove(Sprite.MOVIESPRITE_SKIP_NEXT); } - var newFrame = _position*symbol.frameRate; + var newFrame = _position * symbol.frameRate; goto(newFrame); } - + private function goto (newFrame :Float) { if (_frame == newFrame) { @@ -128,36 +150,32 @@ class MovieSprite extends Sprite } var wrapped = newFrame < _frame; - if (wrapped) { - for (animator in _animators) { - animator.needsKeyframeUpdate = true; - animator.keyframeIdx = 0; - } - } - for (animator in _animators) { - animator.composeFrame(newFrame); - } - + + for (animator in _animators) { + animator.composeFrame(newFrame, wrapped); + if (animator.labelChanged) labelPassed.emit(animator.currentLabel); + } + _frame = newFrame; } - + inline private function get_position () :Float - { - return _position; - } - + { + return _position; + } + private function set_position (position :Float) :Float - { - return _position = FMath.clamp(position, 0, symbol.duration); - } + { + return _position = FMath.clamp(position, 0, symbol.duration); + } inline private function get_paused () :Bool - { - return _flags.contains(Sprite.MOVIESPRITE_PAUSED); - } + { + return _flags.contains(Sprite.MOVIESPRITE_PAUSED); + } private function set_paused (paused :Bool) - { + { _flags = _flags.set(Sprite.MOVIESPRITE_PAUSED, paused); return paused; } @@ -169,7 +187,17 @@ class MovieSprite extends Sprite } return _looped; } - + + + inline function get_frame():Float return _frame; + inline function set_frame(value:Float):Float { + _position = value / symbol.frameRate; + goto(value); + return _frame; + } + + inline function get_totalFrames():Int return Math.ceil(symbol.duration / symbol.frameRate); + /** * Internal method to set the position to 0 and skip the next update. This is required to modify * the playback position of child movies during an update step, so that after the update @@ -178,34 +206,42 @@ class MovieSprite extends Sprite @:allow(flambe) function rewind () { _position = 0; + _lastLabelFrame = -1; _flags = _flags.add(Sprite.MOVIESPRITE_SKIP_NEXT); } private var _animators :Array; - - private var _position :Float; + private var _position :Float; private var _frame :Float; - - private var _looped :Signal0 = null; + + private var _looped :Signal0 = null; + + // frame labels... + var _framesToLabels:Map; + var _labelsToFrames:Map; + var _lastLabelFrame:Int = -1; + var _frameLabels:Array; } private class LayerAnimator { - public var content (default, null) :Entity; - - public var needsKeyframeUpdate :Bool = false; + public var content(default, null):Entity; + + public var currentLabel(default, null):String = null; + public var labelChanged(default, null):Bool = false; + + public var needsKeyframeUpdate:Bool = false; public var keyframeIdx :Int = 0; - - public var layer :MovieLayer; - + + public var layer:MovieLayer; + public function new (layer :MovieLayer) { this.layer = layer; - + content = new Entity(); if (layer.empty) { _sprites = null; - } else { // Populate _sprites with the Sprite at each keyframe, reusing consecutive symbols _sprites = Arrays.create(layer.keyframes.length); @@ -223,17 +259,25 @@ private class LayerAnimator } } - public function composeFrame (frame :Float) + public function composeFrame (frame :Float, wrapped:Bool) { - if (_sprites == null) { + + labelChanged = false; + + if (wrapped) { + needsKeyframeUpdate = true; + keyframeIdx = 0; + } + + if (_sprites == null) { // TODO(bruno): Test this code path // Don't animate empty layers return; } - - var keyframes = layer.keyframes; + + var keyframes = layer.keyframes; var finalFrame = keyframes.length - 1; - + if (frame > layer.frames) { // TODO(bruno): Test this code path // Not enough frames on this layer, hide it @@ -247,11 +291,12 @@ private class LayerAnimator ++keyframeIdx; needsKeyframeUpdate = true; } - + var sprite; if (needsKeyframeUpdate) { needsKeyframeUpdate = false; - // Switch to the next instance if this is a multi-layer symbol + currentLabel = null; + // Switch to the next instance if this is a multi-layer symbol sprite = _sprites[keyframeIdx]; if (sprite != content.get(Sprite)) { if (Type.getClass(sprite) == MovieSprite) { @@ -263,14 +308,22 @@ private class LayerAnimator } else { sprite = content.get(Sprite); } - + + var kf = keyframes[keyframeIdx]; var visible = kf.visible && kf.symbol != null; sprite.visible = visible; - if (!visible) { + + var label = kf.label; + if (label != currentLabel) { + currentLabel = label; + labelChanged = true; + } + + if (!visible) { return; // Don't bother animating invisible layers - } - + } + var x = kf.x; var y = kf.y; var scaleX = kf.scaleX; @@ -278,7 +331,7 @@ private class LayerAnimator var skewX = kf.skewX; var skewY = kf.skewY; var alpha = kf.alpha; - + if (kf.tweened && keyframeIdx < finalFrame) { var interp = (frame-kf.index) / kf.duration; var ease = kf.ease; @@ -295,7 +348,7 @@ private class LayerAnimator } interp = ease*t + (1 - ease)*interp; } - + var nextKf = keyframes[keyframeIdx + 1]; x += (nextKf.x-x) * interp; y += (nextKf.y-y) * interp; @@ -310,14 +363,14 @@ private class LayerAnimator var matrix = sprite.getLocalMatrix(); var sinX = Math.sin(skewX), cosX = Math.cos(skewX); var sinY = Math.sin(skewY), cosY = Math.cos(skewY); - matrix.set(cosY*scaleX, sinY*scaleX, -sinX*scaleY, cosX*scaleY, x, y); + matrix.set(cosY * scaleX, sinY * scaleX, -sinX * scaleY, cosX * scaleY, x, y); // Append the pivot matrix.translate(-kf.pivotX, -kf.pivotY); - - sprite.alpha._ = alpha; + + sprite.alpha._ = alpha; } // The sprite to show at each keyframe index, or null if this layer has no symbol instances private var _sprites :Array; -} +} \ No newline at end of file diff --git a/src/flambe/swf/MovieSymbol.hx b/src/flambe/swf/MovieSymbol.hx index 88d44b16..9162d24b 100644 --- a/src/flambe/swf/MovieSymbol.hx +++ b/src/flambe/swf/MovieSymbol.hx @@ -36,16 +36,28 @@ class MovieSymbol */ public var duration (default, null) :Float; + /** + * All frame labels within the layers of this symbol + */ + public var frameLabels(default, null):Map; + + public function new (lib :Library, json :MovieFormat) { _name = json.id; frameRate = lib.frameRate; - + frameLabels = new Map(); + frames = 0; layers = Arrays.create(json.layers.length); for (ii in 0...layers.length) { var layer = new MovieLayer(json.layers[ii]); frames = cast Math.max(layer.frames, frames); + for (kf in layer.keyframes) { + var kfi = kf.index; + if (kf.label != null) frameLabels.set(kf.label, kf.index); + } + layers[ii] = layer; } duration = frames / frameRate; @@ -60,33 +72,43 @@ class MovieSymbol { return new MovieSprite(this); } - - private var _name :String; + + /** + * Find the frame for a given label + * @param name + * @return An index, or -1 if not found + */ + public function findLabel(name:String):Float + { + return frameLabels.exists(name) ? frameLabels.get(name) : -1; + } + + private var _name :String; + } class MovieLayer { - public var name (default, null) :String; + public var name (default, null) :String; public var keyframes (default, null) :Array; public var frames (default, null) :Int; - - /** Whether this layer has no symbol instances. */ + + /** Whether this layer has no symbol instances, and no labels. */ public var empty (default, null) :Bool = true; - + public function new (json :LayerFormat) { name = json.name; - + var prevKf = null; keyframes = Arrays.create(json.keyframes.length); for (ii in 0...keyframes.length) { prevKf = new MovieKeyframe(json.keyframes[ii], prevKf); keyframes[ii] = prevKf; - - empty = empty && prevKf.symbolName == null; + empty = (empty && prevKf.symbolName == null && prevKf.label == null); } - - frames = (prevKf != null) ? prevKf.index + Std.int(prevKf.duration) : 0; + + frames = (prevKf != null) ? prevKf.index + Std.int(prevKf.duration) : 0; } }