Java-Gaming.org Hi !
Featured games (81)
games approved by the League of Dukes
Games in Showcase (513)
Games in Android Showcase (119)
games submitted by our members
Games in WIP (576)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Strip animation class  (Read 3093 times)
0 Members and 1 Guest are viewing this topic.
Offline idFade

Junior Newbie





« Posted 2011-06-15 11:06:15 »

I've been reading through a book by David Brackeen recently, this is a modified version of his Animation class that I edited. Feel free to improve further ^^
You would still have to type out that monster Graphics2D.drawImage(5 000 000 arguments); statement when drawing the image though, but it removes a lot of the strip animation code from your rendering loop.

To draw the image you would do something like this
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
protected Animation mainChar = new Animation("/images/spritesheet.png", 50, 100, 1, 3, 100, 0, 2);

g2d.drawImage(mainChar.getImage(), mCharPosX, mCharPosY,
            mCharPosX+mainChar.getFrameWidth(),
            mCharPosY+mainChar.getFrameHeight(),
            mainChar.getFrameX(),
            mainChar.getFrameY(),
            mainChar.getFrameX()+mainChar.getFrameWidth(),
            mainChar.getFrameY()+mainChar.getFrameHeight(),
            null);


[CODE]
/**
 * @param String stripname - the name of the strip image
 * @param Integer framewidth - the width of an individual frame in pixels
 * @param Integer frameheight - the height of an individual frame in pixels
 * @param Integer rows - the number of rows the strip has
 * @param Integer columns - the number of columns the strip has
 * @param Long frametime - how long each frame will be shown
 * @param Integer startpos - This is which frame in the strip image the animation will start from
 * @param Integer endpos - You guessed it! The frame on the strip image the animation will end at and loop back to the startpos
 *
 * @version 0.005
 *<br />
 *Date created: 2011-06-13
 *<br />
 *Date updated: 2011-06-14
 */
public class Animation {

   private int frameWidth, frameHeight;
   private int rows, cols;
   private int totFrames;
   private int currFrameIndex;
   protected int frameX, frameY;
   private long animTime;
   private long totalDuration;
   private long frameTime;
   private int startPos;
   private int endPos;
   protected boolean moving;
   private int updateCount;
   
   protected Image stripImage;
   
   
   //Creates a new empty Animation
   public Animation(String stripname, int framewidth, int frameheight, int row, int col, long frametime, int startpos, int endpos){
      frameWidth = framewidth;
      frameHeight = frameheight;
      rows = row;
      cols = col;
      totFrames = cols*rows;
      totalDuration = 0;
      frameTime = frametime;
      stripImage = loadImage(stripname);
      startPos = startpos;
      endPos = endpos;
      start();
   }
   
   private Image loadImage(String fileName){
      return new ImageIcon(getClass().getResource(fileName)).getImage();
   }
   
   /**
    * Starts the animation over from the beginning
    */
   public synchronized void start(){
      animTime = 0;
      currFrameIndex = startPos;
   }
   
   /**
    * Updates this animation's current image(frame), if necessary
    */
   public synchronized void update(long elapsedTime){
      updateCount++;
      
      if(totFrames > 1){
         animTime += elapsedTime;
         totalDuration += frameTime;
         
         if(updateCount >= frameTime/20){
            if(currFrameIndex >= endPos+1){
               currFrameIndex = startPos;
            }
         
            frameX = (currFrameIndex % cols) * frameWidth;
            frameY = (currFrameIndex / cols) * frameHeight;
         
            if(moving){
               currFrameIndex++;
            }
            updateCount = 0;
         }
      }
   }
   
   //Gets the image lol
   public synchronized int getIndex(){
      if(totFrames == 0){
         return 0;
      }else{
         return currFrameIndex;
      }
   }
   
   public Image getImage(){
      return stripImage;
   }
   
   public void setMoving(boolean b){ moving = b; }
   
   public synchronized int getFrameX(){ return frameX; }
   public synchronized int getFrameY(){ return frameY; }
   public synchronized int getFrameWidth(){ return frameWidth; }
   public synchronized int getFrameHeight(){return frameHeight; }
   
}[/CODE]

Hope this will help some of you ^^

