diff --git a/src/as/com/threerings/flash/path/CompositePath.as b/src/as/com/threerings/flash/path/CompositePath.as new file mode 100644 index 00000000..c52d66f2 --- /dev/null +++ b/src/as/com/threerings/flash/path/CompositePath.as @@ -0,0 +1,60 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.flash.path { + +import flash.display.Sprite; + +import com.threerings.util.Util; + +/** + * Executes a series of paths in order. + */ +public class CompositePath extends Path +{ + /** + * Creates a composite path with the supplied list of individual paths. + */ + public function CompositePath (... paths) + { + _paths = Util.unfuckVarargs(paths); + init(new Sprite()); // any old display object will do (yay actionscript) + } + + // from Path + override protected function tick (curStamp :int) :int + { + var remain :int = 0; + if (_pathIdx >= 0) { + remain = tickPath(_paths[_pathIdx] as Path, curStamp); + } + if (remain <= 0) { + if (++_pathIdx < _paths.length) { + return startPath(_paths[_pathIdx] as Path, curStamp, remain); + } + } + return remain; + } + + protected var _paths :Array; + protected var _pathIdx :int = -1; +} +} diff --git a/src/as/com/threerings/flash/path/DelayPath.as b/src/as/com/threerings/flash/path/DelayPath.as new file mode 100644 index 00000000..9a77ac1a --- /dev/null +++ b/src/as/com/threerings/flash/path/DelayPath.as @@ -0,0 +1,42 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.flash.path { + +/** + * Doesn't actually move anything, but simply delays in the current position for some period of + * time. This is generally used with a {@link CompositePath}. + */ +public class DelayPath extends Path +{ + public function DelayPath (delay :int) + { + _delay = delay; + } + + override protected function tick (curStamp :int) :int + { + return _delay - (curStamp - _startStamp); + } + + protected var _delay :int; +} +} diff --git a/src/as/com/threerings/flash/path/InterpFunc.as b/src/as/com/threerings/flash/path/InterpFunc.as new file mode 100644 index 00000000..84416820 --- /dev/null +++ b/src/as/com/threerings/flash/path/InterpFunc.as @@ -0,0 +1,34 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.flash.path { + +/** + * Used by paths to interpolate between two values. + */ +public /*abstract*/ class InterpFunc +{ + public /*abstract*/ function getValue (complete :Number) :int + { + throw new Error("abstract"); + } +} +} diff --git a/src/as/com/threerings/flash/path/LinePath.as b/src/as/com/threerings/flash/path/LinePath.as new file mode 100644 index 00000000..2e187434 --- /dev/null +++ b/src/as/com/threerings/flash/path/LinePath.as @@ -0,0 +1,36 @@ +// +// $Id$ + +package com.threerings.flash.path { + +import flash.display.DisplayObject; + +/** + * Moves a display object along a straight line path in a specified amount of time. + */ +public class LinePath extends Path +{ + public function LinePath (target :DisplayObject, xfunc :InterpFunc, yfunc :InterpFunc, + duration :int) + { + _xfunc = xfunc; + _yfunc = yfunc; + _duration = duration; + init(target); + } + + override protected function tick (curStamp :int) :int + { + var complete :Number = (curStamp - _startStamp) / _duration; + _target.x = _xfunc.getValue(complete); + _target.y = _yfunc.getValue(complete); + + // return the number of milliseconds before we're done + return _duration - (curStamp - _startStamp); + } + + protected var _xfunc :InterpFunc; + protected var _yfunc :InterpFunc; + protected var _duration :int; +} +} diff --git a/src/as/com/threerings/flash/path/LinearFunc.as b/src/as/com/threerings/flash/path/LinearFunc.as new file mode 100644 index 00000000..c31fb3dd --- /dev/null +++ b/src/as/com/threerings/flash/path/LinearFunc.as @@ -0,0 +1,50 @@ +// +// $Id$ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.flash.path { + +/** + * Interpolates linearly between two values. + */ +public class LinearFunc extends InterpFunc +{ + public function LinearFunc (start :int, end :int) + { + _start = start; + _end = end; + } + + // from InterpFunc + override public function getValue (complete :Number) :int + { + if (complete >= 1) { + return _end; + } else if (complete < 0) { // cope with a funny startOffset + return _start; + } else { + return int((_end - _start) * complete) + _start; + } + } + + protected var _start :int; + protected var _end :int; +} +} diff --git a/src/as/com/threerings/flash/path/Path.as b/src/as/com/threerings/flash/path/Path.as new file mode 100644 index 00000000..b0e8651b --- /dev/null +++ b/src/as/com/threerings/flash/path/Path.as @@ -0,0 +1,178 @@ +// +// $Id$ + +package com.threerings.flash.path { + +import flash.display.DisplayObject; +import flash.events.Event; +import flash.utils.getTimer; + +/** + * Moves a display object along a particular path in a specified amount of time. + */ +public /*abstract*/ class Path +{ + /** + * Moves the specified display object from the specified starting coordinates to the specified + * ending coordinates in the specified number of milliseconds. + */ + public static function move (target :DisplayObject, startx :int, starty :int, + destx :int, desty :int, duration :int) :Path + { + return new LinePath(target, new LinearFunc(startx, destx), + new LinearFunc(starty, desty), duration); + } + + /** + * Moves the specified display object from its current location to the specified ending + * coordinates in the specified number of milliseconds. NOTE: beware the fact that + * Flash does not immediately apply a display object's location update, so setting x and y and + * then calling moveTo() will not work. Use {@link #move} instead. + */ + public static function moveTo (target :DisplayObject, destx :int, desty :int, + duration :int) :Path + { + return move(target, target.x, target.y, destx, desty, duration); + } + + /** + * Creates a delay path of the desired duration. For use with CompositePath. + */ + public static function delay (delay :int) :Path + { + return new DelayPath(delay); + } + + /** + * Creates a path that executes the supplied sequence of paths one after the other. + */ + public static function connect (... paths) :Path + { + return new CompositePath(paths); + } + + /** + * Starts this path. The path will be ticked once immediately and subsequently ticked every + * frame. + * + * @param onComplete an optional function to be called when it completes (or is aborted). The + * function should have the following signature: function onComplete (path :Path) :void + * @param startOffset an optional number of milliseconds by which to adjust the time at which + * the path believes that it was started. + */ + public function start (onComplete :Function = null, startOffset :int = 0) :void + { + _onComplete = onComplete; + _target.addEventListener(Event.ENTER_FRAME, onEnterFrame); + willStart(getTimer(), startOffset); + } + + /** + * Configures this path's onComplete function. This should generally only be called shortly + * after construction as the function will not be called if the path is already completed or + * aborted. + */ + public function setOnComplete (onComplete :Function) :void + { + _onComplete = onComplete; + } + + /** + * Aborts this path. Any onComplete() function will be called as if the path terminated + * normally. The callback can call {@link #wasAborted} to discover whether the path was aborted + * or terminated normally. + */ + public function abort () :void + { + pathCompleted(true); + } + + /** + * Returns the target of this path. + */ + public function get target () :DisplayObject + { + return _target; + } + + /** + * Returns true if this path was aborted, false if it completed normally. This return value is + * only valid during a call to onComplete(). + */ + public function wasAborted () :Boolean + { + return _wasAborted; + } + + /** + * Derived classes must call this method to wire this path up to its target. + */ + protected function init (target :DisplayObject) :void + { + _target = target; + } + + /** + * Called when this path is about to start, either due to a call to {@link #start} or to the + * path being started by a {@link CompositePath}. + */ + protected function willStart (now :int, startOffset :int = 0) :int + { + _startStamp = now + startOffset; + return tick(now); + } + + /** + * Derived classes should override this method and update their target based on the current + * timestamp. They should return a positive number, indicating the number of milliseconds they + * have remaining before they are complete, or zero if they completed perfectly on time, or a + * negative number if they completed with milliseconds to spare since their last tick. + */ + protected function tick (curStamp :int) :int + { + return 0; + } + + /** + * Called when this path is has completed or was aborted. + */ + protected function didComplete (aborted :Boolean) :void + { + _wasAborted = aborted; + if (_onComplete != null) { + _onComplete(this); + } + } + + protected function onEnterFrame (event :Event) :void + { + if (tick(getTimer()) <= 0) { + pathCompleted(false); + } + } + + protected function pathCompleted (aborted :Boolean) :void + { + _target.removeEventListener(Event.ENTER_FRAME, onEnterFrame); + didComplete(aborted); + } + + // needed by CompositePath + protected static function tickPath (path :Path, curStamp :int) :int + { + return (path == null) ? 0 : path.tick(curStamp); + } + + // needed by CompositePath + protected static function startPath (path :Path, curStamp :int, startOffset :int) :int + { + return (path == null) ? 0 : path.willStart(curStamp, startOffset); + } + + protected var _target :DisplayObject; + protected var _onComplete :Function; + protected var _startStamp :int = -1; + protected var _wasAborted :Boolean; +} + +}