Modified animation manager to provide a timestamp with every tick.

Modified sprite and path code to make use of these timestamps to determine
a sprite's progress along the path. Abstracted the path stuff so that
paths other than line segment paths can be cleanly implemented.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@337 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-09-13 19:10:26 +00:00
parent 2fc86f89b3
commit 780e39d4e9
11 changed files with 410 additions and 321 deletions
@@ -1,5 +1,5 @@
// //
// $Id: CharacterSprite.java,v 1.8 2001/08/21 19:40:30 mdb Exp $ // $Id: CharacterSprite.java,v 1.9 2001/09/13 19:10:26 mdb Exp $
package com.threerings.miso.scene; package com.threerings.miso.scene;
@@ -18,7 +18,7 @@ public class AmbulatorySprite extends Sprite implements Traverser
/** /**
* Construct an <code>AmbulatorySprite</code>, with a multi-frame * Construct an <code>AmbulatorySprite</code>, with a multi-frame
* image associated with each of the eight compass directions. The * image associated with each of the eight compass directions. The
* array should be in the order defined by the <code>Path</code> * array should be in the order defined by the <code>Sprite</code>
* direction constants (SW, W, NW, N, NE, E, SE, S). * direction constants (SW, W, NW, N, NE, E, SE, S).
* *
* @param x the sprite x-position in pixels. * @param x the sprite x-position in pixels.
@@ -30,43 +30,22 @@ public class AmbulatorySprite extends Sprite implements Traverser
{ {
super(x, y); super(x, y);
// keep track of these
_anims = anims; _anims = anims;
_dir = Path.DIR_SOUTH;
setFrames(_anims[Path.DIR_NORTH]); // give ourselves an initial orientation
setOrientation(DIR_NORTH);
// we only animate when we're moving
setAnimationMode(MOVEMENT_CUED);
} }
/** public void setOrientation (int orient)
* Alter the sprite's direction to reflect the direction the
* destination point lies in before calling the superclass's
* <code>setDestination</code> method.
*
* @param x the destination x-position.
* @param y the destination y-position.
*/
protected void moveAlongPath ()
{ {
// select the new path node super.setOrientation(orient);
super.moveAlongPath();
// bail if we're at the end of the path
if (_dest == null) {
return;
}
// update the sprite frames to reflect the direction // update the sprite frames to reflect the direction
setFrames(_anims[_dir = _dest.dir]); setFrames(_anims[_orient]);
// start tile animation to show movement
setAnimationDelay(0);
}
public void stop ()
{
super.stop();
// stop any walking animation
setAnimationDelay(ANIM_NONE);
} }
public boolean canTraverse (Tile tile) public boolean canTraverse (Tile tile)
@@ -77,7 +56,4 @@ public class AmbulatorySprite extends Sprite implements Traverser
/** The animation frames for the sprite facing each direction. */ /** The animation frames for the sprite facing each direction. */
protected MultiFrameImage[] _anims; protected MultiFrameImage[] _anims;
/** The direction the sprite is currently facing. */
protected int _dir;
} }
@@ -1,5 +1,5 @@
// //
// $Id: AnimationManager.java,v 1.15 2001/08/23 00:23:58 shaper Exp $ // $Id: AnimationManager.java,v 1.16 2001/09/13 19:10:26 mdb Exp $
package com.threerings.media.sprite; package com.threerings.media.sprite;
@@ -110,8 +110,11 @@ public class AnimationManager
*/ */
protected void tick () protected void tick ()
{ {
// every tick should have a timestamp associated with it
long now = System.currentTimeMillis();
// call tick on all sprites // call tick on all sprites
_spritemgr.tick(); _spritemgr.tick(now);
// invalidate screen-rects dirtied by sprites // invalidate screen-rects dirtied by sprites
DirtyRectList rects = _spritemgr.getDirtyRects(); DirtyRectList rects = _spritemgr.getDirtyRects();
+146 -209
View File
@@ -1,5 +1,5 @@
// //
// $Id: Sprite.java,v 1.19 2001/09/07 23:01:53 shaper Exp $ // $Id: Sprite.java,v 1.20 2001/09/13 19:10:26 mdb Exp $
package com.threerings.media.sprite; package com.threerings.media.sprite;
@@ -8,7 +8,6 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.util.MathUtil;
/** /**
* The sprite class represents a single moveable object in an animated * The sprite class represents a single moveable object in an animated
@@ -17,6 +16,38 @@ import com.threerings.media.util.MathUtil;
*/ */
public class Sprite public class Sprite
{ {
/** The number of distinct directions. */
public static final int NUM_DIRECTIONS = 8;
/** Direction constants. */
public static final int DIR_NONE = -1;
public static final int DIR_SOUTHWEST = 0;
public static final int DIR_WEST = 1;
public static final int DIR_NORTHWEST = 2;
public static final int DIR_NORTH = 3;
public static final int DIR_NORTHEAST = 4;
public static final int DIR_EAST = 5;
public static final int DIR_SOUTHEAST = 6;
public static final int DIR_SOUTH = 7;
/** String translations for the direction constants. */
public static String[] XLATE_DIRS = {
"Southwest", "West", "Northwest", "North", "Northeast",
"East", "Southeast", "South"
};
/** Default frame rate. */
public static final int DEFAULT_FRAME_RATE = 15;
/** Animation mode indicating no animation. */
public static final int NO_ANIMATION = 0;
/** Animation mode indicating movement cued animation. */
public static final int MOVEMENT_CUED = 1;
/** Animation mode indicating time based animation. */
public static final int TIME_BASED = 2;
/** /**
* Construct a sprite object. * Construct a sprite object.
* *
@@ -82,15 +113,6 @@ public class Sprite
return _rbounds; return _rbounds;
} }
/**
* Return whether the sprite is currently moving (following an
* assigned path.)
*/
public boolean isMoving ()
{
return (_dest != null);
}
/** /**
* Moves the sprite to the specified location. The location specified * Moves the sprite to the specified location. The location specified
* will be used as the center of the bottom edge of the sprite. * will be used as the center of the bottom edge of the sprite.
@@ -112,6 +134,18 @@ public class Sprite
invalidate(); invalidate();
} }
/**
* Sprites have an orientation in one of the eight cardinal
* directions: <code>DIR_NORTH</code>, <code>DIR_NORTHEAST</code>,
* etc. Sprite derived classes can choose to override this member
* function and select a different set of images based on their
* orientation, or they can ignore the orientation information.
*/
public void setOrientation (int orient)
{
_orient = orient;
}
/** /**
* Initialize the sprite object with its variegated parameters. * Initialize the sprite object with its variegated parameters.
*/ */
@@ -122,15 +156,10 @@ public class Sprite
updateRenderOrigin(); updateRenderOrigin();
// set default velocity and units
_vel = new Point(1, 1);
_velUnit = new Point(1, 1);
updateVelocity();
// initialize frame animation member data // initialize frame animation member data
_frameIdx = 0; _frameIdx = 0;
_animDelay = ANIM_NONE; _animMode = NO_ANIMATION;
_numTicks = 0; _frameDelay = 1000L/DEFAULT_FRAME_RATE;
setFrames(frames); setFrames(frames);
invalidate(); invalidate();
@@ -164,17 +193,9 @@ public class Sprite
*/ */
public void paintPath (Graphics2D gfx) public void paintPath (Graphics2D gfx)
{ {
if (_fullpath == null) return; if (_path != null) {
_path.paint(gfx);
gfx.setColor(Color.red); }
Point prev = null;
int size = _fullpath.size();
for (int ii = 0; ii < size; ii++) {
PathNode n = (PathNode)_fullpath.getNode(ii);
if (prev == null) prev = n.loc;
gfx.drawLine(prev.x, prev.y, n.loc.x, n.loc.y);
prev = n.loc;
}
} }
/** /**
@@ -204,14 +225,33 @@ public class Sprite
} }
/** /**
* Set the number of ticks to wait before switching to the next image * Sets the animation mode for this sprite. The available modes are:
* in the array of images used to display the sprite.
* *
* @param ticks the number of ticks. * <ul>
* <li><code>TIME_BASED</code>: cues the animation based on a target
* frame rate (specified via {@link #setFrameRate}).
* <li><code>MOVEMENT_CUED</code>: ticks the animation to the next
* frame every time the sprite is moved along its path.
* <li><code>NO_ANIMATION</code>: disables animation.
* </ul>
*
* @param mode the desired animation mode.
*/ */
public void setAnimationDelay (int ticks) public void setAnimationMode (int mode)
{ {
_animDelay = ticks; _animMode = mode;
}
/**
* Sets the number of frames per second desired for the sprite
* animation. This is only used when the animation mode is
* <code>TIME_BASED</code>.
*
* @param fps the desired frames per second.
*/
public void setFrameRate (int fps)
{
_frameDelay = 1000L/fps;
} }
/** /**
@@ -228,6 +268,7 @@ public class Sprite
} }
_frames = frames; _frames = frames;
_frameIdx %= _frames.getFrameCount();
_frame = _frames.getFrame(_frameIdx); _frame = _frames.getFrame(_frameIdx);
// determine our drawing offsets and rendered rectangle size // determine our drawing offsets and rendered rectangle size
@@ -248,53 +289,6 @@ public class Sprite
invalidate(); invalidate();
} }
/**
* Set the sprite's velocity when following a path.
*
* @param vx the x-axis velocity.
* @param vy the y-axis velocity.
*/
public void setVelocity (int vx, int vy)
{
_vel.setLocation(vx, vy);
updateVelocity();
}
/**
* Set the units upon which the sprite's velocity is based.
*
* @param ux the x-axis units.
* @param uy the y-axis units.
*/
public void setVelocityUnits (int ux, int uy)
{
_velUnit.setLocation(ux, uy);
updateVelocity();
}
/**
* Update the distance the sprite moves each tick, based on the
* sprite velocity and the velocity units.
*/
protected void updateVelocity ()
{
_fracx = (_vel.x / (float)_velUnit.x);
_fracy = (_vel.y / (float)_velUnit.y);
}
/**
* Stop the sprite from any movement along a path it may be
* engaged in.
*/
public void stop ()
{
// TODO: make sure we come to a stop on a full coordinate,
// even in the case where we aborted a path mid-traversal.
_dest = null;
_path = null;
_fullpath = null;
}
/** /**
* Set the sprite's active path and start moving it along its * Set the sprite's active path and start moving it along its
* merry way. If the sprite is already moving along a previous * merry way. If the sprite is already moving along a previous
@@ -305,64 +299,34 @@ public class Sprite
*/ */
public void move (Path path) public void move (Path path)
{ {
// make sure following the path is a sensible thing to do // save our path
if (path == null || path.size() < 2) { _path = path;
// halt any previously existing movement since, regardless
// of its reasonableness, we've been asked to follow a new
// path
stop();
return;
}
// save an enumeration of the path nodes // and initialize it
_path = path.elements(); _path.init(this, System.currentTimeMillis());
// and the full path for potential rendering
_fullpath = path;
// skip the first node since it's our starting position.
// perhaps someday we'll do something with this.
_path.next();
// start our meandering
moveAlongPath();
} }
/** /**
* Start the sprite moving toward the next node in its path. * Cancels any path that the sprite may currently be moving along.
*/ */
protected void moveAlongPath () public void cancelMove ()
{ {
if (!_path.hasNext()) { // TODO: make sure we come to a stop on a full coordinate,
// inform observers that we've finished our path // even in the case where we aborted a path mid-traversal.
notifyObservers(SpriteEvent.FINISHED_PATH, _path);
// clear out our path and bail
stop();
return;
}
if (_dest != null) { _path = null;
// inform observers that we've finished a path node }
notifyObservers(SpriteEvent.FINISHED_PATH_NODE, _dest);
}
// grab the next node in our path /**
_dest = (PathNode)_path.next(); * Called by the active path when it has completed.
*/
protected void pathCompleted ()
{
// inform observers that we've finished our path
notifyObservers(SpriteEvent.FINISHED_PATH, _path);
// if we're already here, move on to the next node // we no longer want to keep a reference to this path
if (_x == _dest.loc.x && _y == _dest.loc.y) { _path = null;
moveAlongPath();
return;
}
// determine the horizontal/vertical move increments
float dist = MathUtil.distance(_x, _y, _dest.loc.x, _dest.loc.y);
_incx = (float)(_dest.loc.x - _x) / (dist / _fracx);
_incy = (float)(_dest.loc.y - _y) / (dist / _fracy);
// init position data used to track fractional pixels
_movex = _x;
_movey = _y;
} }
/** /**
@@ -379,7 +343,9 @@ public class Sprite
*/ */
public void invalidate () public void invalidate ()
{ {
if (_frame == null) return; if (_frame == null) {
return;
}
if (_spritemgr != null) { if (_spritemgr != null) {
_spritemgr.addDirtyRect(getRenderedBounds()); _spritemgr.addDirtyRect(getRenderedBounds());
@@ -391,59 +357,49 @@ public class Sprite
} }
/** /**
* This method is called periodically by the SpriteManager to give * This method is called periodically by the sprite manager to give
* the sprite a chance to update its state. * the sprite a chance to update its state. The sprite manager will
* attempt to call this with the desired refresh rate, but will drop
* calls to tick if it can't keep up. Thus, a sprite should rely on
* the timestamp information to compute elapsed progress if it wishes
* to handle heavy loads gracefully.
*/ */
public void tick () public void tick (long timestamp)
{ {
// increment the display image if performing image animation int fcount = _frames.getFrameCount();
if (_animDelay != ANIM_NONE && (_numTicks++ == _animDelay)) { int nfidx = _frameIdx;
_numTicks = 0; boolean moved = false;
_frameIdx = (_frameIdx + 1) % _frames.getFrameCount();
_frame = _frames.getFrame(_frameIdx);
// move the sprite along toward its destination, if any
if (_path != null) {
moved = _path.updatePosition(this, timestamp);
}
// increment the display image if performing image animation
switch (_animMode) {
case NO_ANIMATION:
// nothing doing
break;
case TIME_BASED:
_frameIdx = (int)((timestamp/_frameDelay) % fcount);
break;
case MOVEMENT_CUED:
// update the frame if the sprite moved
if (moved) {
nfidx = (_frameIdx + 1) % fcount;
}
break;
}
// only update the sprite if our frame index changed
if (nfidx != _frameIdx) {
_frameIdx = nfidx;
_frame = _frames.getFrame(_frameIdx);
// dirty our rectangle since we've altered our display image // dirty our rectangle since we've altered our display image
invalidate(); invalidate();
} }
// move the sprite along toward its destination, if any
if (_dest != null) {
handleMove();
}
}
/**
* Actually move the sprite's position toward its destination one
* display increment.
*/
protected void handleMove ()
{
// dirty our rectangle since we're going to move
invalidate();
// move the sprite incrementally toward its goal
_x = (int)(_movex += _incx);
_y = (int)(_movey += _incy);
// stop moving once we've reached our destination
if (_incx > 0 && _x > _dest.loc.x ||
_incx < 0 && _x < _dest.loc.x ||
_incy > 0 && _y > _dest.loc.y ||
_incy < 0 && _y < _dest.loc.y) {
// make sure we stop exactly where desired
_x = _dest.loc.x;
_y = _dest.loc.y;
// move further along the path if necessary
moveAlongPath();
}
// update the draw coordinates to reflect our new position
updateRenderOrigin();
// dirty our rectangle in the new position
invalidate();
} }
/** /**
@@ -490,7 +446,9 @@ public class Sprite
*/ */
protected void notifyObservers (int eventCode, Object arg) protected void notifyObservers (int eventCode, Object arg)
{ {
if (_observers == null) return; if (_observers == null) {
return;
}
SpriteEvent evt = new SpriteEvent(this, eventCode, arg); SpriteEvent evt = new SpriteEvent(this, eventCode, arg);
_spritemgr.notifySpriteObservers(_observers, evt); _spritemgr.notifySpriteObservers(_observers, evt);
@@ -508,9 +466,6 @@ public class Sprite
return buf.append("]").toString(); return buf.append("]").toString();
} }
/** Value used to denote that no image animation is desired. */
protected static final int ANIM_NONE = -1;
/** The images used to render the sprite. */ /** The images used to render the sprite. */
protected MultiFrameImage _frames; protected MultiFrameImage _frames;
@@ -520,6 +475,9 @@ public class Sprite
/** The current frame index to render. */ /** The current frame index to render. */
protected int _frameIdx; protected int _frameIdx;
/** The orientation of this sprite. */
protected int _orient = DIR_NONE;
/** The location of the sprite in pixel coordinates. */ /** The location of the sprite in pixel coordinates. */
protected int _x, _y; protected int _x, _y;
@@ -529,35 +487,14 @@ public class Sprite
/** Our rendered bounds in pixel coordinates. */ /** Our rendered bounds in pixel coordinates. */
protected Rectangle _rbounds = new Rectangle(); protected Rectangle _rbounds = new Rectangle();
/** The PathNode objects describing the path the sprite is following. */ /** What type of animation is desired for this sprite. */
protected Iterator _path; protected int _animMode;
/** When moving, the sprite's destination path node. */ /** For how many milliseconds to display an animation frame. */
protected PathNode _dest; protected long _frameDelay;
/** When moving, the sprite position including fractional pixels. */ /** When moving, the path the sprite is traversing. */
protected float _movex, _movey; protected Path _path;
/** When moving, the distance to move on each axis per tick. */
protected float _incx, _incy;
/** The distance to move on the straight path line per tick. */
protected float _fracx, _fracy;
/** The number of ticks to wait before rendering with the next image. */
protected int _animDelay;
/** The number of ticks since the last image animation. */
protected int _numTicks;
/** When moving, the full path the sprite is traversing. */
protected Path _fullpath;
/** The sprite velocity when moving. */
protected Point _vel;
/** The sprite velocity units. */
protected Point _velUnit;
/** The sprite observers observing this sprite. */ /** The sprite observers observing this sprite. */
protected ArrayList _observers; protected ArrayList _observers;
@@ -1,5 +1,5 @@
// //
// $Id: SpriteManager.java,v 1.12 2001/09/07 23:01:53 shaper Exp $ // $Id: SpriteManager.java,v 1.13 2001/09/13 19:10:26 mdb Exp $
package com.threerings.media.sprite; package com.threerings.media.sprite;
@@ -154,10 +154,10 @@ public class SpriteManager
* queue by the {@link AnimationManager}. Handles moving about of * queue by the {@link AnimationManager}. Handles moving about of
* sprites and reporting of sprite collisions. * sprites and reporting of sprite collisions.
*/ */
public void tick () public void tick (long timestamp)
{ {
// tick all sprites // tick all sprites
tickSprites(); tickSprites(timestamp);
// re-sort the sprite list to account for potential new positions // re-sort the sprite list to account for potential new positions
sortSprites(); sortSprites();
@@ -179,12 +179,12 @@ public class SpriteManager
* chance to move themselves about, change their display image, * chance to move themselves about, change their display image,
* and so forth. * and so forth.
*/ */
protected void tickSprites () protected void tickSprites (long timestamp)
{ {
int size = _sprites.size(); int size = _sprites.size();
for (int ii = 0; ii < size; ii++) { for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii); Sprite sprite = (Sprite)_sprites.get(ii);
sprite.tick(); sprite.tick(timestamp);
} }
} }
@@ -1,65 +1,50 @@
// //
// $Id: LineSegmentPath.java,v 1.8 2001/09/05 00:40:17 shaper Exp $ // $Id: LineSegmentPath.java,v 1.9 2001/09/13 19:10:26 mdb Exp $
package com.threerings.media.sprite; package com.threerings.media.sprite;
import java.awt.Color;
import java.awt.Point;
import java.awt.Graphics2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.media.util.MathUtil;
/** /**
* The <code>Path</code> class represents the path a sprite follows * The <code>LineSegmentPath</code> class is used to cause a sprite to
* while meandering about the screen. There must be at least two * follow a path that is made up of a sequence of line segments. There
* nodes in any worthwhile path. The direction of the first node in * must be at least two nodes in any worthwhile path. The direction of the
* the path is meaningless since the sprite begins at that node and * first node in the path is meaningless since the sprite begins at that
* will therefore never be heading towards it. * node and will therefore never be heading towards it.
*/ */
public class Path public class LineSegmentPath implements Path
{ {
/** The number of distinct directions. */
public static final int NUM_DIRECTIONS = 8;
/** Direction constants. */
public static final int DIR_NONE = -1;
public static final int DIR_SOUTHWEST = 0;
public static final int DIR_WEST = 1;
public static final int DIR_NORTHWEST = 2;
public static final int DIR_NORTH = 3;
public static final int DIR_NORTHEAST = 4;
public static final int DIR_EAST = 5;
public static final int DIR_SOUTHEAST = 6;
public static final int DIR_SOUTH = 7;
/** String translations for the direction constants. */
public static String[] XLATE_DIRS = {
"Southwest", "West", "Northwest", "North", "Northeast",
"East", "Southeast", "South"
};
/** /**
* Construct a <code>Path</code> object. * Construct a <code>LineSegmentPath</code> object.
*/ */
public Path () public LineSegmentPath ()
{ {
_nodes = new ArrayList(); _nodes = new ArrayList();
} }
/** /**
* Construct a <code>Path</code> object with the specified * Construct a <code>LineSegmentPath</code> object with the specified
* starting node coordinates. An arbitrary direction will be * starting node coordinates. An arbitrary direction will be assigned
* assigned to the starting node. * to the starting node.
* *
* @param x the starting node x-position. * @param x the starting node x-position.
* @param y the starting node y-position. * @param y the starting node y-position.
*/ */
public Path (int x, int y) public LineSegmentPath (int x, int y)
{ {
_nodes = new ArrayList(); _nodes = new ArrayList();
// add the starting node with an arbitrarily chosen direction // add the starting node with an arbitrarily chosen direction
// since direction is meaningless here // since direction is meaningless here
addNode(x, y, DIR_NORTH); addNode(x, y, Sprite.DIR_NORTH);
} }
/** /**
@@ -76,15 +61,15 @@ public class Path
} }
/** /**
* Add a node to the path with the specified destination point. * Add a node to the path with the specified destination point. An
* An arbitrary direction will be assigned to the node. * arbitrary direction will be assigned to the node.
* *
* @param x the x-position. * @param x the x-position.
* @param y the y-position. * @param y the y-position.
*/ */
public void addNode (int x, int y) public void addNode (int x, int y)
{ {
_nodes.add(new PathNode(x, y, Path.DIR_NORTH)); _nodes.add(new PathNode(x, y, Sprite.DIR_NORTH));
} }
/** /**
@@ -100,14 +85,6 @@ public class Path
return (PathNode)_nodes.get(idx); return (PathNode)_nodes.get(idx);
} }
/**
* Return an enumeration of the PathNode objects in this path.
*/
public Iterator elements ()
{
return _nodes.iterator();
}
/** /**
* Return the number of nodes in the path. * Return the number of nodes in the path.
*/ */
@@ -116,6 +93,127 @@ public class Path
return _nodes.size(); return _nodes.size();
} }
/**
* Sets the velocity of this sprite in pixels per millisecond. The
* velocity is measured as pixels traversed along the path that the
* sprite is traveling rather than in the x or y directions
* individually.
*
* @param velocity the sprite velocity in pixels per millisecond.
*/
public void setVelocity (float velocity)
{
_vel = velocity;
}
// documentation inherited
public void init (Sprite sprite, long timestamp)
{
// if we have only one node then let the sprite know that we're
// done straight away
if (size() < 2) {
// move the sprite to the location specified by the first node
// (assuming we have a first node)
if (size() == 1) {
PathNode node = (PathNode)_nodes.get(0);
sprite.setLocation(node.loc.x, node.loc.y);
}
// and let the sprite know that we're done
sprite.pathCompleted();
return;
}
// and an enumeration of the path nodes
_niter = _nodes.iterator();
// pretend like we were previously heading to our starting position
_dest = (PathNode)_niter.next();
// begin traversing the path
headToNextNode(sprite, timestamp, timestamp);
}
// documentation inherited
public boolean updatePosition (Sprite sprite, long timestamp)
{
// figure out how far along this segment we should be
long msecs = timestamp - _nodestamp;
float travpix = msecs * _vel;
float pctdone = travpix / _seglength;
// if we've moved beyond the end of the path, we need to adjust
// the timestamp to determine how much time we used getting to the
// end of this node, then move to the next one
if (pctdone >= 1.0) {
long used = (long)(_seglength / _vel);
return headToNextNode(sprite, _nodestamp + used, timestamp);
}
// otherwise we position the sprite along the path
int ox = sprite.getX();
int oy = sprite.getY();
int nx = _src.loc.x + (int)((_dest.loc.x - _src.loc.x) * pctdone);
int ny = _src.loc.y + (int)((_dest.loc.y - _src.loc.y) * pctdone);
// only update the sprite's location if it actually moved
if (ox != nx || oy != ny) {
sprite.setLocation(nx, ny);
return true;
}
return false;
}
protected boolean headToNextNode (Sprite sprite, long startstamp, long now)
{
// check to see if we've completed our path
if (!_niter.hasNext()) {
// move the sprite to the location of our last destination
sprite.setLocation(_dest.loc.x, _dest.loc.y);
sprite.pathCompleted();
return true;
}
// our previous destination is now our source
_src = _dest;
// pop the next node off the path
_dest = (PathNode)_niter.next();
// adjust the sprite's orientation
sprite.setOrientation(_dest.dir);
// make a note of when we started traversing this node
_nodestamp = startstamp;
// figure out the distance from source to destination
_seglength = MathUtil.distance(_src.loc.x, _src.loc.y,
_dest.loc.x, _dest.loc.y);
// if we're already there (the segment length is zero), we skip to
// the next segment
if (_seglength == 0) {
return headToNextNode(sprite, startstamp, now);
}
// now update the sprite's position based on our progress thus far
return updatePosition(sprite, now);
}
public void paint (Graphics2D gfx)
{
gfx.setColor(Color.red);
Point prev = null;
int size = size();
for (int ii = 0; ii < size; ii++) {
PathNode n = (PathNode)getNode(ii);
if (prev != null) {
gfx.drawLine(prev.x, prev.y, n.loc.x, n.loc.y);
}
prev = n.loc;
}
}
public String toString () public String toString ()
{ {
return StringUtil.toString(_nodes.iterator()); return StringUtil.toString(_nodes.iterator());
@@ -123,4 +221,34 @@ public class Path
/** The nodes that make up the path. */ /** The nodes that make up the path. */
protected ArrayList _nodes; protected ArrayList _nodes;
/** We use this when moving along this path. */
protected Iterator _niter;
/** When moving, the sprite's source path node. */
protected PathNode _src;
/** When moving, the sprite's destination path node. */
protected PathNode _dest;
/** The time at which we started traversing the current node. */
protected long _nodestamp;
/** The length in pixels of the current path segment. */
protected float _seglength;
/** The path velocity in pixels per millisecond. */
protected float _vel = DEFAULT_VELOCITY;
/** When moving, the sprite position including fractional pixels. */
protected float _movex, _movey;
/** When moving, the distance to move on each axis per tick. */
protected float _incx, _incy;
/** The distance to move on the straight path line per tick. */
protected float _fracx, _fracy;
/** Default sprite velocity. */
protected static final float DEFAULT_VELOCITY = 200f/1000f;
} }
@@ -0,0 +1,45 @@
//
// $Id: Path.java,v 1.1 2001/09/13 19:10:26 mdb Exp $
package com.threerings.media.sprite;
import java.awt.Graphics2D;
/**
* A path is used to cause a sprite to follow a particular path along the
* screen. The sprite will call into the path every time it is ticked by
* the animation manager and the path is responsible for updating the
* position of the sprite based on the time that has elapsed since the
* sprite started down the path. The animation manager attempts to tick
* once per frame at the desired frame rate, but may tick less often if
* CPU resources are limited, thus paths should not rely on tick counts
* but instead use elapsed time to determine progress.
*
* <p> The path should call back to the sprite (via {@link
* Sprite#pathCompleted}) to let it know when the path has been completed.
*/
public interface Path
{
/**
* Called once to let the path prepare itself for the process of
* animating the supplied sprite.
*/
public void init (Sprite sprite, long timestamp);
/**
* Called to request that this path update the position of the
* specified sprite based on the supplied timestamp information. A
* path should record its initial timestamp and determine the progress
* of the sprite along the path based on the time elapsed since the
* sprite began down the path.
*
* @return true if the sprite's position was updated, false if the
* path determined that the sprite should not move at this time.
*/
public boolean updatePosition (Sprite sprite, long timestamp);
/**
* Paint this path on the screen (used for debugging purposes only).
*/
public void paint (Graphics2D gfx);
}
@@ -1,5 +1,5 @@
// //
// $Id: IsoSceneView.java,v 1.53 2001/08/29 19:50:46 shaper Exp $ // $Id: IsoSceneView.java,v 1.54 2001/09/13 19:10:26 mdb Exp $
package com.threerings.miso.scene; package com.threerings.miso.scene;
@@ -618,7 +618,8 @@ public class IsoSceneView implements EditableSceneView
// to second tile, and penultimate to ultimate tile. // to second tile, and penultimate to ultimate tile.
// construct path with starting screen position // construct path with starting screen position
Path path = new Path(sprite.getX(), sprite.getY()); LineSegmentPath path =
new LineSegmentPath(sprite.getX(), sprite.getY());
// add all nodes on the calculated path // add all nodes on the calculated path
Point nspos = new Point(); Point nspos = new Point();
@@ -1,9 +1,9 @@
// //
// $Id: Location.java,v 1.5 2001/08/14 23:35:22 mdb Exp $ // $Id: Location.java,v 1.6 2001/09/13 19:10:26 mdb Exp $
package com.threerings.miso.scene; package com.threerings.miso.scene;
import com.threerings.media.sprite.Path; import com.threerings.media.sprite.Sprite;
/** /**
* The <code>Location</code> class represents a unique well-defined * The <code>Location</code> class represents a unique well-defined
@@ -45,7 +45,7 @@ public class Location
{ {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.orient = Path.DIR_SOUTHWEST; this.orient = Sprite.DIR_SOUTHWEST;
} }
/** /**
@@ -1,12 +1,12 @@
// //
// $Id: IsoUtil.java,v 1.5 2001/08/15 22:16:43 shaper Exp $ // $Id: IsoUtil.java,v 1.6 2001/09/13 19:10:26 mdb Exp $
package com.threerings.miso.scene.util; package com.threerings.miso.scene.util;
import java.awt.Point; import java.awt.Point;
import java.awt.Polygon; import java.awt.Polygon;
import com.threerings.media.sprite.Path; import com.threerings.media.sprite.Sprite;
import com.threerings.media.util.MathUtil; import com.threerings.media.util.MathUtil;
import com.threerings.miso.Log; import com.threerings.miso.Log;
@@ -28,7 +28,7 @@ public class IsoUtil
* @param bx the x-position of point B. * @param bx the x-position of point B.
* @param by the y-position of point B. * @param by the y-position of point B.
* *
* @return the direction specified as one of the <code>Path</code> * @return the direction specified as one of the <code>Sprite</code>
* class's direction constants. * class's direction constants.
*/ */
public static int getDirection ( public static int getDirection (
@@ -50,7 +50,7 @@ public class IsoUtil
// compare tile coordinates to determine direction // compare tile coordinates to determine direction
int dir = getIsoDirection(tax, tay, tbx, tby); int dir = getIsoDirection(tax, tay, tbx, tby);
if (dir != Path.DIR_NONE) return dir; if (dir != Sprite.DIR_NONE) return dir;
// destination point is in the same tile as the // destination point is in the same tile as the
// origination point, so consider fine coordinates // origination point, so consider fine coordinates
@@ -66,7 +66,7 @@ public class IsoUtil
dir = getIsoDirection(fax, fay, fbx, fby); dir = getIsoDirection(fax, fay, fbx, fby);
// arbitrarily return southwest if fine coords were also equivalent // arbitrarily return southwest if fine coords were also equivalent
return (dir == -1) ? Path.DIR_SOUTHWEST : dir; return (dir == -1) ? Sprite.DIR_SOUTHWEST : dir;
} }
/** /**
@@ -81,23 +81,23 @@ public class IsoUtil
* @param bx the x-position of point B. * @param bx the x-position of point B.
* @param by the y-position of point B. * @param by the y-position of point B.
* *
* @return the direction specified as one of the <code>Path</code> * @return the direction specified as one of the <code>Sprite</code>
* class's direction constants, or <code>Path.DIR_NONE</code> * class's direction constants, or <code>Sprite.DIR_NONE</code>
* if point B is equivalent to point A. * if point B is equivalent to point A.
*/ */
public static int getIsoDirection (int ax, int ay, int bx, int by) public static int getIsoDirection (int ax, int ay, int bx, int by)
{ {
if (bx > ax) { if (bx > ax) {
if (by == ay) return Path.DIR_SOUTH; if (by == ay) return Sprite.DIR_SOUTH;
return (by < ay) ? Path.DIR_SOUTHEAST : Path.DIR_SOUTHWEST; return (by < ay) ? Sprite.DIR_SOUTHEAST : Sprite.DIR_SOUTHWEST;
} else if (bx == ax) { } else if (bx == ax) {
if (by == ay) return Path.DIR_NONE; if (by == ay) return Sprite.DIR_NONE;
return (by < ay) ? Path.DIR_EAST : Path.DIR_WEST; return (by < ay) ? Sprite.DIR_EAST : Sprite.DIR_WEST;
} else { // bx < ax } else { // bx < ax
if (by == ay) return Path.DIR_NORTH; if (by == ay) return Sprite.DIR_NORTH;
return (by < ay) ? Path.DIR_NORTHEAST : Path.DIR_NORTHWEST; return (by < ay) ? Sprite.DIR_NORTHEAST : Sprite.DIR_NORTHWEST;
} }
} }
@@ -1,12 +1,12 @@
// //
// $Id: TileUtil.java,v 1.4 2001/09/05 00:44:37 shaper Exp $ // $Id: TileUtil.java,v 1.5 2001/09/13 19:10:26 mdb Exp $
package com.threerings.miso.tile; package com.threerings.miso.tile;
import java.awt.Image; import java.awt.Image;
import com.threerings.media.sprite.MultiFrameImage; import com.threerings.media.sprite.MultiFrameImage;
import com.threerings.media.sprite.Path; import com.threerings.media.sprite.Sprite;
import com.threerings.media.tile.*; import com.threerings.media.tile.*;
import com.threerings.miso.scene.AmbulatorySprite; import com.threerings.miso.scene.AmbulatorySprite;
@@ -20,7 +20,7 @@ public class TileUtil
* Returns an array of multi-frame images corresponding to the * Returns an array of multi-frame images corresponding to the
* frames of animation used to render the ambulatory sprite in * frames of animation used to render the ambulatory sprite in
* each of the directions it may face. The tileset id referenced * each of the directions it may face. The tileset id referenced
* must contain <code>Path.NUM_DIRECTIONS</code> rows of tiles, * must contain <code>Sprite.NUM_DIRECTIONS</code> rows of tiles,
* with each row containing <code>NUM_DIR_FRAMES</code> tiles. * with each row containing <code>NUM_DIR_FRAMES</code> tiles.
* *
* @param tilemgr the tile manager to retrieve tiles from. * @param tilemgr the tile manager to retrieve tiles from.
@@ -31,9 +31,9 @@ public class TileUtil
public static MultiFrameImage[] getAmbulatorySpriteFrames ( public static MultiFrameImage[] getAmbulatorySpriteFrames (
TileManager tilemgr, int tsid) TileManager tilemgr, int tsid)
{ {
MultiFrameImage[] anims = new MultiFrameImage[Path.NUM_DIRECTIONS]; MultiFrameImage[] anims = new MultiFrameImage[Sprite.NUM_DIRECTIONS];
for (int ii = 0; ii < Path.NUM_DIRECTIONS; ii++) { for (int ii = 0; ii < Sprite.NUM_DIRECTIONS; ii++) {
Tile[] tiles = new Tile[NUM_DIR_FRAMES]; Tile[] tiles = new Tile[NUM_DIR_FRAMES];
for (int jj = 0; jj < NUM_DIR_FRAMES; jj++) { for (int jj = 0; jj < NUM_DIR_FRAMES; jj++) {
int idx = (ii * NUM_DIR_FRAMES) + jj; int idx = (ii * NUM_DIR_FRAMES) + jj;
@@ -1,5 +1,5 @@
// //
// $Id: ViewerFrame.java,v 1.17 2001/09/05 00:45:27 shaper Exp $ // $Id: ViewerFrame.java,v 1.18 2001/09/13 19:10:26 mdb Exp $
package com.threerings.miso.viewer; package com.threerings.miso.viewer;
@@ -46,7 +46,6 @@ class ViewerFrame extends JFrame implements WindowListener
MultiFrameImage[] anims = MultiFrameImage[] anims =
TileUtil.getAmbulatorySpriteFrames(tilemgr, TSID_CHAR); TileUtil.getAmbulatorySpriteFrames(tilemgr, TSID_CHAR);
AmbulatorySprite sprite = new AmbulatorySprite(300, 300, anims); AmbulatorySprite sprite = new AmbulatorySprite(300, 300, anims);
sprite.setVelocity(6, 6);
spritemgr.addSprite(sprite); spritemgr.addSprite(sprite);
// create a top-level panel to manage everything // create a top-level panel to manage everything