diff --git a/src/java/com/threerings/media/sprite/Sprite.java b/src/java/com/threerings/media/sprite/Sprite.java
index a37db608b..230ce1fb4 100644
--- a/src/java/com/threerings/media/sprite/Sprite.java
+++ b/src/java/com/threerings/media/sprite/Sprite.java
@@ -1,18 +1,19 @@
//
-// $Id: Sprite.java,v 1.16 2001/08/22 02:14:57 mdb Exp $
+// $Id: Sprite.java,v 1.17 2001/09/05 00:40:33 shaper Exp $
package com.threerings.media.sprite;
import java.awt.*;
+import java.util.ArrayList;
import java.util.Iterator;
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).
+ * The sprite class represents a single moveable object in an animated
+ * view. A sprite has a position within the view, and a set of images
+ * used to render it (perhaps multiple frames for animation).
*/
public class Sprite
{
@@ -31,7 +32,7 @@ public class Sprite
/**
* 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().
+ * subsequent call to {@link #setFrames}.
*
* @param x the sprite x-position in pixels.
* @param y the sprite y-position in pixels.
@@ -57,9 +58,45 @@ public class Sprite
return _y;
}
+ /**
+ * Returns the sprite's width in pixels.
+ */
+ public int getWidth ()
+ {
+ return _rbounds.width;
+ }
+
+ /**
+ * Returns the sprite's height in pixels.
+ */
+ public int getHeight ()
+ {
+ return _rbounds.height;
+ }
+
+ /**
+ * Returns the sprite's rendered bounds in pixels.
+ */
+ public Rectangle getBounds ()
+ {
+ 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
* will be used as the center of the bottom edge of the sprite.
+ *
+ * @param x the x-position in pixels.
+ * @param y the y-position in pixels.
*/
public void setLocation (int x, int y)
{
@@ -100,6 +137,8 @@ public class Sprite
/**
* Called by the sprite manager when a sprite is added to said manager
* for management.
+ *
+ * @param mgr the sprite manager.
*/
protected void setSpriteManager (SpriteManager mgr)
{
@@ -208,7 +247,7 @@ public class Sprite
}
/**
- * Set the sprite's velocity when walking.
+ * Set the sprite's velocity when following a path.
*
* @param vx the x-axis velocity.
* @param vy the y-axis velocity.
@@ -225,7 +264,7 @@ public class Sprite
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.
+ // even in the case where we aborted a path mid-traversal.
_dest = null;
_path = null;
_fullpath = null;
@@ -243,8 +282,9 @@ public class Sprite
{
// 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
+ // halt any previously existing movement since, regardless
+ // of its reasonableness, we've been asked to follow a new
+ // path
stop();
return;
}
@@ -268,12 +308,19 @@ public class Sprite
*/
protected void moveAlongPath ()
{
- // if no more nodes remain, clear out our path and bail
if (!_path.hasNext()) {
+ // inform observers that we've finished our path
+ notifyObservers(SpriteObserver.FINISHED_PATH, _path);
+ // clear out our path and bail
stop();
return;
}
+ if (_dest != null) {
+ // inform observers that we've finished a path node
+ notifyObservers(SpriteObserver.FINISHED_PATH_NODE, _dest);
+ }
+
// grab the next node in our path
_dest = (PathNode)_path.next();
@@ -387,6 +434,46 @@ public class Sprite
_rbounds.y = _y + _ryoff;
}
+ /**
+ * Add a sprite observer to observe this sprite's events.
+ *
+ * @param obs the sprite observer.
+ */
+ public void addSpriteObserver (SpriteObserver obs)
+ {
+ // create the observer list if it doesn't yet exist
+ if (_observers == null) {
+ _observers = new ArrayList();
+ }
+
+ // make sure each observer observes only once
+ if (_observers.contains(obs)) {
+ Log.info("Attempt to observe sprite already observing " +
+ "[sprite=" + this + ", obs=" + obs + "].");
+ return;
+ }
+
+ // add the observer
+ _observers.add(obs);
+ }
+
+ /**
+ * Inform all sprite observers of a sprite event.
+ *
+ * @param eventCode the type of sprite event.
+ * @param arg the argument associated with the event.
+ */
+ protected void notifyObservers (int eventCode, Object arg)
+ {
+ if (_observers == null) return;
+
+ int size = _observers.size();
+ for (int ii = 0; ii < size; ii++) {
+ SpriteObserver obs = (SpriteObserver)_observers.get(ii);
+ obs.spriteChanged(this, eventCode, arg);
+ }
+ }
+
/**
* Return a string representation of the sprite.
*/
@@ -441,9 +528,12 @@ public class Sprite
/** When moving, the full path the sprite is traversing. */
protected Path _fullpath;
- /** The sprite velocity when walking. */
+ /** The sprite velocity when moving. */
protected Point _vel;
+ /** The sprite observers observing this sprite. */
+ protected ArrayList _observers;
+
/** The sprite manager. */
protected SpriteManager _spritemgr;
}
diff --git a/src/java/com/threerings/media/sprite/SpriteManager.java b/src/java/com/threerings/media/sprite/SpriteManager.java
index 32779365e..9f37c8b8f 100644
--- a/src/java/com/threerings/media/sprite/SpriteManager.java
+++ b/src/java/com/threerings/media/sprite/SpriteManager.java
@@ -1,11 +1,12 @@
//
-// $Id: SpriteManager.java,v 1.10 2001/08/22 02:14:57 mdb Exp $
+// $Id: SpriteManager.java,v 1.11 2001/09/05 00:40:33 shaper Exp $
package com.threerings.media.sprite;
import java.awt.*;
-import java.util.ArrayList;
+import java.util.*;
+import com.samskivert.util.Tuple;
import com.threerings.media.Log;
/**
@@ -147,23 +148,151 @@ public class SpriteManager
}
}
+ /**
+ * Called periodically by the tick tasks put on the AWT event
+ * queue by the {@link AnimationManager}. Handles moving about of
+ * sprites and reporting of sprite collisions.
+ */
+ public void tick ()
+ {
+ // tick all sprites
+ tickSprites();
+
+ // re-sort the sprite list to account for potential new positions
+ sortSprites();
+
+ // notify sprite observers of any collisions
+ handleCollisions();
+ }
+
/**
* Call tick() on all sprite objects to give them a
* chance to move themselves about, change their display image,
* and so forth.
*/
- public void tick ()
+ protected void tickSprites ()
{
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.tick();
}
+ }
+
+ /**
+ * Sort the sprite list in order of ascending x-coordinate.
+ */
+ protected void sortSprites ()
+ {
+ Object[] sprites = new Sprite[_sprites.size()];
+ _sprites.toArray(sprites);
+ Arrays.sort(sprites, SPRITE_COMP);
+ _sprites.clear();
+ for (int ii = 0; ii < sprites.length; ii++) {
+ _sprites.add(sprites[ii]);
+ }
}
+ /**
+ * Check all sprites for collisions with others and inform any
+ * sprite observers.
+ */
+ protected void handleCollisions ()
+ {
+ // gather a list of all sprite collisions
+ int size = _sprites.size();
+ ArrayList collisions = new ArrayList();
+ for (int ii = 0; ii < size; ii++) {
+ Sprite sprite = (Sprite)_sprites.get(ii);
+ checkCollisions(ii, size, sprite, collisions);
+ }
+
+ // observers are notified of sprite collisions after all
+ // collision-detection is performed so that modifications to
+ // the sprite, e.g. location or velocity, won't impact the
+ // validity of our checking.
+ int csize = collisions.size();
+ for (int ii = 0; ii < csize; ii++) {
+ Tuple tup = (Tuple)collisions.get(ii);
+ Sprite a = (Sprite)tup.left, b = (Sprite)tup.right;
+
+ // inform sprite observers for both sprites of the collision
+ a.notifyObservers(SpriteObserver.COLLIDED_SPRITE, b);
+ b.notifyObservers(SpriteObserver.COLLIDED_SPRITE, a);
+ }
+ }
+
+ /**
+ * Check a sprite for collision with any other sprites in the
+ * sprite list. The sprites involved in a collision are added to
+ * the collisions list of {@link Tuple} objects.
+ *
+ * @param idx the starting sprite index.
+ * @param size the total number of sprites.
+ * @param sprite the sprite to check against other sprites for
+ * collisions.
+ * @param collisions the list of collisions gathered so far.
+ */
+ protected void checkCollisions (int idx, int size, Sprite sprite,
+ ArrayList collisions)
+ {
+ // TODO: make this handle quickly moving objects that may pass
+ // through each other.
+
+ // if we're the last sprite we know we've already handled any
+ // collisions
+ if (idx == (size - 1)) {
+ return;
+ }
+
+ // calculate the x-position of the right edge of the sprite
+ // we're checking for collisions
+ Rectangle bounds = sprite.getBounds();
+ int edgeX = bounds.x + bounds.width;
+
+ for (int ii = (idx + 1); ii < size; ii++) {
+ Sprite other = (Sprite)_sprites.get(ii);
+ Rectangle obounds = other.getBounds();
+
+// Log.info("Checking collision [bounds=" + bounds +
+// ", obounds=" + obounds + "].");
+
+ if (obounds.x > edgeX) {
+ // since sprites are stored in the list sorted by
+ // ascending x-position, we know this sprite and any
+ // other sprites farther on in the list can't possibly
+ // intersect with the sprite we're checking, so we're
+ // done.
+ return;
+ }
+
+ if (obounds.intersects(bounds)) {
+ collisions.add(new Tuple(sprite, other));
+ }
+ }
+ }
+
+ /** The comparator used to sort sprites by horizontal position. */
+ protected static final Comparator SPRITE_COMP = new SpriteComparator();
+
/** The sprite objects we're managing. */
protected ArrayList _sprites;
/** The dirty rectangles created by sprites. */
protected DirtyRectList _dirty;
+
+ protected static class SpriteComparator implements Comparator
+ {
+ public int compare (Object o1, Object o2)
+ {
+ Sprite s1 = (Sprite)o1;
+ Sprite s2 = (Sprite)o2;
+ return (s2.getX() - s1.getX());
+ }
+
+ public boolean equals (Object obj)
+ {
+ return (obj == this);
+ }
+ }
}
diff --git a/src/java/com/threerings/media/sprite/SpriteObserver.java b/src/java/com/threerings/media/sprite/SpriteObserver.java
new file mode 100644
index 000000000..4b9db6842
--- /dev/null
+++ b/src/java/com/threerings/media/sprite/SpriteObserver.java
@@ -0,0 +1,42 @@
+//
+// $Id: SpriteObserver.java,v 1.1 2001/09/05 00:40:33 shaper Exp $
+
+package com.threerings.media.sprite;
+
+/**
+ * An interface to be implemented by classes that would like to
+ * observe a sprite and be notified of meaningful events as it, and
+ * any other sprites, move about.
+ */
+public interface SpriteObserver
+{
+ /**
+ * This method is called by the {@link SpriteManager} when
+ * something interesting is accomplished by or happens to the
+ * sprite.
+ *
+ * @param sprite the involved sprite.
+ * @param eventCode the type of sprite event.
+ * @param arg the argument associated with the event.
+ */
+ public void spriteChanged (Sprite sprite, int eventCode, Object arg);
+
+ /**
+ * Event code noting that the sprite completed a path node. The
+ * argument to the event is the {@link PathNode} completed.
+ */
+ public static final int FINISHED_PATH_NODE = 0;
+
+ /**
+ * Event code noting that the sprite completed its entire path.
+ * The argument to the event is the {@link Path} completed.
+ */
+ public static final int FINISHED_PATH = 1;
+
+ /**
+ * Event code noting that the sprite collided with another
+ * sprite. The argument to the event is the {@link Sprite} the
+ * observed sprite collided with.
+ */
+ public static final int COLLIDED_SPRITE = 2;
+}
diff --git a/src/java/com/threerings/media/util/PathNode.java b/src/java/com/threerings/media/util/PathNode.java
index a121826c0..22800c4cd 100644
--- a/src/java/com/threerings/media/util/PathNode.java
+++ b/src/java/com/threerings/media/util/PathNode.java
@@ -1,13 +1,12 @@
//
-// $Id: PathNode.java,v 1.4 2001/08/14 22:54:45 mdb Exp $
+// $Id: PathNode.java,v 1.5 2001/09/05 00:40:33 shaper Exp $
package com.threerings.media.sprite;
import java.awt.Point;
/**
- * The PathNode is a single destination point in a
- * Path.
+ * A path node is a single destination point in a {@link Path}.
*/
public class PathNode
{
@@ -18,7 +17,7 @@ public class PathNode
public int dir;
/**
- * Construct a PathNode object.
+ * Construct a path node object.
*
* @param x the node x-position.
* @param y the node y-position.