Edit: Whoops, forgot to update the params
Offline ra4king

JGO Kernel


Medals: 350
Projects: 3
Exp: 5 years


I'm the King!


« Reply #1 - Posted 2011-06-16 00:29:29 »

The update method is not reliable. Lots of thoughts:
1. How does the updateCount relate to the frameTime? It makes no sense since the elapsed time could be anything. Instead you should update based on the animTime.
2. totalDuration is pointless, I don't see it used anywhere except just adding the frameTime, which is also not correct since the frameTime is not the time that has passed.
3. You are not getting frameY correctly, it should be "frameY = (currFrameIndex % rows) * frameHeight;"
4. The getIndex() method only needs 1 line: "return currFrameIndex;" since currFrameIndex is already 0 if the totFrames = 0.
5. If you don't want to have that massive drawImage call, your getImage method should only return the frame that is shown. You do this by creating a new BufferedImage and using that massive drawImage method there. All the user would have to do is call "g.drawImage(anim.getImage(),x,y,null);"

Offline idFade

Junior Newbie





« Reply #2 - Posted 2011-06-16 10:14:08 »

Thank you for the feedback ra4king, I will try to answer your questions the best I can ^^

1. Each time the update method is called from the class that calls it, it adds one to updateCount. The FrameTime is how long you want each frame to display, I added the if(updateCount >= frameTime/20) to stop the animation from updating index once every 20 ms. The elapsedTime and animTime variables are leftovers from the original Animation class used in david brackeens book, I should actually remove them since they serves no purpose.

2. Also a leftover from the original class, I have been sloppy removing em it seems.

3. I believe the (currFrameIndex / cols) * frameHeight is indeed correct since it returns the correct frames for any animation I run. When I first learned strip animation it was by the formulas shown in this class. Might be multiple ways to get the coordinates in the strip though.
 /*Edit: I tried with frameY = (currFrameIndex % rows) * frameHeight, it couldn't get the y position and kept looping row 1 on the strip image. */

4. Thank you for pointing that out, I will change it in my class right away ^^

5. I was thinking about building a draw method in the class and tuck that massive drawImage method there, but your idea sounds like a better solution. Quick question: Would there be any major performance difference if a use BufferedImages instead of regular Images? I have only used BufferedImages for backbuffring, I have never stored image files in them.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline ra4king

JGO Kernel


Medals: 350
Projects: 3
Exp: 5 years


I'm the King!


« Reply #3 - Posted 2011-06-17 01:04:23 »

1. Oh, your update method depends on a fixed game loop running at 50FPS. However, since game loops may not be reliable and the elapsed time could vary which would cause a non-fluid animation, it is best if you keep the elapsedTime and animTime variables and remove updateCount variable altogether:
1  
2  
3  
4  
5  
6  
7  
8  
public void update(long deltaTime) {
   if(totFrames <= 1)
      return;
   
   animTime = (animTime+deltaTime)%(totFrames*frameTime);
   
   currentFrameIndex = animTime/frameTime;
}

You could also remove the frameX and frameY variables, since the getImage() method will take care of the slicing of the whole image.
Take a look at the getImage() method at bullet #5.

3. Aha, I just looked at the math again and you are correct, I was wrong. You get the column from the remainder and the row number from the division.

5. You can't instantiate an object of type Image. BufferedImage is the only class which you may instantiate directly. However it is more recommended to get a compatible image from GraphicsConfiguration:
1  
2  
3  
4  
5  
6  
7  
8  
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();

//There are two different createCompatibleImage methods:
BufferedImage compatibleImage = gc.createCompatibleImage(width,height);
//and
BufferedImage compatibleImage = gc.createCompatibleImage(width,height,transparency);

Make sure to look at the javadocs: http://ra4king.is-a-geek.net/javadocs/java/awt/GraphicsConfiguration.html

And now for an example of how your getImage() should look:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
public BufferedImage getImage() {
   BufferedImage image = GraphicsEnvironment.getLocalGraphicsEnvironment()
                                  .getDefaultScreenDevice()
                                  .getDefaultConfiguration()
                                  .createCompatibleImage(frameWidth,frameHeight,stripImage.getTransparency());
   
   int frameX = (currentFrameIndex % cols) * frameWidth;
   int frameY = (currentFrameIndex / cols) * frameHeight;
   
   Graphics g = image.getGraphics();
   g.drawImage(stripImage,0,0,frameWidth,frameHeight,frameX,frameY,frameX+frameWidth,frameY+frameHeight,null);
   g.dispose();
   
   return image;
}

