
com.threerings.media.sprite.ImageSprite Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nenya Show documentation
Show all versions of nenya Show documentation
Facilities for making networked multiplayer games.
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.media.sprite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.threerings.media.AbstractMedia;
import com.threerings.media.image.Mirage;
import com.threerings.media.util.MultiFrameImage;
import com.threerings.media.util.SingleFrameImageImpl;
/**
* Extends the sprite class to support rendering the sprite with one or
* more frames of image animation. Overrides various methods to provide
* correspondingly desirable functionality, e.g., {@link #hitTest} only
* reports a hit if the specified point is within a non-transparent pixel
* for the sprite's current image frame.
*/
public class ImageSprite extends Sprite
{
/** Default frame rate. */
public static final int DEFAULT_FRAME_RATE = 15;
/** Animation mode indicating no animation. */
public static final int NO_ANIMATION = 0;
/** Animation mode indicating movement cued animation. */
public static final int MOVEMENT_CUED = 1;
/** Animation mode indicating time based animation. */
public static final int TIME_BASED = 2;
/** Animation mode indicating sequential progressive animation.
* Frame 0 is guaranteed to be shown first for the full duration, and
* so on. */
public static final int TIME_SEQUENTIAL = 3;
/**
* Constructs an image sprite without any associated frames and with an invalid default initial
* location. The sprite should be populated with a set of frames used to display it via a
* subsequent call to {@link #setFrames}, and its location updated with
* {@link AbstractMedia#setLocation}.
*/
public ImageSprite ()
{
this((MultiFrameImage)null);
}
/**
* Constructs an image sprite.
*
* @param frames the multi-frame image used to display the sprite.
*/
public ImageSprite (MultiFrameImage frames)
{
// initialize frame animation member data
_frames = frames;
_frameIdx = 0;
_animMode = NO_ANIMATION;
_frameDelay = 1000L/DEFAULT_FRAME_RATE;
}
/**
* Constructs an image sprite that will display the supplied single image when rendering itself.
*/
public ImageSprite (Mirage image)
{
this(new SingleFrameImageImpl(image));
}
@Override
protected void init ()
{
super.init();
// now that we have our sprite manager, we can lay ourselves out and initialize our frames
layout();
}
/**
* Returns true if the sprite's bounds contain the specified point, and if there is a
* non-transparent pixel in the sprite's image at the specified point, false if not.
*/
@Override
public boolean hitTest (int x, int y)
{
// first check to see that we're in the sprite's bounds and that we've got a frame image
// (if we've got no image, there's nothing to be hit)
if (!super.hitTest(x, y) || _frames == null) {
return false;
}
return _frames.hitTest(_frameIdx, x - _bounds.x, y - _bounds.y);
}
/**
* Sets the animation mode for this sprite. The available modes are:
*
*
* - {@link #TIME_BASED}: cues the animation based on a target
* frame rate (specified via {@link #setFrameRate}).
* - {@link #MOVEMENT_CUED}: ticks the animation to the next
* frame every time the sprite is moved along its path.
* - {@link #NO_ANIMATION}:
* disables animation.
*
*
* @param mode the desired animation mode.
*/
public void setAnimationMode (int mode)
{
_animMode = mode;
}
/**
* Sets the number of frames per second desired for the sprite animation. This is only used
* when the animation mode is TIME_BASED
.
*
* @param fps the desired frames per second.
*/
public void setFrameRate (float fps)
{
_frameDelay = (long)(1000/fps);
}
/**
* Set the image to be used for this sprite.
*/
public void setMirage (Mirage mirage)
{
setFrames(new SingleFrameImageImpl(mirage));
}
/**
* Set the image array used to render the sprite.
*
* @param frames the sprite images.
*/
public void setFrames (MultiFrameImage frames)
{
if (frames == null) {
// log.warning("Someone set up us the null frames!", "sprite", this);
return;
}
// if these are the same frames we already had, no need to do a bunch of pointless business
if (frames == _frames) {
return;
}
// set and init our frames
_frames = frames;
_frameIdx = 0;
layout();
}
/**
* Instructs this sprite to lay out its current frame and any accoutrements.
*/
public void layout ()
{
if (_frames != null) {
setFrameIndex(_frameIdx, true);
}
}
/**
* Instructs the sprite to display the specified frame index.
*/
protected void setFrameIndex (int frameIdx, boolean forceUpdate)
{
// make sure we're displaying a valid frame
frameIdx = (frameIdx % _frames.getFrameCount());
// if this is the same frame we're already displaying and we're
// not being forced to update, we can stop now
if (frameIdx == _frameIdx && !forceUpdate) {
return;
} else {
_frameIdx = frameIdx;
}
// start with our old bounds
Rectangle dirty = new Rectangle(_bounds);
// determine our drawing offsets and rendered rectangle size
accomodateFrame(_frameIdx, _frames.getWidth(_frameIdx),
_frames.getHeight(_frameIdx));
// add our new bounds
dirty.add(_bounds);
// give the dirty rectangle to the region manager
if (_mgr != null) {
_mgr.getRegionManager().addDirtyRegion(dirty);
}
}
/**
* Must adjust the bounds to accommodate the our new frame. This includes changing the width
* and height to reflect the size of the new frame and also updating the render origin (if
* necessary) and calling {@link Sprite#updateRenderOrigin} to reflect those changes in the
* sprite's bounds.
*
* @param frameIdx the index of our new frame.
* @param width the width of the new frame.
* @param height the height of the new frame.
*/
protected void accomodateFrame (int frameIdx, int width, int height)
{
_bounds.width = width;
_bounds.height = height;
}
@Override
public void paint (Graphics2D gfx)
{
if (_frames != null) {
// // DEBUG: fill our background with an alpha'd rectangle
// Composite ocomp = gfx.getComposite();
// gfx.setComposite(ALPHA_BOUNDS);
// gfx.setColor(Color.blue);
// gfx.fill(_bounds);
// gfx.setComposite(ocomp);
// render our frame
_frames.paintFrame(gfx, _frameIdx, _bounds.x, _bounds.y);
} else {
super.paint(gfx);
}
}
@Override
public void tick (long timestamp)
{
// if we have no frames, we're hosulated (to use a Greenwell term)
if (_frames == null) {
return;
}
int fcount = _frames.getFrameCount();
boolean moved = false;
// move the sprite along toward its destination, if any
moved = tickPath(timestamp);
// increment the display image if performing image animation
int nfidx = _frameIdx;
switch (_animMode) {
case NO_ANIMATION:
// nothing doing
break;
case TIME_BASED:
nfidx = (int)((timestamp/_frameDelay) % fcount);
break;
case TIME_SEQUENTIAL:
if (_firstStamp == 0L) {
_firstStamp = timestamp;
}
nfidx = (int) (((timestamp - _firstStamp) / _frameDelay) % fcount);
break;
case MOVEMENT_CUED:
// update the frame if the sprite moved
if (moved) {
nfidx = (_frameIdx + 1) % fcount;
}
break;
}
// update our frame (which will do nothing if this is the same as
// our existing frame index)
setFrameIndex(nfidx, false);
}
@Override
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", fidx=").append(_frameIdx);
}
/** The images used to render the sprite. */
protected MultiFrameImage _frames;
/** The current frame index to render. */
protected int _frameIdx;
/** What type of animation is desired for this sprite. */
protected int _animMode;
/** For how many milliseconds to display an animation frame. */
protected long _frameDelay;
/** The first timestamp seen (in TIME_SEQUENTIAL mode). */
protected long _firstStamp = 0L;
// /** DEBUG: The alpha level used when rendering our bounds. */
// protected static final Composite ALPHA_BOUNDS =
// AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy