
com.threerings.media.sprite.Sprite Maven / Gradle / Ivy
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// https://github.com/threerings/nenya
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.sprite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import com.samskivert.util.ObserverList;
import com.threerings.util.DirectionCodes;
import com.threerings.media.AbstractMedia;
import com.threerings.media.util.Path;
import com.threerings.media.util.Pathable;
/**
* The sprite class represents a single movable object in an animated view. A sprite has a
* position and orientation within the view, and can be moved along a path.
*/
public abstract class Sprite extends AbstractMedia
implements DirectionCodes, Pathable
{
/**
* Constructs a sprite with an initially invalid location. Because sprite derived classes
* generally want to get in on the business when a sprite's location is set, it is not safe to
* do so in the constructor because their derived methods will be called before their
* constructor has been called. Thus a sprite should be fully constructed and then its
* location should be set.
*/
public Sprite ()
{
this(0, 0);
}
/**
* Constructs a sprite with the supplied dimensions. Because sprite derived classes generally
* want to get in on the business when a sprite's location is set, it is not safe to do so in
* the constructor because their derived methods will be called before their constructor has
* been called. Thus a sprite should be fully constructed and then its location should
* be set.
*/
public Sprite (int width, int height)
{
super(new Rectangle(0, 0, width, height));
}
/**
* Returns the sprite's x position in screen coordinates. This is the x coordinate of the
* sprite's origin, not the upper left of its bounds.
*/
public int getX ()
{
return _ox;
}
/**
* Returns the sprite's y position in screen coordinates. This is the y coordinate of the
* sprite's origin, not the upper left of its bounds.
*/
public int getY ()
{
return _oy;
}
/**
* Returns the offset to the sprite's origin from the upper-left of the sprite's image.
*/
public int getXOffset ()
{
return _oxoff;
}
/**
* Returns the offset to the sprite's origin from the upper-left of the sprite's image.
*/
public int getYOffset ()
{
return _oyoff;
}
/**
* Returns the sprite's width in pixels.
*/
public int getWidth ()
{
return _bounds.width;
}
/**
* Returns the sprite's height in pixels.
*/
public int getHeight ()
{
return _bounds.height;
}
/**
* Sprites have an orientation in one of the eight cardinal directions:
* {@link DirectionCodes#NORTH}, {@link DirectionCodes#NORTHEAST}, etc. 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.
*
* @see DirectionCodes
*/
public void setOrientation (int orient)
{
_orient = orient;
}
/**
* Returns the sprite's orientation as one of the eight cardinal directions:
* {@link DirectionCodes#NORTH}, {@link DirectionCodes#NORTHEAST}, etc.
*
* @see DirectionCodes
*/
public int getOrientation ()
{
return _orient;
}
@Override
public void setLocation (int x, int y)
{
if (x == _ox && y == _oy) {
return; // no-op
}
// make a note of our current bounds
Rectangle obounds = new Rectangle(_bounds);
// move ourselves
_ox = x;
_oy = y;
// we need to update our draw position which is based on the size of our current bounds
updateRenderOrigin();
// this method will invalidate our old and new bounds efficiently
invalidateAfterChange(obounds);
}
@Override
public void paint (Graphics2D gfx)
{
gfx.drawRect(_bounds.x, _bounds.y, _bounds.width-1, _bounds.height-1);
}
/**
* Paint the sprite's path, if any, to the specified graphics context.
*
* @param gfx the graphics context.
*/
public void paintPath (Graphics2D gfx)
{
if (_path != null) {
_path.paint(gfx);
}
}
/**
* Returns true if the sprite's bounds contain the specified point, false if not.
*/
public boolean contains (int x, int y)
{
return _bounds.contains(x, y);
}
/**
* Returns true if the sprite's bounds contain the specified point, false if not.
*/
public boolean hitTest (int x, int y)
{
return _bounds.contains(x, y);
}
/**
* Returns whether the sprite is inside the given shape in pixel coordinates.
*/
public boolean inside (Shape shape)
{
return shape.contains(_ox, _oy);
}
/**
* Returns whether the sprite's drawn rectangle intersects the given shape in pixel coordinates.
*/
public boolean intersects (Shape shape)
{
return shape.intersects(_bounds);
}
/**
* Returns true if this sprite is currently following a path, false if it is not.
*/
public boolean isMoving ()
{
return (_path != 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)
{
// if there's a previous path, let it know that it's going away
cancelMove();
// save off this path
_path = path;
// we'll initialize it on our next tick thanks to a zero path stamp
_pathStamp = 0;
}
/**
* Cancels any path that the sprite may currently be moving along.
*/
public void cancelMove ()
{
if (_path != null) {
Path oldpath = _path;
_path = null;
oldpath.wasRemoved(this);
if (_observers != null) {
_observers.apply(new CancelledOp(this, oldpath));
}
}
}
/**
* Returns the path being followed by this sprite or null if the sprite is not following a path.
*/
public Path getPath ()
{
return _path;
}
/**
* Called by the active path when it begins.
*/
public void pathBeginning ()
{
// nothing for now
}
/**
* Called by the active path when it has completed.
*/
public void pathCompleted (long timestamp)
{
Path oldpath = _path;
_path = null;
oldpath.wasRemoved(this);
if (_observers != null) {
_observers.apply(new CompletedOp(this, oldpath, timestamp));
}
}
@Override
public void tick (long tickStamp)
{
tickPath(tickStamp);
}
/**
* Ticks any path assigned to this sprite.
*
* @return true if the path relocated the sprite as a result of this tick, false if it
* remained in the same position.
*/
protected boolean tickPath (long tickStamp)
{
if (_path == null) {
return false;
}
// initialize the path if we haven't yet
if (_pathStamp == 0) {
_path.init(this, _pathStamp = tickStamp);
}
// it's possible that as a result of init() the path completed and removed itself with a
// call to pathCompleted(), so we have to be careful here
return (_path == null) ? true : _path.tick(this, tickStamp);
}
@Override
public void fastForward (long timeDelta)
{
// fast forward any path we're following
if (_path != null) {
_path.fastForward(timeDelta);
}
}
/**
* Update the coordinates at which the sprite image is drawn to reflect the sprite's current
* position.
*/
protected void updateRenderOrigin ()
{
// our bounds origin may differ from the sprite's origin
_bounds.x = _ox - _oxoff;
_bounds.y = _oy - _oyoff;
}
/**
* Add a sprite observer to observe this sprite's events.
*
* @param obs the sprite observer.
*/
public void addSpriteObserver (Object obs)
{
addObserver(obs);
}
/**
* Remove a sprite observer.
*/
public void removeSpriteObserver (Object obs)
{
removeObserver(obs);
}
@Override
public void viewLocationDidChange (int dx, int dy)
{
if (_renderOrder >= HUD_LAYER) {
setLocation(_ox + dx, _oy + dy);
}
}
@Override
protected void shutdown ()
{
super.shutdown();
cancelMove(); // cancel any active path
}
@Override
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", ox=").append(_ox);
buf.append(", oy=").append(_oy);
buf.append(", oxoff=").append(_oxoff);
buf.append(", oyoff=").append(_oyoff);
}
/** Used to dispatch {@link PathObserver#pathCancelled}. */
protected static class CancelledOp implements ObserverList.ObserverOp
© 2015 - 2025 Weber Informatics LLC | Privacy Policy