diff --git a/spriter/definitions/SpatialInfo.hx b/spriter/definitions/SpatialInfo.hx index 0962737..d67d43b 100644 --- a/spriter/definitions/SpatialInfo.hx +++ b/spriter/definitions/SpatialInfo.hx @@ -61,6 +61,12 @@ class SpatialInfo implements ISpriterPooled return this; } + /** + * Convenient method to set both x and y in one line. + * @param x + * @param y + * @return + */ public function setPos(x:Float = 0, y:Float = 0):SpatialInfo { this.x = x; @@ -68,6 +74,11 @@ class SpatialInfo implements ISpriterPooled return this; } + /** + * Convenient method to set both scaleX and scaleY. + * @param scale + * @return + */ public function setScale(scale:Float):SpatialInfo { this.scaleX = scale; @@ -75,10 +86,10 @@ class SpatialInfo implements ISpriterPooled return this; } /** - * + * Get SpatialInfo modified by parent. * @param parentInfo * @param out if null, this method will override this SpatialInfo - * @return + * @return SpatialInfo modified with parent values. Use out if specified. */ public function unmapFromParent(parentInfo:SpatialInfo, out:SpatialInfo = null):SpatialInfo { @@ -115,10 +126,18 @@ class SpatialInfo implements ISpriterPooled return out; } + /** + * Create a new SpatialInfo with values from this. + * @return new SpatialInfo + */ inline public function copy():SpatialInfo { return new SpatialInfo(x, y, angle, scaleX, scaleY, a, spin); } + /** + * Copy SpatialInfo from other values + * @param out spatialInfo to copy + */ public function clone(out:SpatialInfo):Void { out.init(x, y, angle, scaleX, scaleY, a, spin);//initializing the out object with the values of this object diff --git a/spriter/definitions/SpriterAnimation.hx b/spriter/definitions/SpriterAnimation.hx index 3c915a9..05db37c 100644 --- a/spriter/definitions/SpriterAnimation.hx +++ b/spriter/definitions/SpriterAnimation.hx @@ -46,6 +46,8 @@ class SpriterAnimation public var soundlines:Array>; #end + var _triggerTime:Int; + public function new(xml:Access) @@ -121,10 +123,13 @@ class SpriterAnimation } #end } + + _triggerTime = -1; } /** * - * @param newTime Use a time between [0,length] + * @param newTime Use a time between [0,length[ + * @param elapsedTime elapsed time * @param library library to compute and draw the final graphics * @param root IScml to use some features * @param currentEntity to use some features @@ -231,6 +236,8 @@ class SpriterAnimation objectKeys[k].paint(); }*/ + var triggerResult = false; + //following lines not in scml references yet #if !SPRITER_NO_TAG if (taglines != null) @@ -239,6 +246,7 @@ class SpriterAnimation { if (isTriggered(tag.time, mainKey.time, newTime, elapsedTime)) { + triggerResult = true; spriter.clearTag(); for (i in 0...tag.t.length) { @@ -258,6 +266,7 @@ class SpriterAnimation { if (isTriggered(keyVar.time, mainKey.time, newTime, elapsedTime)) { + triggerResult = true; spriter.updateVar(_var.id, keyVar.value); } } @@ -273,6 +282,7 @@ class SpriterAnimation { if (isTriggered(soundKey.time, mainKey.time, newTime, elapsedTime)) { + triggerResult = true; spriter.dispatchSound(soundKey.folder, soundKey.file); } } @@ -288,24 +298,42 @@ class SpriterAnimation { if (isTriggered(eventKey.time, mainKey.time, newTime, elapsedTime)) { + triggerResult = true; spriter.dispatchEvent(event.name); } } } } #end + + //we store the trigger time only when everything has been tested (if not we'll only trigger first event...) + if(triggerResult) + _triggerTime = mainKey.time; + else if (_triggerTime != mainKey.time) + _triggerTime = -1;//we reset if we changed the mainkey because it'll allow to trigger when play back and forth + //clean up spatialInfo = null; } - function isTriggered(triggerTime:Int, keyTime:Int, newTime:Int, elapsedTime:Int):Bool + /** + * Check if an event, variable or sound is triggered. + * By checking if triggerTime and keyTime have same value; + * And then if triggerTime has been reached for the first time (in the current loop) + * @param triggerTime + * @param keyTime + * @param newTime + * @param elapsedTime + * @return Bool + */ + function isTriggered(eventTime:Int, keyTime:Int, newTime:Int, elapsedTime:Int):Bool { - if (triggerTime == keyTime) + if (eventTime == keyTime) { - if (newTime - elapsedTime < keyTime) + if (newTime - elapsedTime <= keyTime && _triggerTime != keyTime) { return true; - }else if (triggerTime == 0 && newTime == elapsedTime) { //allow to trigger the first frame + }else if (eventTime == 0 && newTime == elapsedTime) { //allow to trigger the first frame return true; }else { return false; diff --git a/spriter/engine/Spriter.hx b/spriter/engine/Spriter.hx index 9c95ebf..5d4c567 100644 --- a/spriter/engine/Spriter.hx +++ b/spriter/engine/Spriter.hx @@ -10,9 +10,9 @@ import spriter.definitions.SpriterEntity; import spriter.definitions.SpriterFile; import spriter.definitions.SpriterFolder; import spriter.library.AbstractLibrary; -import spriter.util.MathUtils; import spriter.util.SpriterUtil; import spriter.vars.Variable; +import spriter.util.MathUtils; #if SPRITER_CUSTOM_MAP import spriter.definitions.CustomCharMap; #end @@ -28,13 +28,26 @@ class Spriter public var library:AbstractLibrary; public var spriterName:String; /** - * Time elapsed since beginning of current animation + * Time in ms that can be used to jump to a specific time. + * Beware of specifities that make it difficult to read: + * - time elapsed since beginning of current animation. + * - Resetted when starting new animation or on reflection. + * - Starting at animation length when animating backward and then become negative. + * @see also progress property that should be more convenient + */ + public var time:Int = 0; + /** + * Time in the range of [0,currentAnimation.length[ in ms + * Calculated from time and playbackSpeed */ - public var timeMS:Int = 0; + public var normalizedTime(default, null):Int = 0; + /** - * Time in the range of [0,currentAnimation.length] + * Time progression in the range of [0,1] in ms that can be used to jump to a specific progression */ - public var normalizedTime:Int = 0; + public var progress(get, set):Float; + //used to update progress without calling setter that modifies time and normalized Time + var _progress:Float; /** * Manipulate positions (x,y), scale, alpha and rotation through this object. @@ -126,37 +139,43 @@ class Spriter public function advanceTime(elapsedMS:Int):Void { + var speedElapsed = Std.int(elapsedMS * playbackSpeed); + var changedSign = false; if (!paused) { - timeMS += Std.int(elapsedMS * playbackSpeed); + var prevValue = time; + time += speedElapsed; + changedSign = !SpriterUtil.sameSign(prevValue, time); if (currentAnimation.loopType == LOOPING) { - normalizedTime += Std.int(elapsedMS * playbackSpeed); - if (normalizedTime >= currentAnimation.length)//forward - { - normalizedTime -= currentAnimation.length; - ++loop; - }else if (normalizedTime <= 0)//backward - { - normalizedTime += currentAnimation.length; - ++loop; - } + if(time >= 0) + { + normalizedTime = time % currentAnimation.length; + }else{ + normalizedTime = ((currentAnimation.length + (time % currentAnimation.length)) % currentAnimation.length); + } + + loop = Std.int(time / currentAnimation.length); - }else{//no looping - normalizedTime = Std.int(Math.max(0, Math.min(timeMS, currentAnimation.length))); + }else{ + //no looping + normalizedTime = Std.int(Math.max(0, Math.min(time, currentAnimation.length))); } + } + _progress = normalizedTime / currentAnimation.length; //even if paused we need to draw it - currentAnimation.setCurrentTime(normalizedTime, Std.int(MathUtils.fabs(elapsedMS * playbackSpeed)), library, this, currentEntity, info); + currentAnimation.setCurrentTime(normalizedTime, MathUtils.abs(speedElapsed), library, this, currentEntity, info); //callback if (currentAnimation.loopType == LOOPING) { - if (loop > lastLoop) { + if (loop != lastLoop || changedSign) { lastLoop = loop; dispatchComplete(); } - }else {//no looping + }else { + //no looping var when:Int = playbackSpeed > 0 ? currentAnimation.length : 0; if (normalizedTime == when) { if (!onCompleteOnce && !hasReflect) onCompleteOnce = true;//force to avoid dispatching every frame when it's done @@ -351,7 +370,7 @@ class Spriter /** * Play a stack of animations whatever the entity. - * @param name of the entity + * @param name of the entities * @param anims names of the animations in order * @param endAnimCallback function callback, return (s:Spriter, entity:String, anim:String) * @param removeCallback remove function callback after dispatch @@ -377,16 +396,97 @@ class Spriter } } + /** + * Reset time to start time or end time depending of playback direction. + */ public function resetTime():Void { if (playbackSpeed > 0) { - loop = lastLoop = normalizedTime = timeMS = 0; - }else{ loop = lastLoop = 0; - normalizedTime = timeMS = currentAnimation.length; + progress = 0; + }else{ + loop = 0; + lastLoop = 0; + progress = 1; } } + + /** + * Set time progression + * @param p progress of the animation. Value must be [0,1]. + */ + function set_progress(p:Float):Float + { + if (p < 0) + p = 0; + else if (p > 1) + p = 1; + _progress = p; + time = normalizedTime = Std.int(p * currentAnimation.length); + return _progress; + } + function get_progress():Float + { + return _progress; + } + + public function getNextKeyTime():Int + { + var nextTime = 0; + + var len:Int = currentAnimation.mainlineKeys.length; + for (m in 0...len) + { + if(currentAnimation.mainlineKeys[m].time > time) + { + nextTime = currentAnimation.mainlineKeys[m].time; + break; + } + + } + + return nextTime; + } + + public function getPrevKeyTime():Int + { + var i = currentAnimation.mainlineKeys.length - 1; + + var prevTime = currentAnimation.mainlineKeys[i].time; + while(i >= 0) { + if(currentAnimation.mainlineKeys[i].time < time) + { + prevTime = currentAnimation.mainlineKeys[i].time; + break; + } + i--; + } + return prevTime; + } + + /** + * Set the current time to the next frame time + */ + public function nextFrame():Void + { + var t = getNextKeyTime(); + progress = t / currentAnimation.length; + } + /** + * Set the current time to the previous frame time + */ + public function prevFrame():Void + { + var t = getPrevKeyTime(); + progress = t / currentAnimation.length; + } + + /** + * Reverse the playback speed and reset the time to start or end time depending if playing forward or backward + * @param value + * @return + */ public function reverse(value:Bool = true):Spriter { if (value) @@ -400,6 +500,11 @@ class Spriter resetTime(); return this; } + /** + * Set to true to animate forward and then backward + * @param value + * @return + */ public function reflect(value:Bool = true):Spriter { if (value) diff --git a/spriter/util/MathUtils.hx b/spriter/util/MathUtils.hx index 3193063..a18db81 100644 --- a/spriter/util/MathUtils.hx +++ b/spriter/util/MathUtils.hx @@ -6,7 +6,8 @@ package spriter.util; */ class MathUtils { - inline static public function fabs(n:Float):Float + @:generic + inline static public function abs(n:T):T { if(n >= 0) { return n; @@ -118,19 +119,18 @@ class MathUtils var t2:Float = x; var x2:Float; var d2:Float; - var i:Int; // First try a few iterations of Newton's method -- normally very fast. for (i in 0...8) { x2 = sampleCurve(ax, bx, cx, t2) - x; - if(fabs(x2) < epsilon) { + if(abs(x2) < epsilon) { return t2; } d2 = sampleCurveDerivativeX(ax, bx, cx, t2); - if(fabs(d2) < 1e-6) { + if(abs(d2) < 1e-6) { break; } @@ -155,7 +155,7 @@ class MathUtils while(t0 < t1) { x2 = sampleCurve(ax, bx, cx, t2); - if(fabs(x2-x) < epsilon) { + if(abs(x2-x) < epsilon) { return t2; } if(x > x2) {