Path: blob/master/SLICK_HOME/src/org/newdawn/slick/Animation.java
1456 views
package org.newdawn.slick;12import java.util.ArrayList;34import org.lwjgl.Sys;5import org.newdawn.slick.util.Log;67/**8* A utility to hold and render animations9*10* @author kevin11* @author DeX (speed updates)12*/13public class Animation implements Renderable {14/** The list of frames to render in this animation */15private ArrayList frames = new ArrayList();16/** The frame currently being displayed */17private int currentFrame = -1;18/** The time the next frame change should take place */19private long nextChange = 0;20/** True if the animation is stopped */21private boolean stopped = false;22/** The time left til the next frame */23private long timeLeft;24/** The current speed of the animation */25private float speed = 1.0f;26/** The frame to stop at */27private int stopAt = -2;28/** The last time the frame was automagically updated */29private long lastUpdate;30/** True if this is the first update */31private boolean firstUpdate = true;32/** True if we should auto update the animation - default true */33private boolean autoUpdate = true;34/** The direction the animation is running */35private int direction = 1;36/** True if the animation in ping ponging back and forth */37private boolean pingPong;38/** True if the animation should loop (default) */39private boolean loop = true;40/** The spriteSheet backing this animation */41private SpriteSheet spriteSheet = null;4243/**44* Create an empty animation45*/46public Animation() {47this(true);48}4950/**51* Create a new animation from a set of images52*53* @param frames The images for the animation frames54* @param duration The duration to show each frame55*/56public Animation(Image[] frames, int duration) {57this(frames, duration, true);58}5960/**61* Create a new animation from a set of images62*63* @param frames The images for the animation frames64* @param durations The duration to show each frame65*/66public Animation(Image[] frames, int[] durations) {67this(frames, durations, true);68}6970/**71* Create an empty animation72*73* @param autoUpdate True if this animation should automatically update. This means that the74* current frame will be caculated based on the time between renders75*/76public Animation(boolean autoUpdate) {77currentFrame = 0;78this.autoUpdate = autoUpdate;79}8081/**82* Create a new animation from a set of images83*84* @param frames The images for the animation frames85* @param duration The duration to show each frame86* @param autoUpdate True if this animation should automatically update. This means that the87* current frame will be caculated based on the time between renders88*/89public Animation(Image[] frames, int duration, boolean autoUpdate) {90for (int i=0;i<frames.length;i++) {91addFrame(frames[i], duration);92}93currentFrame = 0;94this.autoUpdate = autoUpdate;95}9697/**98* Create a new animation from a set of images99*100* @param frames The images for the animation frames101* @param durations The duration to show each frame102* @param autoUpdate True if this animation should automatically update. This means that the103* current frame will be caculated based on the time between renders104*/105public Animation(Image[] frames, int[] durations, boolean autoUpdate) {106this.autoUpdate = autoUpdate;107if (frames.length != durations.length) {108throw new RuntimeException("There must be one duration per frame");109}110111for (int i=0;i<frames.length;i++) {112addFrame(frames[i], durations[i]);113}114currentFrame = 0;115}116117/**118* Create a new animation based on the sprite from a sheet. It assumed that119* the sprites are organised on horizontal scan lines and that every sprite120* in the sheet should be used.121*122* @param frames The sprite sheet containing the frames123* @param duration The duration each frame should be displayed for124*/125public Animation(SpriteSheet frames, int duration) {126this(frames, 0,0,frames.getHorizontalCount()-1,frames.getVerticalCount()-1,true,duration,true);127}128129/**130* Create a new animation based on a selection of sprites from a sheet131*132* @param frames The sprite sheet containing the frames133* @param x1 The x coordinate of the first sprite from the sheet to appear in the animation134* @param y1 The y coordinate of the first sprite from the sheet to appear in the animation135* @param x2 The x coordinate of the last sprite from the sheet to appear in the animation136* @param y2 The y coordinate of the last sprite from the sheet to appear in the animation137* @param horizontalScan True if the sprites are arranged in hoizontal scan lines. Otherwise138* vertical is assumed139* @param duration The duration each frame should be displayed for140* @param autoUpdate True if this animation should automatically update based on the render times141*/142public Animation(SpriteSheet frames, int x1, int y1, int x2, int y2, boolean horizontalScan, int duration, boolean autoUpdate) {143this.autoUpdate = autoUpdate;144145if (!horizontalScan) {146for (int x=x1;x<=x2;x++) {147for (int y=y1;y<=y2;y++) {148addFrame(frames.getSprite(x, y), duration);149}150}151} else {152for (int y=y1;y<=y2;y++) {153for (int x=x1;x<=x2;x++) {154addFrame(frames.getSprite(x, y), duration);155}156}157}158}159160/**161* Creates a new Animation where each frame is a sub-image of <tt>SpriteSheet</tt> ss.162* @param ss The <tt>SpriteSheet</tt> backing this animation163* @param frames An array of coordinates of sub-image locations for each frame164* @param duration The duration each frame should be displayed for165*/166public Animation(SpriteSheet ss, int[] frames, int[] duration){167spriteSheet = ss;168int x = -1;169int y = -1;170171for(int i = 0; i < frames.length/2; i++){172x = frames[i*2];173y = frames[i*2 + 1];174addFrame(duration[i], x, y);175}176}177178/**179* Add animation frame to the animation.180* @param duration The duration to display the frame for181* @param x The x location of the frame on the <tt>SpriteSheet</tt>182* @param y The y location of the frame on the <tt>spriteSheet</tt>183*/184public void addFrame(int duration, int x, int y){185if (duration == 0) {186Log.error("Invalid duration: "+duration);187throw new RuntimeException("Invalid duration: "+duration);188}189190if (frames.isEmpty()) {191nextChange = (int) (duration / speed);192}193194frames.add(new Frame(duration, x, y));195currentFrame = 0;196}197198/**199* Indicate if this animation should automatically update based on the200* time between renders or if it should need updating via the update()201* method.202*203* @param auto True if this animation should automatically update204*/205public void setAutoUpdate(boolean auto) {206this.autoUpdate = auto;207}208209/**210* Indicate if this animation should ping pong back and forth211*212* @param pingPong True if the animation should ping pong213*/214public void setPingPong(boolean pingPong) {215this.pingPong = pingPong;216}217218/**219* Check if this animation has stopped (either explictly or because it's reached its target frame)220*221* @see #stopAt222* @return True if the animation has stopped223*/224public boolean isStopped() {225return stopped;226}227228/**229* Adjust the overall speed of the animation.230*231* @param spd The speed to run the animation. Default: 1.0232*/233public void setSpeed(float spd) {234if (spd > 0) {235// Adjust nextChange236nextChange = (long) (nextChange * speed / spd);237238speed = spd;239}240}241242/**243* Returns the current speed of the animation.244*245* @return The speed this animation is being played back at246*/247public float getSpeed() {248return speed;249}250251252/**253* Stop the animation254*/255public void stop() {256if (frames.size() == 0) {257return;258}259timeLeft = nextChange;260stopped = true;261}262263/**264* Start the animation playing again265*/266public void start() {267if (!stopped) {268return;269}270if (frames.size() == 0) {271return;272}273stopped = false;274nextChange = timeLeft;275}276277/**278* Restart the animation from the beginning279*/280public void restart() {281if (frames.size() == 0) {282return;283}284stopped = false;285currentFrame = 0;286nextChange = (int) (((Frame) frames.get(0)).duration / speed);287firstUpdate = true;288lastUpdate = 0;289}290291/**292* Add animation frame to the animation293*294* @param frame The image to display for the frame295* @param duration The duration to display the frame for296*/297public void addFrame(Image frame, int duration) {298if (duration == 0) {299Log.error("Invalid duration: "+duration);300throw new RuntimeException("Invalid duration: "+duration);301}302303if (frames.isEmpty()) {304nextChange = (int) (duration / speed);305}306307frames.add(new Frame(frame, duration));308currentFrame = 0;309}310311/**312* Draw the animation to the screen313*/314public void draw() {315draw(0,0);316}317318/**319* Draw the animation at a specific location320*321* @param x The x position to draw the animation at322* @param y The y position to draw the animation at323*/324public void draw(float x,float y) {325draw(x,y,getWidth(),getHeight());326}327328/**329* Draw the animation at a specific location330*331* @param x The x position to draw the animation at332* @param y The y position to draw the animation at333* @param filter The filter to apply334*/335public void draw(float x,float y, Color filter) {336draw(x,y,getWidth(),getHeight(), filter);337}338339/**340* Draw the animation341*342* @param x The x position to draw the animation at343* @param y The y position to draw the animation at344* @param width The width to draw the animation at345* @param height The height to draw the animation at346*/347public void draw(float x,float y,float width,float height) {348draw(x,y,width,height,Color.white);349}350351/**352* Draw the animation353*354* @param x The x position to draw the animation at355* @param y The y position to draw the animation at356* @param width The width to draw the animation at357* @param height The height to draw the animation at358* @param col The colour filter to use359*/360public void draw(float x,float y,float width,float height, Color col) {361if (frames.size() == 0) {362return;363}364365if (autoUpdate) {366long now = getTime();367long delta = now - lastUpdate;368if (firstUpdate) {369delta = 0;370firstUpdate = false;371}372lastUpdate = now;373nextFrame(delta);374}375376Frame frame = (Frame) frames.get(currentFrame);377frame.image.draw(x,y,width,height, col);378}379380/**381* Render the appropriate frame when the spriteSheet backing this Animation is in use.382* @param x The x position to draw the animation at383* @param y The y position to draw the animation at384*/385public void renderInUse(int x, int y){386if (frames.size() == 0) {387return;388}389390if (autoUpdate) {391long now = getTime();392long delta = now - lastUpdate;393if (firstUpdate) {394delta = 0;395firstUpdate = false;396}397lastUpdate = now;398nextFrame(delta);399}400401Frame frame = (Frame) frames.get(currentFrame);402spriteSheet.renderInUse(x, y, frame.x, frame.y);403}404405/**406* Get the width of the current frame407*408* @return The width of the current frame409*/410public int getWidth() {411return ((Frame) frames.get(currentFrame)).image.getWidth();412}413414/**415* Get the height of the current frame416*417* @return The height of the current frame418*/419public int getHeight() {420return ((Frame) frames.get(currentFrame)).image.getHeight();421}422423/**424* Draw the animation425*426* @param x The x position to draw the animation at427* @param y The y position to draw the animation at428* @param width The width to draw the animation at429* @param height The height to draw the animation at430*/431public void drawFlash(float x,float y,float width,float height) {432drawFlash(x,y,width,height, Color.white);433}434435/**436* Draw the animation437*438* @param x The x position to draw the animation at439* @param y The y position to draw the animation at440* @param width The width to draw the animation at441* @param height The height to draw the animation at442* @param col The colour for the flash443*/444public void drawFlash(float x,float y,float width,float height, Color col) {445if (frames.size() == 0) {446return;447}448449if (autoUpdate) {450long now = getTime();451long delta = now - lastUpdate;452if (firstUpdate) {453delta = 0;454firstUpdate = false;455}456lastUpdate = now;457nextFrame(delta);458}459460Frame frame = (Frame) frames.get(currentFrame);461frame.image.drawFlash(x,y,width,height,col);462}463464/**465* Update the animation cycle without draw the image, useful466* for keeping two animations in sync467*468* @deprecated469*/470public void updateNoDraw() {471if (autoUpdate) {472long now = getTime();473long delta = now - lastUpdate;474if (firstUpdate) {475delta = 0;476firstUpdate = false;477}478lastUpdate = now;479nextFrame(delta);480}481}482483/**484* Update the animation, note that this will have odd effects if auto update485* is also turned on486*487* @see #autoUpdate488* @param delta The amount of time thats passed since last update489*/490public void update(long delta) {491nextFrame(delta);492}493494/**495* Get the index of the current frame496*497* @return The index of the current frame498*/499public int getFrame() {500return currentFrame;501}502503/**504* Set the current frame to be rendered505*506* @param index The index of the frame to rendered507*/508public void setCurrentFrame(int index) {509currentFrame = index;510}511512/**513* Get the image assocaited with a given frame index514*515* @param index The index of the frame image to retrieve516* @return The image of the specified animation frame517*/518public Image getImage(int index) {519Frame frame = (Frame) frames.get(index);520return frame.image;521}522523/**524* Get the number of frames that are in the animation525*526* @return The number of frames that are in the animation527*/528public int getFrameCount() {529return frames.size();530}531532/**533* Get the image associated with the current animation frame534*535* @return The image associated with the current animation frame536*/537public Image getCurrentFrame() {538Frame frame = (Frame) frames.get(currentFrame);539return frame.image;540}541542/**543* Check if we need to move to the next frame544*545* @param delta The amount of time thats passed since last update546*/547private void nextFrame(long delta) {548if (stopped) {549return;550}551if (frames.size() == 0) {552return;553}554555nextChange -= delta;556557while (nextChange < 0 && (!stopped)) {558if (currentFrame == stopAt) {559stopped = true;560break;561}562if ((currentFrame == frames.size() - 1) && (!loop)) {563stopped = true;564break;565}566currentFrame = (currentFrame + direction) % frames.size();567568if (pingPong) {569if (currentFrame <= 0) {570currentFrame = 0;571direction = 1;572}573if (currentFrame >= frames.size()-1) {574currentFrame = frames.size()-1;575direction = -1;576}577}578int realDuration = (int) (((Frame) frames.get(currentFrame)).duration / speed);579nextChange = nextChange + realDuration;580}581}582583/**584* Indicate if this animation should loop or stop at the last frame585*586* @param loop True if this animation should loop (true = default)587*/588public void setLooping(boolean loop) {589this.loop = loop;590}591592/**593* Get the accurate system time594*595* @return The system time in milliseconds596*/597private long getTime() {598return (Sys.getTime() * 1000) / Sys.getTimerResolution();599}600601/**602* Indicate the animation should stop when it reaches the specified603* frame index (note, not frame number but index in the animation604*605* @param frameIndex The index of the frame to stop at606*/607public void stopAt(int frameIndex) {608stopAt = frameIndex;609}610611/**612* Get the duration of a particular frame613*614* @param index The index of the given frame615* @return The duration in (ms) of the given frame616*/617public int getDuration(int index) {618return ((Frame) frames.get(index)).duration;619}620621/**622* Set the duration of the given frame623*624* @param index The index of the given frame625* @param duration The duration in (ms) for the given frame626*/627public void setDuration(int index, int duration) {628((Frame) frames.get(index)).duration = duration;629}630631/**632* Get the durations of all the frames in this animation633*634* @return The durations of all the frames in this animation635*/636public int[] getDurations() {637int[] durations = new int[frames.size()];638for (int i=0;i<frames.size();i++) {639durations[i] = getDuration(i);640}641642return durations;643}644645646/**647* @see java.lang.Object#toString()648*/649public String toString() {650String res = "[Animation ("+frames.size()+") ";651for (int i=0;i<frames.size();i++) {652Frame frame = (Frame) frames.get(i);653res += frame.duration+",";654}655656res += "]";657return res;658}659660/**661* Create a copy of this animation. Note that the frames662* are not duplicated but shared with the original663*664* @return A copy of this animation665*/666public Animation copy() {667Animation copy = new Animation();668669copy.spriteSheet = spriteSheet;670copy.frames = frames;671copy.autoUpdate = autoUpdate;672copy.direction = direction;673copy.loop = loop;674copy.pingPong = pingPong;675copy.speed = speed;676677return copy;678}679680/**681* A single frame within the animation682*683* @author kevin684*/685private class Frame {686/** The image to display for this frame */687public Image image;688/** The duration to display the image fro */689public int duration;690/** The x location of this frame on a SpriteSheet*/691public int x = -1;692/** The y location of this frame on a SpriteSheet*/693public int y = -1;694695/**696* Create a new animation frame697*698* @param image The image to display for the frame699* @param duration The duration in millisecond to display the image for700*/701public Frame(Image image, int duration) {702this.image = image;703this.duration = duration;704}705706/**707* Creates a new animation frame with the frames image location on a sprite sheet708* @param duration The duration in millisecond to display the image for709* @param x the x location of the frame on the <tt>SpriteSheet</tt>710* @param y the y location of the frame on the <tt>SpriteSheet</tt>711*/712public Frame(int duration, int x, int y) {713this.image = spriteSheet.getSubImage(x, y);714this.duration = duration;715this.x = x;716this.y = y;717}718}719}720721722