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
@@ -0,0 +1,184 @@
//
// $Id: AbstractMedia.java,v 1.1 2002/10/08 21:03:37 ray Exp $
package com.threerings.media;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import com.samskivert.util.StringUtil;
/**
* Something that can be rendered on the media panel.
*/
public abstract class AbstractMedia
{
/**
* Instantiate an abstract media object.
*/
public AbstractMedia (Rectangle bounds)
{
_bounds = bounds;
}
/**
* Called periodically by this media's manager to give it
* a chance to do its thing.
*
* @param tickStamp the system time for this tick.
*/
public abstract void tick (long tickStamp);
/**
* Called by the appropriate manager to request that the
* media render itself with the given graphics context. The
* media may wish to inspect the clipping region that has been set
* on the graphics context to render itself more efficiently. This
* method will only be called after it has been established that this
* media's bounds intersect the clipping region.
*/
public abstract void paint (Graphics2D gfx);
/**
* Called when the appropriate media manager has been paused for some
* length of time and is then unpaused. Media should adjust any time stamps
* that are maintained internally forward by the delta so that time
* maintains the illusion of flowing smoothly forward.
*/
public void fastForward (long timeDelta)
{
}
/**
* Invalidate the media's bounding rectangle for later painting.
*/
public void invalidate ()
{
if (_mgr != null) {
_mgr.getRegionManager().invalidateRegion(_bounds);
}
}
/**
* Set the location.
*/
public void setLocation (int x, int y)
{
_bounds.x = x;
_bounds.y = y;
}
/**
* Returns a rectangle containing all the pixels rendered by this media.
*/
public Rectangle getBounds ()
{
return _bounds;
}
/**
* Sets the render order associated with this media. Media
* can be rendered in two layers; those with negative render order and
* those with positive render order. In the same layer, they
* will be rendered according to their render order's cardinal value
* (least to greatest). Those with the same render order value will be
* rendered in arbitrary order.
*
* <p> This must be set <em>before</em> the media is added to the
* appropriate manager and must not change while the manager is
* managing it. If you wish to change the render order, remove the
* media from the manager, change the order and add it back again.
*/
public void setRenderOrder (int renderOrder)
{
_renderOrder = renderOrder;
}
/**
* Returns the render order of this media element.
*/
public int getRenderOrder ()
{
return _renderOrder;
}
/**
* Initialize the media.
*/
protected final void init (AbstractMediaManager manager)
{
_mgr = manager;
init();
}
/**
* Called when the media has had its manager set.
* Derived classes may override this method, but should be sure to
* call <code>super.init()</code>.
*/
protected void init ()
{
}
/**
* Called by the media manager after the media is removed from service.
* Derived classes may override this method, but should be sure to
* call <code>super.shutdown()</code>.
*/
protected void shutdown ()
{
_mgr = null;
}
/**
* Add the specified observer to this media element.
*/
protected void addObserver (Object obs)
{
if (_observers == null) {
_observers = new ArrayList();
} else if (_observers.contains(obs)) {
Log.info("Attempt to observe media already observing " +
"[media=" + this + ", obs=" + obs + "].");
return;
}
_observers.add(obs);
}
/**
* Dumps this media to a String object.
*/
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 information to the string buffer.
*/
protected void toString (StringBuffer buf)
{
buf.append("bounds=").append(StringUtil.toString(_bounds));
buf.append(", renderOrder=").append(_renderOrder);
}
/** The layer in which to render. */
protected int _renderOrder = 0;
/** The bounds of the media's rendering area. */
protected Rectangle _bounds;
/** Our manager. */
protected AbstractMediaManager _mgr;
/** Our observers. */
protected ArrayList _observers = null;
}
@@ -0,0 +1,200 @@
//
// $Id: AbstractMediaManager.java,v 1.1 2002/10/08 21:03:37 ray Exp $
package com.threerings.media;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.Comparator;
import com.samskivert.util.SortableArrayList;
import com.samskivert.util.StringUtil;
/**
* Manages, ticks, and paints AbstractMedia.
*/
public abstract class AbstractMediaManager
implements MediaConstants
{
/**
* Default constructor.
*/
public AbstractMediaManager (RegionManager remgr)
{
_remgr = remgr;
}
/**
* Provides access to the region manager that the mediamanager is using.
*/
public RegionManager getRegionManager ()
{
return _remgr;
}
/**
* Renders all registered media in the given layer that intersect
* the supplied clipping rectangle to the given graphics context.
*
* @param layer the layer to render; one of {@link #FRONT}, {@link
* #BACK}, or {@link #ALL}. The front layer contains all animations
* with a positive render order; the back layer contains all
* animations with a negative render order; all, both.
*/
public void renderMedia (Graphics2D gfx, int layer, Shape clip)
{
for (int ii=0, nn=_media.size(); ii < nn; ii++) {
AbstractMedia media = (AbstractMedia) _media.get(ii);
int order = media.getRenderOrder();
try {
if (((layer == ALL) ||
(layer == FRONT && order >= 0) ||
(layer == BACK && order < 0)) &&
clip.intersects(media.getBounds())) {
media.paint(gfx);
}
} catch (Exception e) {
Log.warning("Failed to render media " +
"[media=" + media + ", e=" + e + "].");
Log.logStackTrace(e);
}
}
}
/**
* Must be called every frame so that the media can be properly
* updated.
*/
public void tick (long tickStamp)
{
tickAllMedia(tickStamp);
dispatchNotifications();
}
/**
* If the manager is paused for some length of time, it should
* be fast forwarded by the appropriate number of milliseconds. This
* allows media 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)
{
for (int ii=0, nn=_media.size(); ii < nn; ii++) {
((AbstractMedia) _media.get(ii)).fastForward(timeDelta);
}
}
/**
* Call {@link AbstractMedia#tick} on all media to give
* them a chance to move about, change their look, generate dirty
* regions, and so forth.
*/
protected void tickAllMedia (long tickStamp)
{
// we tick media in reverse order, because some may remove
// themselves on the tick
for (int ii=_media.size() - 1; ii >= 0; ii--) {
((AbstractMedia) _media.get(ii)).tick(tickStamp);
}
}
/**
* Insert the specified media into this manager, return true on success.
*/
protected boolean insertMedia (AbstractMedia media)
{
if (_media.contains(media)) {
Log.warning("Attempt to insert media more than once " +
"[media=" + media + "].");
return false;
}
_media.insertSorted(media, RENDER_ORDER);
media.init(this);
return true;
}
/**
* Remove the specified media from this manager, return true on success.
*/
protected boolean removeMedia (AbstractMedia media)
{
if (_media.remove(media)) {
media.invalidate();
media.shutdown();
return true;
}
Log.warning("Attempt to remove media that wasn't inserted " +
"[media=" + media + "].");
return false;
}
/**
* Clears all media from the manager. This does not invalidate
* their vacated bounds, it is assumed that it will be ok.
*/
protected void clearMedia ()
{
for (int ii=_media.size() - 1; ii >= 0; ii--) {
AbstractMedia media = (AbstractMedia) _media.remove(ii);
media.shutdown();
}
}
/**
* Queue the event for dispatching after we've ticked all
* the media.
*/
public void queueNotification (ArrayList observers, Object event)
{
_notify.add(new Object[] {observers, event});
}
/**
* Dispatch all queued events.
*/
protected void dispatchNotifications ()
{
for (int ii=0, nn=_notify.size(); ii < nn; ii++) {
Object[] junk = (Object[]) _notify.remove(0);
ArrayList observers = (ArrayList) junk[0];
Object event = junk[1];
dispatchEvent(observers, event);
}
}
/**
* Dispatch the specified event to the specified observers.
*/
protected abstract void dispatchEvent (ArrayList observers, Object event);
/** The Region Manager. */
protected RegionManager _remgr;
/** List of observers to notify at the end of the tick. */
protected ArrayList _notify = new ArrayList();
/** Our render-order sorted list of media. */
protected SortableArrayList _media = new SortableArrayList();
/** Used to sort media by render order. */
protected static final Comparator RENDER_ORDER = new Comparator() {
public int compare (Object o1, Object o2) {
int result = (((AbstractMedia)o1)._renderOrder -
((AbstractMedia)o2)._renderOrder);
if (result != 0) {
return result;
} else {
// find some other way to keep them stable relative to each
// other.
return o1.hashCode() - o2.hashCode();
}
}
};
}
@@ -1,5 +1,5 @@
// //
// $Id: MediaPanel.java,v 1.17 2002/06/25 17:31:16 mdb Exp $ // $Id: MediaPanel.java,v 1.18 2002/10/08 21:03:37 ray Exp $
package com.threerings.media; package com.threerings.media;
@@ -371,8 +371,8 @@ public class MediaPanel extends JComponent
*/ */
protected void paintBits (Graphics2D gfx, int layer, Rectangle dirty) protected void paintBits (Graphics2D gfx, int layer, Rectangle dirty)
{ {
_animmgr.renderAnimations(gfx, layer, dirty); _animmgr.renderMedia(gfx, layer, dirty);
_spritemgr.renderSprites(gfx, layer, dirty); _spritemgr.renderMedia(gfx, layer, dirty);
} }
/** The frame manager with whom we register. */ /** The frame manager with whom we register. */
@@ -1,15 +1,11 @@
// //
// $Id: Animation.java,v 1.8 2002/09/20 21:28:20 mdb Exp $ // $Id: Animation.java,v 1.9 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.animation; package com.threerings.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.util.ArrayList; import com.threerings.media.AbstractMedia;
import com.samskivert.util.StringUtil;
import com.threerings.media.Log; import com.threerings.media.Log;
/** /**
@@ -17,7 +13,7 @@ import com.threerings.media.Log;
* provide animation functionality. It is generally used in conjunction * provide animation functionality. It is generally used in conjunction
* with an {@link AnimationManager}. * with an {@link AnimationManager}.
*/ */
public abstract class Animation public abstract class Animation extends AbstractMedia
{ {
/** /**
* Constructs an animation. * Constructs an animation.
@@ -26,80 +22,7 @@ public abstract class Animation
*/ */
public Animation (Rectangle bounds) public Animation (Rectangle bounds)
{ {
_bounds = bounds; super(bounds);
}
/**
* Returns the render order of this animation.
*/
public int getRenderOrder ()
{
return _renderOrder;
}
/**
* Sets the render order associated with this animation. Animations
* can be rendered in two layers; those with negative render order and
* those with positive render order. In the same layer, animations
* will be rendered according to their render order's cardinal value
* (least to greatest). Those with the same render order value will be
* rendered in arbitrary order.
*
* <p> This must be set <em>before</em> the animation is handed to the
* {@link AnimationManager} and must not change while the {@link
* AnimationManager} is managing the animation. If you wish to change
* the render order, remove the animation from the manager, change the
* order and add it back again.
*/
public void setRenderOrder (int value)
{
_renderOrder = value;
}
/**
* Sets the location at which this animation will be rendered.
*/
public void setLocation (int x, int y)
{
_bounds.x = x;
_bounds.y = y;
}
/**
* Returns a rectangle containing all pixels rendered by this
* animation.
*/
public Rectangle getBounds ()
{
return _bounds;
}
/**
* Called periodically by the {@link AnimationManager} to give the
* animation a chance to do its thing.
*
* @param tickStamp the system time for this tick.
*/
public abstract void tick (long tickStamp);
/**
* Called by the {@link AnimationManager} to request that the
* animation render itself with the given graphics context. The
* animation may wish to inspect the clipping region that has been set
* on the graphics context to render itself more efficiently. This
* method will only be called after it has been established that this
* animation's bounds intersect the clipping region.
*/
public abstract void paint (Graphics2D gfx);
/**
* This is called if the animation manager is paused for some length
* of time and then unpaused. Animations should adjust any time stamps
* they are maintaining internally by the delta so that time maintains
* the illusion of flowing smoothly forward.
*/
public void fastForward (long timeDelta)
{
} }
/** /**
@@ -120,14 +43,6 @@ public abstract class Animation
_finished = false; _finished = false;
} }
/**
* Invalidates the bounds of this animation.
*/
public void invalidate ()
{
_animmgr.getRegionManager().invalidateRegion(_bounds);
}
/** /**
* Called when the animation is finished and the animation manager has * Called when the animation is finished and the animation manager has
* removed it from service. * removed it from service.
@@ -142,76 +57,20 @@ public abstract class Animation
*/ */
public void addAnimationObserver (AnimationObserver obs) public void addAnimationObserver (AnimationObserver obs)
{ {
// create the observer list if it doesn't yet exist addObserver(obs);
if (_observers == null) {
_observers = new ArrayList();
}
// make sure each observer observes only once
if (_observers.contains(obs)) {
Log.info("Attempt to observe animation already observing " +
"[anim=" + this + ", obs=" + obs + "].");
return;
}
// add the observer
_observers.add(obs);
} }
/** /**
* Notifies any animation observers that the given animation event has * Notifies any animation observers that the given animation event has
* occurred. * occurred.
*/ */
public void notifyObservers (AnimationEvent e) public void notifyObservers (AnimationEvent event)
{ {
int size = (_observers == null) ? 0 : _observers.size(); if (_observers != null) {
for (int ii = 0; ii < size; ii++) { _mgr.queueNotification(_observers, event);
((AnimationObserver)_observers.get(ii)).handleEvent(e);
} }
} }
/**
* Return a string representation of the animation.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer();
buf.append("[");
toString(buf);
return buf.append("]").toString();
}
/**
* Called automatically when an animation is added to an animation
* manager for management.
*/
protected void setAnimationManager (AnimationManager animmgr)
{
_animmgr = animmgr;
}
/**
* This should be overridden by derived classes (which should be sure
* to call <code>super.toString()</code>) to append the derived class
* specific animation information to the string buffer.
*/
protected void toString (StringBuffer buf)
{
buf.append("bounds=").append(StringUtil.toString(_bounds));
}
/** Our animation manager. */
protected AnimationManager _animmgr;
/** Whether the animation is finished. */ /** Whether the animation is finished. */
protected boolean _finished = false; protected boolean _finished = false;
/** The animation bounds. */
protected Rectangle _bounds;
/** The render order of this animation. */
protected int _renderOrder;
/** The list of animation observers. */
protected ArrayList _observers;
} }
@@ -1,17 +1,14 @@
// //
// $Id: AnimationManager.java,v 1.13 2002/09/20 21:28:20 mdb Exp $ // $Id: AnimationManager.java,v 1.14 2002/10/08 21:03:37 ray Exp $
package com.threerings.media.animation; package com.threerings.media.animation;
import java.awt.Graphics2D; import java.util.ArrayList;
import java.awt.Shape;
import java.util.Comparator;
import com.samskivert.util.SortableArrayList; import com.samskivert.util.SortableArrayList;
import com.threerings.media.AbstractMediaManager;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.MediaConstants;
import com.threerings.media.RegionManager; import com.threerings.media.RegionManager;
/** /**
@@ -19,8 +16,7 @@ import com.threerings.media.RegionManager;
* manager itself is ticked and generating events when animations finish * manager itself is ticked and generating events when animations finish
* and suchlike. * and suchlike.
*/ */
public class AnimationManager public class AnimationManager extends AbstractMediaManager
implements MediaConstants
{ {
/** /**
* Construct and initialize the animation manager which readies itself * Construct and initialize the animation manager which readies itself
@@ -28,7 +24,7 @@ public class AnimationManager
*/ */
public AnimationManager (RegionManager remgr) public AnimationManager (RegionManager remgr)
{ {
_remgr = remgr; super(remgr);
} }
/** /**
@@ -37,14 +33,7 @@ public class AnimationManager
*/ */
public void registerAnimation (Animation anim) public void registerAnimation (Animation anim)
{ {
if (_anims.contains(anim)) { insertMedia(anim);
Log.warning("Attempt to register animation more than once " +
"[anim=" + anim + "].");
return;
}
anim.setAnimationManager(this);
_anims.insertSorted(anim, RENDER_ORDER);
} }
/** /**
@@ -55,43 +44,17 @@ public class AnimationManager
*/ */
public void unregisterAnimation (Animation anim) public void unregisterAnimation (Animation anim)
{ {
// un-register the animation removeMedia(anim);
if (!_anims.remove(anim)) {
Log.warning("Attempt to un-register animation that isn't " +
"registered [anim=" + anim + "].");
return;
}
// invalidate its bounds
_remgr.invalidateRegion(anim.getBounds());
} }
/** // documentation inherited
* Provides access to the region manager that the animation manager is protected void tickAllMedia (long tickStamp)
* using to collect invalid regions every frame. This should generally
* only be used by animations that want to invalidate themselves.
*/
public RegionManager getRegionManager ()
{ {
return _remgr; super.tickAllMedia(tickStamp);
}
/**
* Handles updating animations and generating associated events.
*
* @param tickStamp the system clock at the time of the tick.
*/
public void tick (long tickStamp)
{
// tick all of our animations
int size = _anims.size();
for (int ii = 0; ii < size; ii++) {
((Animation)_anims.get(ii)).tick(tickStamp);
}
// remove any finished animations // remove any finished animations
for (int ii = size - 1; ii >= 0; ii--) { for (int ii=_media.size() - 1; ii >= 0; ii--) {
Animation anim = (Animation)_anims.get(ii); Animation anim = (Animation)_media.get(ii);
if (anim.isFinished()) { if (anim.isFinished()) {
// let any animation observers know that we're done // let any animation observers know that we're done
anim.notifyObservers(new AnimationCompletedEvent(anim)); anim.notifyObservers(new AnimationCompletedEvent(anim));
@@ -104,68 +67,12 @@ public class AnimationManager
} }
} }
/** // documentation inherited
* If the animation manager is paused for some length of time, it protected void dispatchEvent (ArrayList observers, Object event)
* should be fast forwarded by the appropriate number of milliseconds.
* This allows animations 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)
{ {
// fast forward all of our animations AnimationEvent aevt = (AnimationEvent) event;
int size = _anims.size(); for (int ii=0, nn=observers.size(); ii < nn; ii++) {
for (int ii = 0; ii < size; ii++) { ((AnimationObserver) observers.get(ii)).handleEvent(aevt);
((Animation)_anims.get(ii)).fastForward(timeDelta);
} }
} }
/**
* Renders all registered animations in the given layer that intersect
* the supplied clipping rectangle to the given graphics context.
*
* @param layer the layer to render; one of {@link #FRONT}, {@link
* #BACK}, or {@link #ALL}. The front layer contains all animations
* with a positive render order; the back layer contains all
* animations with a negative render order; all, both.
*/
public void renderAnimations (Graphics2D gfx, int layer, Shape clip)
{
// now paint them
int size = _anims.size();
for (int ii = 0; ii < size; ii++) {
Animation anim = (Animation)_anims.get(ii);
int order = anim.getRenderOrder();
try {
if (((layer == ALL) ||
(layer == FRONT && order >= 0) ||
(layer == BACK && order < 0)) &&
clip.intersects(anim.getBounds())) {
anim.paint(gfx);
}
} catch (Exception e) {
Log.warning("Failed to render animation " +
"[anim=" + anim + ", e=" + e + "].");
Log.logStackTrace(e);
}
}
}
/** Used to accumulate dirty regions. */
protected RegionManager _remgr;
/** The list of animations. */
protected SortableArrayList _anims = new SortableArrayList();
/** Used to sort animations prior to painting. */
protected static final Comparator RENDER_ORDER = new Comparator() {
public int compare (Object o1, Object o2) {
return (((Animation)o1)._renderOrder -
((Animation)o2)._renderOrder);
}
public boolean equals (Object obj) {
return this == obj;
}
};
} }
@@ -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; package com.threerings.media.sprite;
@@ -81,9 +81,9 @@ public class ImageSprite extends Sprite
} }
// documentation inherited // 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 // now that we have our spritemanager, we can initialize our frames
setFrameIndex(0, true); setFrameIndex(0, true);
@@ -187,8 +187,8 @@ public class ImageSprite extends Sprite
dirty.add(_bounds); dirty.add(_bounds);
// give the dirty rectangle to the region manager // give the dirty rectangle to the region manager
if (_spritemgr != null) { if (_mgr != null) {
_spritemgr.getRegionManager().addDirtyRegion(dirty); _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; package com.threerings.media.sprite;
@@ -35,9 +35,9 @@ public class LabelSprite extends Sprite
} }
// documentation inherited // documentation inherited
protected void init (SpriteManager spritemgr) protected void init ()
{ {
super.init(spritemgr); super.init();
// size the bounds to fit our label // size the bounds to fit our label
Dimension size = _label.getSize(); 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; package com.threerings.media.sprite;
@@ -7,10 +7,9 @@ import java.awt.Graphics2D;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.util.ArrayList;
import com.threerings.util.DirectionCodes; import com.threerings.util.DirectionCodes;
import com.threerings.media.AbstractMedia;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.util.Path; import com.threerings.media.util.Path;
import com.threerings.media.util.Pathable; 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 * view. A sprite has a position and orientation within the view, and can
* be moved along a path. * be moved along a path.
*/ */
public abstract class Sprite public abstract class Sprite extends AbstractMedia
implements DirectionCodes, Pathable implements DirectionCodes, Pathable
{ {
/** /**
@@ -33,27 +32,7 @@ public abstract class Sprite
*/ */
public Sprite () public Sprite ()
{ {
} super(new Rectangle());
/**
* 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;
} }
/** /**
@@ -92,14 +71,6 @@ public abstract class Sprite
return _bounds.height; 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 * Sprites have an orientation in one of the eight cardinal
* directions: <code>NORTH</code>, <code>NORTHEAST</code>, etc. * directions: <code>NORTH</code>, <code>NORTHEAST</code>, etc.
@@ -125,31 +96,7 @@ public abstract class Sprite
return _orient; return _orient;
} }
/** // documentation inherited
* 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.
*/
public void setLocation (int x, int y) public void setLocation (int x, int y)
{ {
// start with our current bounds // start with our current bounds
@@ -165,24 +112,20 @@ public abstract class Sprite
// grow the dirty rectangle to incorporate our new bounds and pass // grow the dirty rectangle to incorporate our new bounds and pass
// the dirty region to our region manager // the dirty region to our region manager
if (_spritemgr != null) { if (_mgr != null) {
// if our new bounds intersect our old bounds, grow a single // if our new bounds intersect our old bounds, grow a single
// dirty rectangle to incorporate them both // dirty rectangle to incorporate them both
if (_bounds.intersects(dirty)) { if (_bounds.intersects(dirty)) {
dirty.add(_bounds); dirty.add(_bounds);
} else { } else {
// otherwise invalidate our new bounds separately // otherwise invalidate our new bounds separately
_spritemgr.getRegionManager().invalidateRegion(_bounds); _mgr.getRegionManager().invalidateRegion(_bounds);
} }
_spritemgr.getRegionManager().addDirtyRegion(dirty); _mgr.getRegionManager().addDirtyRegion(dirty);
} }
} }
/** // documentation inherited
* Paint the sprite to the specified graphics context.
*
* @param gfx the graphics context.
*/
public void paint (Graphics2D gfx) public void paint (Graphics2D gfx)
{ {
gfx.draw(_bounds); gfx.draw(_bounds);
@@ -302,24 +245,7 @@ public abstract class Sprite
notifyObservers(new PathCompletedEvent(this, oldpath)); notifyObservers(new PathCompletedEvent(this, oldpath));
} }
/** // documentation inherited
* 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.
*/
public void tick (long tickStamp) public void tick (long tickStamp)
{ {
// if we've a path, move the sprite along toward its destination // if we've a path, move the sprite along toward its destination
@@ -328,12 +254,7 @@ public abstract class Sprite
} }
} }
/** // documentation inherited
* 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.
*/
public void fastForward (long timeDelta) public void fastForward (long timeDelta)
{ {
// fast forward any path we're following // fast forward any path we're following
@@ -360,20 +281,7 @@ public abstract class Sprite
*/ */
public void addSpriteObserver (SpriteObserver obs) public void addSpriteObserver (SpriteObserver obs)
{ {
// create the observer list if it doesn't yet exist addObserver(obs);
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);
} }
/** /**
@@ -384,39 +292,20 @@ public abstract class Sprite
protected void notifyObservers (SpriteEvent event) protected void notifyObservers (SpriteEvent event)
{ {
if (_observers != null) { if (_observers != null) {
// we pass this notification off to the sprite manager so that _mgr.queueNotification(_observers, event);
// it can dispatch all of the notifications at once after all
// ticking has been completed
_spritemgr.notifySpriteObservers(_observers, event);
} }
} }
/** // documentation inherited
* 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.
*/
protected void toString (StringBuffer buf) 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(", oy=").append(_oy);
buf.append(", oxoff=").append(_oxoff); buf.append(", oxoff=").append(_oxoff);
buf.append(", oyoff=").append(_oyoff); buf.append(", oyoff=").append(_oyoff);
} }
/** The sprite manager. */
protected SpriteManager _spritemgr;
/** The location of the sprite's origin in pixel coordinates. If the /** The location of the sprite's origin in pixel coordinates. If the
* sprite positions itself via a hotspot that is not the upper left * sprite positions itself via a hotspot that is not the upper left
* coordinate of the sprite's bounds, the offset to the hotspot should * coordinate of the sprite's bounds, the offset to the hotspot should
@@ -429,18 +318,9 @@ public abstract class Sprite
* its bounds. */ * its bounds. */
protected int _oxoff, _oyoff; protected int _oxoff, _oyoff;
/** Our rendered bounds in pixel coordinates. */
protected Rectangle _bounds = new Rectangle();
/** The orientation of this sprite. */ /** The orientation of this sprite. */
protected int _orient = NONE; protected int _orient = NONE;
/** When moving, the path the sprite is traversing. */ /** When moving, the path the sprite is traversing. */
protected Path _path; 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; package com.threerings.media.sprite;
import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator;
import java.util.List; import java.util.List;
import com.samskivert.util.CollectionUtil; import com.threerings.media.AbstractMediaManager;
import com.samskivert.util.SortableArrayList;
import com.samskivert.util.Tuple;
import com.threerings.media.Log; import com.threerings.media.Log;
import com.threerings.media.MediaConstants;
import com.threerings.media.RegionManager; import com.threerings.media.RegionManager;
/** /**
* The sprite manager manages the sprites running about in the game. * The sprite manager manages the sprites running about in the game.
*/ */
public class SpriteManager public class SpriteManager extends AbstractMediaManager
implements MediaConstants
{ {
/** /**
* Construct and initialize the sprite manager. * Construct and initialize the sprite manager.
*/ */
public SpriteManager (RegionManager remgr) public SpriteManager (RegionManager remgr)
{ {
_sprites = new SortableArrayList(); super(remgr);
_notify = new ArrayList();
_remgr = remgr;
} }
/** /**
@@ -53,9 +43,9 @@ public class SpriteManager
*/ */
public void getIntersectingSprites (List list, Shape shape) public void getIntersectingSprites (List list, Shape shape)
{ {
int size = _sprites.size(); int size = _media.size();
for (int ii = 0; ii < size; ii++) { for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii); Sprite sprite = (Sprite)_media.get(ii);
if (sprite.intersects(shape)) { if (sprite.intersects(shape)) {
list.add(sprite); list.add(sprite);
} }
@@ -75,9 +65,9 @@ public class SpriteManager
*/ */
public void getHitSprites (List list, int x, int y) public void getHitSprites (List list, int x, int y)
{ {
int size = _sprites.size(); int size = _media.size();
for (int ii = 0; ii < size; ii++) { for (int ii = 0; ii < size; ii++) {
Sprite sprite = (Sprite)_sprites.get(ii); Sprite sprite = (Sprite)_media.get(ii);
if (sprite.hitTest(x, y)) { if (sprite.hitTest(x, y)) {
list.add(sprite); list.add(sprite);
} }
@@ -91,12 +81,10 @@ public class SpriteManager
*/ */
public void addSprite (Sprite sprite) public void addSprite (Sprite sprite)
{ {
// initialize the sprite if (insertMedia(sprite)) {
sprite.init(this); // and invalidate the sprite's original position
// add the sprite to our list sprite.invalidate();
_sprites.add(sprite); }
// and invalidate the sprite's original position
sprite.invalidate();
} }
/** /**
@@ -106,7 +94,7 @@ public class SpriteManager
*/ */
public List getSprites () public List getSprites ()
{ {
return Collections.unmodifiableList(_sprites); return Collections.unmodifiableList(_media);
} }
/** /**
@@ -117,12 +105,7 @@ public class SpriteManager
*/ */
public void removeSprite (Sprite sprite) public void removeSprite (Sprite sprite)
{ {
// invalidate the current sprite position removeMedia(sprite);
sprite.invalidate();
// remove the sprite from our list
_sprites.remove(sprite);
// clear out our manager reference
sprite.shutdown();
} }
/** /**
@@ -133,51 +116,7 @@ public class SpriteManager
*/ */
public void clearSprites () public void clearSprites ()
{ {
int size = _sprites.size(); clearMedia();
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);
}
}
} }
/** /**
@@ -187,187 +126,91 @@ public class SpriteManager
*/ */
public void renderSpritePaths (Graphics2D gfx) public void renderSpritePaths (Graphics2D gfx)
{ {
int size = _sprites.size(); for (int ii=0, nn=_media.size(); ii < nn; ii++) {
for (int ii = 0; ii < size; ii++) { Sprite sprite = (Sprite)_media.get(ii);
Sprite sprite = (Sprite)_sprites.get(ii);
sprite.paintPath(gfx); sprite.paintPath(gfx);
} }
} }
/** // documentation inherited
* Must be called every frame so that the sprites can be properly protected void dispatchEvent (ArrayList observers, Object event)
* updated. Normally a sprite manager is used in conjunction with an
* animated panel which case this is called automatically.
*/
public void tick (long tickStamp)
{ {
// tick all sprites SpriteEvent sevt = (SpriteEvent) event;
tickSprites(tickStamp); for (int ii=0, nn=observers.size(); ii < nn; ii++) {
((SpriteObserver) observers.get(ii)).handleEvent(sevt);
// 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);
} }
} }
/** // NOTE- collision handling code is turned off for now. To re-implement,
* Call {@link Sprite#tick} on all sprite objects to give them a // a new array should be kept with sprites sorted in some sort of x/y order
* chance to move themselves about, change their display image, //
* generate dirty regions and so forth. // /**
*/ // * Check all sprites for collisions with others and inform any
protected void tickSprites (long tickStamp) // * sprite observers.
{ // */
// we tick sprites in reverse order so as not to freak out if a // protected void handleCollisions ()
// sprite is removed as a result of our calling tick() on it // {
int size = _sprites.size(); // // gather a list of all sprite collisions
for (int ii = size-1; ii >= 0; ii--) { // int size = _sprites.size();
Sprite sprite = (Sprite)_sprites.get(ii); // for (int ii = 0; ii < size; ii++) {
sprite.tick(tickStamp); // Sprite sprite = (Sprite)_sprites.get(ii);
} // checkCollisions(ii, size, sprite);
} // }
// }
/** //
* Check all sprites for collisions with others and inform any // /**
* sprite observers. // * Check a sprite for collision with any other sprites in the
*/ // * sprite list and notify the sprite observers associated with any
protected void handleCollisions () // * sprites that do indeed collide.
{ // *
// gather a list of all sprite collisions // * @param idx the starting sprite index.
int size = _sprites.size(); // * @param size the total number of sprites.
for (int ii = 0; ii < size; ii++) { // * @param sprite the sprite to check against other sprites for
Sprite sprite = (Sprite)_sprites.get(ii); // * collisions.
checkCollisions(ii, size, sprite); // */
} // protected void checkCollisions (int idx, int size, Sprite sprite)
} // {
// // TODO: make this handle quickly moving objects that may pass
/** // // through each other.
* Check a sprite for collision with any other sprites in the //
* sprite list and notify the sprite observers associated with any // // if we're the last sprite we know we've already handled any
* sprites that do indeed collide. // // collisions
* // if (idx == (size - 1)) {
* @param idx the starting sprite index. // return;
* @param size the total number of sprites. // }
* @param sprite the sprite to check against other sprites for //
* collisions. // // calculate the x-position of the right edge of the sprite we're
*/ // // checking for collisions
protected void checkCollisions (int idx, int size, Sprite sprite) // Rectangle bounds = sprite.getBounds();
{ // int edgeX = bounds.x + bounds.width;
// TODO: make this handle quickly moving objects that may pass //
// through each other. // for (int ii = (idx + 1); ii < size; ii++) {
// Sprite other = (Sprite)_sprites.get(ii);
// if we're the last sprite we know we've already handled any // Rectangle obounds = other.getBounds();
// collisions // if (obounds.x > edgeX) {
if (idx == (size - 1)) { // // since sprites are stored in the list sorted by
return; // // 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
// calculate the x-position of the right edge of the sprite we're // // done.
// checking for collisions // return;
Rectangle bounds = sprite.getBounds(); //
int edgeX = bounds.x + bounds.width; // } else if (obounds.intersects(bounds)) {
// sprite.notifyObservers(new CollisionEvent(sprite, other));
for (int ii = (idx + 1); ii < size; ii++) { // }
Sprite other = (Sprite)_sprites.get(ii); // }
Rectangle obounds = other.getBounds(); // }
if (obounds.x > edgeX) { // /** The comparator used to sort sprites by horizontal position. */
// since sprites are stored in the list sorted by // protected static final Comparator SPRITE_COMP = new SpriteComparator();
// ascending x-position, we know this sprite and any //
// other sprites farther on in the list can't possibly // /** Used to sort sprites. */
// intersect with the sprite we're checking, so we're // protected static class SpriteComparator implements Comparator
// done. // {
return; // public int compare (Object o1, Object o2)
// {
} else if (obounds.intersects(bounds)) { // Sprite s1 = (Sprite)o1;
sprite.notifyObservers(new CollisionEvent(sprite, other)); // Sprite s2 = (Sprite)o2;
} // return (s2.getX() - s1.getX());
} // }
} // }
/**
* 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);
}
}
} }
@@ -1,5 +1,5 @@
// //
// $Id: SceneViewPanel.java,v 1.44 2002/09/24 07:56:25 shaper Exp $ // $Id: SceneViewPanel.java,v 1.45 2002/10/08 21:03:37 ray Exp $
package com.threerings.miso.scene; package com.threerings.miso.scene;
@@ -169,7 +169,7 @@ public class SceneViewPanel extends VirtualMediaPanel
*/ */
protected void paintBits (Graphics2D gfx, int layer, Rectangle dirty) protected void paintBits (Graphics2D gfx, int layer, Rectangle dirty)
{ {
_animmgr.renderAnimations(gfx, layer, dirty); _animmgr.renderMedia(gfx, layer, dirty);
} }
/** /**