Offline aazimon
« Reply #4 - Posted 2011-06-17 22:44:59 »

I would recommend moving the loading of your image outside the animation class. Have the Animation class take an Image object. That way you can reuse the image object on different animations. It will save memory. This is my version of the Animation class from the same book.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
public class LoopedAnimationStrip implements Animation {
   private boolean animating;
   private List<AnimatedFrame> frames = new ArrayList<AnimatedFrame>();
   private int totalFrames = 0;
   private int currentFrameIndex = 0;
   // total time of animation.
   private long totalDuration = 0;
   private long sequenceTime = 0;
   private int height;
   private int width;
   private int xOffset = 0;
   private int yOffset = 0;
   private String name;

   public LoopedAnimationStrip(Image image, long duration) {
      totalDuration = duration;
      totalFrames = 1;
      frames.add(new AnimatedFrame(image, duration));
      height = image.getHeight(null);
      width = image.getWidth(null);
      reset();
   }

   public synchronized void addFrame(Image image, long duration) {
      totalDuration += duration;
      totalFrames++;
      frames.add(new AnimatedFrame(image, totalDuration));
      if (height == 0 && width == 0) {
         height = image.getHeight(null);
         width = image.getWidth(null);
      }
   }

   public synchronized void reset() {
      currentFrameIndex = 0;
      sequenceTime = 0;
   }

   public synchronized void update(long elapseTime) {
      // Add the elapseTime to the sequence time and get the total sequence time.
      sequenceTime += elapseTime;
      // If the sequence time is greater than the total duration time,
      // reset the loop to the begining.
      if (sequenceTime >= totalDuration) {
         // get the "left over" time from the total time.
         sequenceTime = sequenceTime % totalDuration;
         currentFrameIndex = 0;
      }
      // if we have exceeded the frame's end time, advance to the next frame.
      while (sequenceTime > getAnimatedFrame(currentFrameIndex).getEndTime()) {
         currentFrameIndex++;
      }
   }

   public synchronized Image getImage() {
      return getAnimatedFrame(currentFrameIndex).getImage();
   }
   public void draw(int x, int y) {
      int xL = x + xOffset;
      int yL = y + yOffset;
      GameGFX.gfx().drawImage(getAnimatedFrame(currentFrameIndex).getImage(), xL, yL, null);
   }

   public Image getFrame(int i) {
      return ((AnimatedFrame) frames.get(i)).getImage();
   }

   private AnimatedFrame getAnimatedFrame(int i) {
      return (AnimatedFrame) frames.get(i);
   }

   public int getHeight() {
      return height;
   }

   public int getWidth() {
      return width;
   }

   public void setOffsets(int x, int y) {
      xOffset = x;
      yOffset = y;
   }

   public boolean isAnimating() {
      return true;
   }

   public void setName(String name) {
      this.name = name;
   }
}


I use an interface so I can have different implementation, such as an Animation that goes through the sequence once (for deaths and such). Also, I store my current graphics in a static object which is created from the main loop when I update the buffer.
Offline ra4king

JGO Kernel


Medals: 350
Projects: 3
Exp: 5 years


I'm the King!


« Reply #5 - Posted 2011-06-17 23:37:25 »

My Animation class is quite similar with an addFrame method that takes an image and duration. However, your draw method is weird....you get your Graphics object through a singleton? That is a big no no. It is best if you pass the Graphics object to the draw method.

Offline zoto

Senior Duke


Medals: 4



« Reply #6 - Posted 2011-06-18 02:04:32 »

@ra4king
It's a static function, not necessarily a singleton, I don't see the relevant code to say one way or the other. I agree it looks a lot uglier than having the graphics object passed in tho.

@aazimon
You declared the getImage() function but didn't use it in your draw, this function will help make the draw more readable.

There is no reason to typecast your frames.get calls to an AnimatedFrame because they already are AnimatedFrame's.

Offline ra4king

