Brought my path stuff into general purpose land and improved it in the process.

git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@300 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Michael Bayne
2007-10-09 07:17:01 +00:00
parent 094e6b1730
commit 6f67eac059
6 changed files with 400 additions and 0 deletions
@@ -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;
}
}
@@ -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;
}
}
@@ -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");
}
}
}
@@ -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;
}
}
@@ -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;
}
}
+178
View File
@@ -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. <em>NOTE:</em> 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;
}
}