Pulled out gobs of Sprite/Animation functionality into AbstractMedia.

Pulled out gobs of SpriteManager/AnimationManager functionality into
  AbstractMediaManager.
The big change: sprites can have their render order fine-tuned like
animations.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1787 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2002-10-08 21:03:37 +00:00
parent e804abad30
commit a0730067ec
10 changed files with 532 additions and 659 deletions
@@ -1,5 +1,5 @@
//
// $Id: ImageSprite.java,v 1.14 2002/09/17 20:21:19 mdb Exp $
// $Id: ImageSprite.java,v 1.15 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.sprite;
@@ -81,9 +81,9 @@ public class ImageSprite extends Sprite
}
// documentation inherited
protected void init (SpriteManager spritemgr)
protected void init ()
{
super.init(spritemgr);
super.init();
// now that we have our spritemanager, we can initialize our frames
setFrameIndex(0, true);
@@ -187,8 +187,8 @@ public class ImageSprite extends Sprite
dirty.add(_bounds);
// give the dirty rectangle to the region manager
if (_spritemgr != null) {
_spritemgr.getRegionManager().addDirtyRegion(dirty);
if (_mgr != null) {
_mgr.getRegionManager().addDirtyRegion(dirty);
}
}
@@ -1,5 +1,5 @@
//
// $Id: LabelSprite.java,v 1.3 2002/07/08 21:15:35 mdb Exp $
// $Id: LabelSprite.java,v 1.4 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.sprite;
@@ -35,9 +35,9 @@ public class LabelSprite extends Sprite
}
// documentation inherited
protected void init (SpriteManager spritemgr)
protected void init ()
{
super.init(spritemgr);
super.init();
// size the bounds to fit our label
Dimension size = _label.getSize();
+16 -136
View File
@@ -1,5 +1,5 @@
//
// $Id: Sprite.java,v 1.50 2002/07/08 21:15:35 mdb Exp $
// $Id: Sprite.java,v 1.51 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.sprite;
@@ -7,10 +7,9 @@ import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import com.threerings.util.DirectionCodes;
import com.threerings.media.AbstractMedia;
import com.threerings.media.Log;
import com.threerings.media.util.Path;
import com.threerings.media.util.Pathable;
@@ -20,7 +19,7 @@ import com.threerings.media.util.Pathable;
* view. A sprite has a position and orientation within the view, and can
* be moved along a path.
*/
public abstract class Sprite
public abstract class Sprite extends AbstractMedia
implements DirectionCodes, Pathable
{
/**
@@ -33,27 +32,7 @@ public abstract class Sprite
*/
public Sprite ()
{
}
/**
* Called by the sprite manager to initialize the sprite when a sprite
* is added to said manager for management.
*
* @param spritemgr the sprite manager.
*/
protected void init (SpriteManager spritemgr)
{
_spritemgr = spritemgr;
}
/**
* Called by the sprite manager after the sprite is removed from
* service. Derived classes may override this method but should be
* sure to call <code>super.shutdown()</code>.
*/
protected void shutdown ()
{
_spritemgr = null;
super(new Rectangle());
}
/**
@@ -92,14 +71,6 @@ public abstract class Sprite
return _bounds.height;
}
/**
* Returns the sprite's rendered bounds in pixels.
*/
public Rectangle getBounds ()
{
return _bounds;
}
/**
* Sprites have an orientation in one of the eight cardinal
* directions: <code>NORTH</code>, <code>NORTHEAST</code>, etc.
@@ -125,31 +96,7 @@ public abstract class Sprite
return _orient;
}
/**
* Returns the render order of this sprite.
*/
public int getRenderOrder ()
{
return _renderOrder;
}
/**
* Sets the render order associated with this animation. Sprites can
* be rendered in two layers; those with negative render order and
* those with positive render order. Someday sprites will be rendered
* in each layer according to render order.
*/
public void setRenderOrder (int value)
{
_renderOrder = value;
}
/**
* Moves the sprite to the specified location.
*
* @param x the x-position in pixels.
* @param y the y-position in pixels.
*/
// documentation inherited
public void setLocation (int x, int y)
{
// start with our current bounds
@@ -165,24 +112,20 @@ public abstract class Sprite
// grow the dirty rectangle to incorporate our new bounds and pass
// the dirty region to our region manager
if (_spritemgr != null) {
if (_mgr != null) {
// if our new bounds intersect our old bounds, grow a single
// dirty rectangle to incorporate them both
if (_bounds.intersects(dirty)) {
dirty.add(_bounds);
} else {
// otherwise invalidate our new bounds separately
_spritemgr.getRegionManager().invalidateRegion(_bounds);
_mgr.getRegionManager().invalidateRegion(_bounds);
}
_spritemgr.getRegionManager().addDirtyRegion(dirty);
_mgr.getRegionManager().addDirtyRegion(dirty);
}
}
/**
* Paint the sprite to the specified graphics context.
*
* @param gfx the graphics context.
*/
// documentation inherited
public void paint (Graphics2D gfx)
{
gfx.draw(_bounds);
@@ -302,24 +245,7 @@ public abstract class Sprite
notifyObservers(new PathCompletedEvent(this, oldpath));
}
/**
* Invalidate the sprite's bounding rectangle for later repainting.
*/
public void invalidate ()
{
if (_spritemgr != null) {
_spritemgr.getRegionManager().invalidateRegion(_bounds);
}
}
/**
* This method is called periodically by the sprite manager to give
* 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.
*/
// documentation inherited
public void tick (long tickStamp)
{
// if we've a path, move the sprite along toward its destination
@@ -328,12 +254,7 @@ public abstract class Sprite
}
}
/**
* This is called if the sprite manager is paused for some length of
* time and then unpaused. Sprites should adjust any time stamps they
* are maintaining internally by the delta so that time maintains the
* illusion of flowing smoothly forward.
*/
// documentation inherited
public void fastForward (long timeDelta)
{
// fast forward any path we're following
@@ -360,20 +281,7 @@ public abstract class Sprite
*/
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);
addObserver(obs);
}
/**
@@ -384,39 +292,20 @@ public abstract class Sprite
protected void notifyObservers (SpriteEvent event)
{
if (_observers != null) {
// we pass this notification off to the sprite manager so that
// it can dispatch all of the notifications at once after all
// ticking has been completed
_spritemgr.notifySpriteObservers(_observers, event);
_mgr.queueNotification(_observers, event);
}
}
/**
* Return a string representation of the sprite.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer("[");
toString(buf);
return buf.append("]").toString();
}
/**
* This should be overridden by derived classes (which should be sure
* to call <code>super.toString()</code>) to append the derived class
* specific sprite information to the string buffer.
*/
// documentation inherited
protected void toString (StringBuffer buf)
{
buf.append("ox=").append(_ox);
super.toString(buf);
buf.append(", ox=").append(_ox);
buf.append(", oy=").append(_oy);
buf.append(", oxoff=").append(_oxoff);
buf.append(", oyoff=").append(_oyoff);
}
/** The sprite manager. */
protected SpriteManager _spritemgr;
/** The location of the sprite's origin in pixel coordinates. If the
* sprite positions itself via a hotspot that is not the upper left
* coordinate of the sprite's bounds, the offset to the hotspot should
@@ -429,18 +318,9 @@ public abstract class Sprite
* its bounds. */
protected int _oxoff, _oyoff;
/** Our rendered bounds in pixel coordinates. */
protected Rectangle _bounds = new Rectangle();
/** The orientation of this sprite. */
protected int _orient = NONE;
/** When moving, the path the sprite is traversing. */
protected Path _path;
/** The render order of this sprite. */
protected int _renderOrder;
/** The sprite observers observing this sprite. */
protected ArrayList _observers;
}
@@ -1,42 +1,32 @@
//
// $Id: SpriteManager.java,v 1.37 2002/09/20 02:19:53 mdb Exp $
// $Id: SpriteManager.java,v 1.38 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.sprite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.samskivert.util.CollectionUtil;
import com.samskivert.util.SortableArrayList;
import com.samskivert.util.Tuple;
import com.threerings.media.AbstractMediaManager;
import com.threerings.media.Log;
import com.threerings.media.MediaConstants;
import com.threerings.media.RegionManager;
/**
* The sprite manager manages the sprites running about in the game.
*/
public class SpriteManager
implements MediaConstants
public class SpriteManager extends AbstractMediaManager
{
/**
* Construct and initialize the sprite manager.
*/
public SpriteManager (RegionManager remgr)
{
_sprites = new SortableArrayList();
_notify = new ArrayList();
_remgr = remgr;
super(remgr);
}
/**
@@ -53,9 +43,9 @@ public class SpriteManager
*/
public void getIntersectingSprites (List list, Shape shape)
{
int size = _sprites.size();
int size = _media.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
Sprite sprite = (Sprite)_media.get(ii);
if (sprite.intersects(shape)) {
list.add(sprite);
}
@@ -75,9 +65,9 @@ public class SpriteManager
*/
public void getHitSprites (List list, int x, int y)
{
int size = _sprites.size();
int size = _media.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
Sprite sprite = (Sprite)_media.get(ii);
if (sprite.hitTest(x, y)) {
list.add(sprite);
}
@@ -91,12 +81,10 @@ public class SpriteManager
*/
public void addSprite (Sprite sprite)
{
// initialize the sprite
sprite.init(this);
// add the sprite to our list
_sprites.add(sprite);
// and invalidate the sprite's original position
sprite.invalidate();
if (insertMedia(sprite)) {
// and invalidate the sprite's original position
sprite.invalidate();
}
}
/**
@@ -106,7 +94,7 @@ public class SpriteManager
*/
public List getSprites ()
{
return Collections.unmodifiableList(_sprites);
return Collections.unmodifiableList(_media);
}
/**
@@ -117,12 +105,7 @@ public class SpriteManager
*/
public void removeSprite (Sprite sprite)
{
// invalidate the current sprite position
sprite.invalidate();
// remove the sprite from our list
_sprites.remove(sprite);
// clear out our manager reference
sprite.shutdown();
removeMedia(sprite);
}
/**
@@ -133,51 +116,7 @@ public class SpriteManager
*/
public void clearSprites ()
{
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
sprite.shutdown();
}
_sprites.clear();
}
/**
* Provides access to the region manager that the sprite manager is
* using to collect invalid regions every frame. This should generally
* only be used by sprites that want to invalidate themselves.
*/
public RegionManager getRegionManager ()
{
return _remgr;
}
/**
* Render to the given graphics context the sprites intersecting the
* given shape and residing in the specified layer.
*
* @param layer the layer to render; one of {@link #FRONT}, {@link
* #BACK}, or {@link #ALL}. The front layer contains all sprites with
* a positive render order; the back layer contains all sprites with a
* negative render order; all, both.
* @param bounds the bounding shape.
*/
public void renderSprites (Graphics2D gfx, int layer, Shape bounds)
{
// TODO: optimize to store sprites based on quadrants they're in
// (or somesuch), and sorted, so that we can more quickly
// determine which sprites to draw.
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
int order = sprite.getRenderOrder();
if (((layer == ALL) ||
(layer == FRONT && order >= 0) ||
(layer == BACK && order < 0)) &&
sprite.intersects(bounds)) {
sprite.paint(gfx);
}
}
clearMedia();
}
/**
@@ -187,187 +126,91 @@ public class SpriteManager
*/
public void renderSpritePaths (Graphics2D gfx)
{
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
for (int ii=0, nn=_media.size(); ii < nn; ii++) {
Sprite sprite = (Sprite)_media.get(ii);
sprite.paintPath(gfx);
}
}
/**
* Must be called every frame so that the sprites can be properly
* updated. Normally a sprite manager is used in conjunction with an
* animated panel which case this is called automatically.
*/
public void tick (long tickStamp)
// documentation inherited
protected void dispatchEvent (ArrayList observers, Object event)
{
// tick all sprites
tickSprites(tickStamp);
// re-sort the sprite list to account for potential new positions
_sprites.sort(SPRITE_COMP);
// // notify sprite observers of any collisions
// handleCollisions();
// notify sprite observers of any sprite events. note that we
// explicitly queue up events while ticking and checking for
// collisions, and we only notify observers once we're
// finished with all of those antics so that any actions the
// observers may take, such as sprite removal, won't screw us
// up elsewhere.
handleSpriteEvents();
}
/**
* If the sprite manager is paused for some length of time, it should
* be fast forwarded by the appropriate number of milliseconds. This
* allows sprites to smoothly pick up where they left off rather than
* abruptly jumping into the future, thinking that some outrageous
* amount of time passed since their last tick.
*/
public void fastForward (long timeDelta)
{
int size = _sprites.size();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
sprite.fastForward(timeDelta);
SpriteEvent sevt = (SpriteEvent) event;
for (int ii=0, nn=observers.size(); ii < nn; ii++) {
((SpriteObserver) observers.get(ii)).handleEvent(sevt);
}
}
/**
* Call {@link Sprite#tick} on all sprite objects to give them a
* chance to move themselves about, change their display image,
* generate dirty regions and so forth.
*/
protected void tickSprites (long tickStamp)
{
// we tick sprites in reverse order so as not to freak out if a
// sprite is removed as a result of our calling tick() on it
int size = _sprites.size();
for (int ii = size-1; ii >= 0; ii--) {
Sprite sprite = (Sprite)_sprites.get(ii);
sprite.tick(tickStamp);
}
}
/**
* 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();
for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii);
checkCollisions(ii, size, sprite);
}
}
/**
* Check a sprite for collision with any other sprites in the
* sprite list and notify the sprite observers associated with any
* sprites that do indeed collide.
*
* @param idx the starting sprite index.
* @param size the total number of sprites.
* @param sprite the sprite to check against other sprites for
* collisions.
*/
protected void checkCollisions (int idx, int size, Sprite sprite)
{
// 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();
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;
} else if (obounds.intersects(bounds)) {
sprite.notifyObservers(new CollisionEvent(sprite, other));
}
}
}
/**
* Notify all sprite observers of any sprite events that took place
* during our most recent <code>tick()</code>.
*/
protected void handleSpriteEvents ()
{
int size = _notify.size();
for (int ii = 0; ii < size; ii++) {
Tuple tup = (Tuple)_notify.remove(0);
ArrayList observers = (ArrayList)tup.left;
SpriteEvent evt = (SpriteEvent)tup.right;
int osize = observers.size();
for (int jj = 0; jj < osize; jj++) {
SpriteObserver obs = (SpriteObserver)observers.get(jj);
obs.handleEvent(evt);
}
}
}
/**
* Called by {@link Sprite#notifyObservers} to note that the
* sprite's observers should be informed of a sprite event once
* the current <code>tick()</code> is complete.
*
* @param observers the list of sprite observers.
* @param event the sprite event to notify the observers of.
*/
protected void notifySpriteObservers (ArrayList observers,
SpriteEvent event)
{
// throw it on the list of events for later
_notify.add(new Tuple(observers, event));
}
/** The sprite objects we're managing. */
protected SortableArrayList _sprites;
/** The list of pending sprite notifications. */
protected ArrayList _notify;
/** Used to accumulate dirty regions. */
protected RegionManager _remgr;
/** The comparator used to sort sprites by horizontal position. */
protected static final Comparator SPRITE_COMP = new SpriteComparator();
/** Used to sort sprites. */
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);
}
}
// NOTE- collision handling code is turned off for now. To re-implement,
// a new array should be kept with sprites sorted in some sort of x/y order
//
// /**
// * 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();
// for (int ii = 0; ii < size; ii++) {
// Sprite sprite = (Sprite)_sprites.get(ii);
// checkCollisions(ii, size, sprite);
// }
// }
//
// /**
// * Check a sprite for collision with any other sprites in the
// * sprite list and notify the sprite observers associated with any
// * sprites that do indeed collide.
// *
// * @param idx the starting sprite index.
// * @param size the total number of sprites.
// * @param sprite the sprite to check against other sprites for
// * collisions.
// */
// protected void checkCollisions (int idx, int size, Sprite sprite)
// {
// // 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();
// 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;
//
// } else if (obounds.intersects(bounds)) {
// sprite.notifyObservers(new CollisionEvent(sprite, other));
// }
// }
// }
// /** The comparator used to sort sprites by horizontal position. */
// protected static final Comparator SPRITE_COMP = new SpriteComparator();
//
// /** Used to sort sprites. */
// 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());
// }
// }
}