
com.threerings.cast.CharacterSprite 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.cast;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.SwingUtilities;
import com.threerings.media.sprite.ImageSprite;
import static com.threerings.cast.Log.log;
/**
* A character sprite is a sprite that animates itself while walking about in a scene.
*/
public class CharacterSprite extends ImageSprite
implements StandardActions
{
/**
* Initializes this character sprite with the specified character descriptor and character
* manager. It will obtain animation data from the supplied character manager.
*/
public void init (CharacterDescriptor descrip, CharacterManager charmgr)
{
// keep track of this stuff
_descrip = descrip;
_charmgr = charmgr;
// sanity check our values
sanityCheckDescrip();
// assign an arbitrary starting orientation
_orient = SOUTHWEST;
// pass the buck to derived classes
didInit();
}
/**
* Called after this sprite has been initialized with its character descriptor and character
* manager. Derived classes can do post-init business here.
*/
protected void didInit ()
{
}
/**
* Reconfigures this sprite to use the specified character descriptor.
*/
public void setCharacterDescriptor (CharacterDescriptor descrip)
{
// keep the new descriptor
_descrip = descrip;
// sanity check our values
sanityCheckDescrip();
// update our action frames
updateActionFrames();
}
/**
* Specifies the action to use when the sprite is at rest. The default is
* STANDING
.
*/
public void setRestingAction (String action)
{
_restingAction = action;
}
/**
* Returns the action to be used when the sprite is at rest. Derived classes may wish to
* override this method and vary the action based on external parameters (or randomly).
*/
public String getRestingAction ()
{
return _restingAction;
}
/**
* Specifies the action to use when the sprite is following a path. The default is
* WALKING
.
*/
public void setFollowingPathAction (String action)
{
_followingPathAction = action;
}
/**
* Returns the action to be used when the sprite is following a path. Derived classes may wish
* to override this method and vary the action based on external parameters (or randomly).
*/
public String getFollowingPathAction ()
{
return _followingPathAction;
}
/**
* Sets the action sequence used when rendering the character, from the set of available
* sequences.
*/
public void setActionSequence (String action)
{
// sanity check
if (action == null) {
log.warning("Refusing to set null action sequence " + this + ".", new Exception());
return;
}
// no need to noop
if (action.equals(_action)) {
return;
}
_action = action;
updateActionFrames();
}
@Override
public void setOrientation (int orient)
{
if (orient < 0 || orient >= FINE_DIRECTION_COUNT) {
log.info("Refusing to set invalid orientation",
"sprite", this, "orient", orient, new Exception());
return;
}
int oorient = _orient;
super.setOrientation(orient);
if (_orient != oorient) {
_frames = null;
}
}
@Override
public boolean hitTest (int x, int y)
{
// the irect adjustments are to account for our decorations
return (_frames != null && _ibounds.contains(x, y) &&
_frames.hitTest(_frameIdx, x - _ibounds.x, y - _ibounds.y));
}
@Override
public void tick (long tickStamp)
{
// composite our action frames if something since the last call to
// tick caused them to become invalid
compositeActionFrames();
super.tick(tickStamp);
// composite our action frames if something during tick() caused them to become invalid
compositeActionFrames();
}
@Override
public void cancelMove ()
{
super.cancelMove();
halt();
}
@Override
public void pathBeginning ()
{
super.pathBeginning();
// enable walking animation
setAnimationMode(TIME_BASED);
setActionSequence(getFollowingPathAction());
}
@Override
public void pathCompleted (long timestamp)
{
super.pathCompleted(timestamp);
halt();
}
@Override
public void paint (Graphics2D gfx)
{
if (_frames != null) {
decorateBehind(gfx);
// paint the image using _ibounds rather than _bounds which
// has been modified to include the bounds of our decorations
_frames.paintFrame(gfx, _frameIdx, _ibounds.x, _ibounds.y);
decorateInFront(gfx);
} else {
super.paint(gfx);
}
}
/**
* Called to paint any decorations that should appear behind the character sprite image.
*/
protected void decorateBehind (Graphics2D gfx)
{
}
/**
* Called to paint any decorations that should appear in front of the character sprite image.
*/
protected void decorateInFront (Graphics2D gfx)
{
}
/**
* Rebuilds our action frames given our current character descriptor and action sequence. This
* is called when either of those two things changes.
*/
protected void updateActionFrames ()
{
// get a reference to the action sequence so that we can obtain
// our animation frames and configure our frames per second
ActionSequence actseq = _charmgr.getActionSequence(_action);
if (actseq == null) {
String errmsg = "No such action '" + _action + "'.";
throw new IllegalArgumentException(errmsg);
}
try {
// obtain our animation frames for this action sequence
_aframes = _charmgr.getActionFrames(_descrip, _action);
// clear out our frames so that we recomposite on next tick
_frames = null;
// update the sprite render attributes
setFrameRate(actseq.framesPerSecond);
} catch (NoSuchComponentException nsce) {
log.warning("Character sprite references non-existent component",
"sprite", this, "err", nsce);
} catch (Exception e) {
log.warning("Failed to obtain action frames",
"sprite", this, "descrip", _descrip, "action", _action, e);
}
}
/** Called to recomposite our action frames if needed. */
protected final void compositeActionFrames ()
{
if (_frames == null && _aframes != null) {
setFrames(_aframes.getFrames(_orient));
}
}
/**
* Makes it easier to track down problems with bogus character descriptors.
*/
protected void sanityCheckDescrip ()
{
if (_descrip.getComponentIds() == null ||
_descrip.getComponentIds().length == 0) {
log.warning("Invalid character descriptor [sprite=" + this +
", descrip=" + _descrip + "].", new Exception());
}
}
@Override
protected boolean tickPath (long tickStamp)
{
boolean moved = super.tickPath(tickStamp);
// composite our action frames if our path caused them to become invalid
compositeActionFrames();
return moved;
}
@Override
protected void updateRenderOrigin ()
{
super.updateRenderOrigin();
// adjust our image bounds to reflect the new location
_ibounds.x = _bounds.x + _ioff.x;
_ibounds.y = _bounds.y + _ioff.y;
}
@Override
protected void accomodateFrame (int frameIdx, int width, int height)
{
// this will update our width and height
super.accomodateFrame(frameIdx, width, height);
// we now need to update the render offset for this frame
if (_aframes == null) {
log.warning("Have no action frames! " + _aframes + ".");
} else {
_oxoff = _aframes.getXOrigin(_orient, frameIdx);
_oyoff = _aframes.getYOrigin(_orient, frameIdx);
}
// and cause those changes to be reflected in our bounds
updateRenderOrigin();
// start out with our bounds the same as our image bounds
_ibounds.setBounds(_bounds);
// now we can call down and incorporate the dimensions of any
// decorations that will be rendered along with our image
unionDecorationBounds(_bounds);
// compute our new render origin
_oxoff = _ox - _bounds.x;
_oyoff = _oy - _bounds.y;
// track the offset from our expanded bounds to our image bounds
_ioff.x = _ibounds.x - _bounds.x;
_ioff.y = _ibounds.y - _bounds.y;
}
/**
* Called by {@link #accomodateFrame} to give derived classes an opportunity to incorporate
* the bounds of any decorations that will be drawn along with this sprite. The
* {@link #_ibounds} rectangle will contain the bounds of the image that comprises the
* undecorated sprite. From that the position and size of decorations can be computed and
* unioned with the supplied bounds rectangle (most likely by using
* {@link SwingUtilities#computeUnion} which applies the union in place rather than creating a
* new rectangle).
*/
protected void unionDecorationBounds (Rectangle bounds)
{
}
/**
* Updates the sprite animation frame to reflect the cessation of movement and disables any
* further animation.
*/
protected void halt ()
{
// only do something if we're actually animating
if (_animMode != NO_ANIMATION) {
// disable animation
setAnimationMode(NO_ANIMATION);
// come to a halt looking settled and at peace
String rest = getRestingAction();
if (rest != null) {
setActionSequence(rest);
}
}
}
/** The action to use when at rest. */
protected String _restingAction = STANDING;
/** The action to use when following a path. */
protected String _followingPathAction = WALKING;
/** A reference to the descriptor for the character that we're visualizing. */
protected CharacterDescriptor _descrip;
/** A reference to the character manager that created us. */
protected CharacterManager _charmgr;
/** The action we are currently displaying. */
protected String _action;
/** The animation frames for the active action sequence in each orientation. */
protected ActionFrames _aframes;
/** The offset from the upper-left of the total sprite bounds to the upper-left of the image
* within those bounds. */
protected Point _ioff = new Point();
/** The bounds of the current sprite image. */
protected Rectangle _ibounds = new Rectangle();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy