//
// $Id: Sprite.java,v 1.14 2001/08/21 19:40:30 mdb Exp $
package com.threerings.media.sprite;
import java.awt.*;
import java.util.Enumeration;
import com.threerings.media.Log;
import com.threerings.media.util.MathUtil;
/**
* The Sprite class represents a single moveable object
* within a scene. A sprite has a position within the scene, and a set of
* images used to render it (perhaps multiple frames for animation).
*/
public class Sprite
{
/** The sprite's x-position in pixel coordinates. */
public int x;
/** The sprite's y-position in pixel coordinates. */
public int y;
/**
* Construct a sprite object.
*
* @param x the sprite x-position in pixels.
* @param y the sprite y-position in pixels.
* @param frames the multi-frame image used to display the sprite.
*/
public Sprite (int x, int y, MultiFrameImage frames)
{
init(x, y, frames);
}
/**
* Construct a sprite object without any associated frames. The sprite
* should be populated with a set of frames used to display it via a
* subsequent call to setFrames().
*
* @param x the sprite x-position in pixels.
* @param y the sprite y-position in pixels.
*/
public Sprite (int x, int y)
{
init(x, y, null);
}
/**
* Moves the sprite to the specified location. The location specified
* will be used as the center of the bottom edge of the sprite.
*/
public void setLocation (int x, int y)
{
// invalidate our current position
invalidate();
// move ourselves
this.x = x;
this.y = y;
// we need to update our draw position which is based on the size
// of our current frame
updateDrawPosition();
// invalidate our new position
invalidate();
}
/**
* Initialize the sprite object with its variegated parameters.
*/
protected void init (int x, int y, MultiFrameImage frames)
{
this.x = x;
this.y = y;
updateDrawPosition();
// set default velocity
_vel = new Point(1, 1);
// initialize frame animation member data
_frameIdx = 0;
_animDelay = ANIM_NONE;
_numTicks = 0;
setFrames(frames);
invalidate();
}
/**
* Called by the sprite manager when a sprite is added to said manager
* for management.
*/
protected void setSpriteManager (SpriteManager mgr)
{
_spritemgr = mgr;
}
/**
* Paint the sprite to the specified graphics context.
*
* @param gfx the graphics context.
*/
public void paint (Graphics2D gfx)
{
gfx.drawImage(_frame, _drawx, _drawy, null);
}
/**
* Paint the sprite's path, if any, to the specified graphics context.
*
* @param gfx the graphics context.
*/
public void paintPath (Graphics2D gfx)
{
if (_fullpath == null) return;
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;
}
}
/**
* Returns whether the sprite is inside the given polygon in
* pixel coordinates.
*
* @param bounds the bounding polygon.
*
* @return whether the sprite is inside the polygon.
*/
public boolean inside (Polygon bounds)
{
return bounds.contains(this.x, this.y);
}
/**
* Set the number of ticks to wait before switching to the next image
* in the array of images used to display the sprite.
*
* @param ticks the number of ticks.
*/
public void setAnimationDelay (int ticks)
{
_animDelay = ticks;
}
/**
* Set the image array used to render the sprite.
*
* @param frames the sprite images.
*/
public void setFrames (MultiFrameImage frames)
{
if (frames == null) return;
_frames = frames;
_frame = _frames.getFrame(_frameIdx);
updateDrawPosition();
invalidate();
}
/**
* Set the sprite's velocity when walking.
*
* @param vx the x-axis velocity.
* @param vy the y-axis velocity.
*/
public void setVelocity (int vx, int vy)
{
_vel.setLocation(vx, vy);
}
/**
* 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 walk mid-traversal.
_dest = null;
_path = null;
_fullpath = null;
}
/**
* Set the sprite's active path and start moving it along its
* merry way. If the sprite is already moving along a previous
* path the old path will be lost and the new path will begin to
* be traversed.
*
* @param path the path to follow.
*/
public void move (Path path)
{
// make sure following the path is a sensible thing to do
if (path == null || path.size() < 2) {
// halt movement if we're walking since, regardless of its
// reasonableness, we've been asked to follow a new path
stop();
return;
}
// save an enumeration of the path nodes
_path = path.elements();
// 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.nextElement();
// start our meandering
moveAlongPath();
}
/**
* Start the sprite moving toward the next node in its path.
*/
protected void moveAlongPath ()
{
// grab the next node in our path
_dest = (PathNode)_path.nextElement();
// if no more nodes remain, clear out our path and bail
if (_dest == null) {
stop();
return;
}
// if we're already here, move on to the next node
if (x == _dest.loc.x && y == _dest.loc.y) {
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 / _vel.x);
_incy = (float)(_dest.loc.y - y) / (dist / _vel.y);
// init position data used to track fractional pixels
_movex = x;
_movey = y;
}
/**
* Invalidate the sprite's display rectangle for later repainting.
*/
public void invalidate ()
{
if (_frame == null) return;
if (_spritemgr != null) {
Rectangle dirty = new Rectangle(
_drawx, _drawy,
_frame.getWidth(null), _frame.getHeight(null));
_spritemgr.addDirtyRect(dirty);
} else {
Log.warning("Was invalidated but have no sprite manager " +
this + ".");
}
}
/**
* This method is called periodically by the SpriteManager to give
* the sprite a chance to update its state.
*/
public void tick ()
{
// increment the display image if performing image animation
if (_animDelay != ANIM_NONE && (_numTicks++ == _animDelay)) {
_numTicks = 0;
_frameIdx = (_frameIdx + 1) % _frames.getFrameCount();
_frame = _frames.getFrame(_frameIdx);
// dirty our rectangle since we've altered our display image
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
updateDrawPosition();
// dirty our rectangle in the new position
invalidate();
}
/**
* Update the coordinates at which the sprite image is drawn to
* reflect the sprite's current position.
*/
protected void updateDrawPosition ()
{
if (_frame == null) {
_drawx = x;
_drawy = y;
} else {
_drawx = x - (_frame.getWidth(null) / 2);
_drawy = y - _frame.getHeight(null);
}
}
/**
* Return a string representation of the sprite.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer();
buf.append("[x=").append(x);
buf.append(", y=").append(y);
buf.append(", fidx=").append(_frameIdx);
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. */
protected MultiFrameImage _frames;
/** The current frame being rendered. */
protected Image _frame;
/** The current frame index to render. */
protected int _frameIdx;
/** The coordinates at which the frame image is drawn. */
protected int _drawx, _drawy;
/** The PathNode objects describing the path the sprite is following. */
protected Enumeration _path;
/** When moving, the sprite's destination path node. */
protected PathNode _dest;
/** When moving, the sprite position including fractional pixels. */
protected float _movex, _movey;
/** When moving, the distance to move per tick in fractional pixels. */
protected float _incx, _incy;
/** 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 walking. */
protected Point _vel;
/** The sprite manager. */
protected SpriteManager _spritemgr;
}