JGO Kernel


Medals: 350
Projects: 3
Exp: 5 years


I'm the King!


« Reply #7 - Posted 2011-06-18 12:37:44 »

Ah yes my mistake, it is a static method not a singleton, I didn't look very closely. Grin

Offline aazimon
« Reply #8 - Posted 2011-06-18 13:22:39 »

The draw method allows for an image to be larger then the given cell. Say the image is a tree, I set the base of the tree at a specific cell, and the top of the tree can extend outside the tree. Since it is at the specific cell, there is not collision detection for the branches at the top of the tree. The values passed in are the cell location which comes from the sprite, and the internal values are adjustments for the image itself.

Not the graphics object is not a singleton. It is taken from the canvas. I set it in the static element. I got the idea reading through the LWJGL. It just a convenience, so I don't have to pass the Graphics around everywhere.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
   private void gameRender() {
      if (GameGFX.dbImage == null) {
         GameGFX.dbImage = createImage(this.getWidth(), this.getHeight());
         if (GameGFX.dbImage == null) {
            throw new NullPointerException("Double buffer image is null.");
         }
      }
      // clear the background
      GameGFX.gfx().setColor(Color.white);
      GameGFX.gfx().fillRect(0, 0, this.getWidth(), this.getHeight());

      // draw game elements
      gameMap.draw();

      if (gameOver) {
         gameOverMessge();
      }
   }


The getImage method is old code, and I don't use it anymore. Sorry I didn't remove it.
Offline idFade

Junior Newbie





« Reply #9 - Posted 2011-06-18 15:05:46 »

@ra4king: Thanks for the suggestions, I will play around a bit with your code there and try to improve my animation class with it ^^ Also, lol@ "http://ra4king.is-a-geek.net", I wish I had made a domain with such a name XD

@aazimon: When I created my animation class I tried to focus on the object logic I guess you could call it. You know all those examples in books:
"A dog object has a weight, so we'll give it a weight.
private int weight;"

That's the reason I stored the Image inside the animation class, however, your idea sounds better, and nicer to the memory, so I will probably change the Animation class a bit ^^


Thanks for the feedback everyone! ^^
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline zoto

Senior Duke


Medals: 4



« Reply #10 - Posted 2011-06-18 20:57:39 »

I just meant turn
1  
GameGFX.gfx().drawImage(getAnimatedFrame(currentFrameIndex).getImage(), xL, yL, null);

into
1  
GameGFX.gfx().drawImage(getImage(), xL, yL, null);


I would advise keeping the getImage function, it will create a nice function to overwrite if you ever want to expand on your animations later, like supporting layered animations for example.
Offline ra4king

JGO Kernel


Medals: 350
Projects: 3
Exp: 5 years


I'm the King!


« Reply #11 - Posted 2011-06-18 22:44:27 »

@aazimon
LWJGL and Java2D are two completely different systems. In LWJGL, you use static methods to access OpenGL functions because you only have 1 OpenGL "context" so to say. It doesn't make sense to create multiple OpenGL objects.
However in Java2D, each Graphics object is unique. Setting the Graphics object in a static method not only is quite ugly and non-OO, it may produce synchronization issues where the Graphics object used is the wrong one.

Offline aazimon
« Reply #12 - Posted 2011-06-19 14:15:30 »

@zoto
I see what you're saying. Why didn't I think of that.
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Longarmx (37 views)
2014-10-17 03:59:02

Norakomi (28 views)
2014-10-16 15:22:06

Norakomi (24 views)
2014-10-16 15:20:20

lcass (27 views)
2014-10-15 16:18:58

TehJavaDev (52 views)
2014-10-14 00:39:48

TehJavaDev (54 views)
2014-10-14 00:35:47

TehJavaDev (42 views)
2014-10-14 00:32:37

BurntPizza (64 views)
2014-10-11 23:24:42

BurntPizza (36 views)
2014-10-11 23:10:45

BurntPizza (77 views)
2014-10-11 22:30:10
Understanding relations between setOrigin, setScale and setPosition in libGdx
by mbabuskov
2014-10-09 22:35:00

Definite guide to supporting multiple device resolutions on Android (2014)
by mbabuskov
2014-10-02 22:36:02

List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!