All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.cast.CharacterSprite Maven / Gradle / Ivy

The newest version!
//
// 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