1

I’m trying to create some animation ( boy who runs whenever key is pressed) using JPanel and painComponent. So, first of all I declare some images, Image array and some methods for drawing. I created timer and added it to constructor.

Image img1;
Image img2;
Image img3;
Image img4;

int index = 0;

Image currentImage l;
Image[] images = new Image[4]

public class Animation extends JPanel implements ActionListener
{

    public Animation()
    {
     loadImages();
     getTimer();
     imgAdder();
     addFirstImage();
    }

    public void serCurrentImage(Image currentImage)
  {
     this.currentImage = currentImage;
   }
     public void getTimer()
   {
     Timer timer = new Timer(20,this)
      timer.start();
   }
     public void imgAdder()
   {
     images[1] = img1;
        ...
   }
     public void addFirstImage()
   {
      currentImage = img1;
   }
      private void loadImages()
   {
       try
   {
      BufferedImage img = ImageIO.read(getClass.getResource(“/resources/images/img1.png”);
      img1 = img;
      // and so on for every image
    }catch(IOexception ioe )
     ioe.printStackTrace();
    }
  }
     public void paintComponent (Graphics g)
     {
           super.paintComponent(g);
           g.drewImage(currentImage,0,0,this);
           requestsFocus();
      }
            public class FieldKeyListener extends KeyAdapter
    {
            public void move()
      {
            setCurrentImage(image[index]);
             index++;
             if( index == 4 )
                      index = 0;
       }
             public void keyPressed(KeyEvent e)
       {
             super.keyPressed(e);
             int key = e.getKeyCode();
             if(key == Key.Event.VK_LEFT)
             move();
       }
     }
}

Then drew all images through paintComponent using loop for my array. Also I declared class which extends KeyAdapter. Everything seems to be fine and my animation works, but problem is that it works not as smoothly as I wanted. When I press and hold key, images are changing too fast and process look unnatural. I want , for instance , 3 or 4 images change per second instead of 20. May I added timer in wrong method ? May be there is something like time delay. I don’t know how exactly it works , and which listener should I mention as argument in timer. P.s. I’m just beginner and my code may look incorrectly in terms of coding standards. Also I wrote just crucial parts of my project which represent problem. I hope you help me with this. Thanks in advance.

Gipsy King
  • 186
  • 2
  • 2
  • 14
  • You might have a look at [this answer](https://gamedev.stackexchange.com/a/160331) regarding game loops. – Michael Krause Jan 27 '19 at 22:43
  • The problem you're having is one of timing. That is, animation should be treated as a change over time. It becomes worse when you have multiple different objects wanting to be animated at different rates. You might consider having a look at [this example](https://stackoverflow.com/questions/35472233/load-a-sprites-image-in-java/35472418#35472418) which defines a "cycle" period and allows sprites to be animated through that cycle independently (so a sprite might repeat once, twice, three times in cycle, what ever you need) – MadProgrammer Jan 27 '19 at 22:46
  • Calling `requestsFocus();` in `paintComponent` is actually a bad idea, to get around this "hack" you should be using the [Key bindings API](https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) instead of `KeyListener` and it will solve your problems automatically – MadProgrammer Jan 27 '19 at 22:49
  • Another thing. You have `Timer`, which I assume is acting as "main loop", but you call `move` within the `KeyListener`, this is a bad idea. The "main loop" should, on each cycle, update the state of the model, based on the current state of the inputs, and schedule a paint pass. Instead of calling `move` directly, you should use a `Set` of some kind and store the active inputs, this overcomes the issues with the way the OS deals with repeated keys – MadProgrammer Jan 27 '19 at 22:52
  • @MadProgrammer sorry, I haven’t exactly got an idea with set.. I created setter within my outer class, how should setter interact with KeyListener expect method move. Might’ve you meant set as another feature ? – Gipsy King Jan 28 '19 at 01:25
  • `java.util.Set` is a type `List` which guarantees uniqueness, so, while a key is pressed you can keep adding the key stroke to it and it won’t have repeated values, makes things simple – MadProgrammer Jan 28 '19 at 01:49

1 Answers1

1

Animation is a complex subject, with lots of boring theory. Basically, animation is the illusion of change over time. This is very important, as everything you do in animation will based around time.

In something like a game, you will have a bunch of entities all playing at a different rates of time. One of the challenges is taking the time to devise a solution which allows a entity to play over a period of time while been decoupled from the refresh cycle (ie frame count), unless you have sprite with the correct number of frames to match you refresh cycle, but even then, I'd be concerned, as the system won't be flexible enough to adapt to situations where the OS and hardware can't keep up.

The following is a simple example which takes a sprite sheet (a series of images stored in a single image), the number of expected images/frames and the time to complete a full cycle.

It calculates the individual frame size and returns a frame based on the amount of time that the sprite has been animated...

public class Sprite {

    private BufferedImage source;
    private int imageCount;
    private int imageWidth;

    // How long it takes to play a full cycle
    private Duration duration;
    // When the last cycle was started
    private Instant startedAt;

    public Sprite(BufferedImage source, int imageCount, int cycleTimeInSeconds) throws IOException {
        this.source = source;
        this.imageCount = imageCount;
        imageWidth = source.getWidth() / imageCount;
        duration = Duration.ofSeconds(cycleTimeInSeconds);
    }

    public BufferedImage getFrame() {
        if (startedAt == null) {
            startedAt = Instant.now();
        }
        Duration timePlayed = Duration.between(startedAt, Instant.now());
        double progress = timePlayed.toMillis() / (double)duration.toMillis();
        if (progress > 1.0) {
            progress = 1.0;
            startedAt = Instant.now();
        }
        int frame = Math.min((int)(imageCount * progress), imageCount - 1);
        return getImageAt(frame);
    }

    protected BufferedImage getImageAt(int index) {
        if (index < 0 || index >= imageCount) {
            return null;
        }
        int xOffset = imageWidth * index;
        return source.getSubimage(xOffset, 0, imageWidth, source.getHeight());
    }

}

nb: It also needs a means to be reset or stopped, so you can force the sprite back to the start, but I'll leave that to you

Next, we need some way to play the animation

public class TestPane extends JPanel {

    private Sprite sprite;

    public TestPane(Sprite sprite) {
        this.sprite = sprite;
        Timer timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                repaint();
            }
        });
        timer.start();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(400, 400);
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        BufferedImage img = sprite.getFrame();
        int x = (getWidth() - img.getWidth()) / 2;
        int y = (getHeight() - img.getHeight()) / 2;
        g2d.drawImage(img, x, y, this);
        g2d.dispose();
    }

}

There's nothing really special here, it's a simple Swing Timer set to a high resolution (5 milliseconds) which constantly updates the UI, requesting the next frame from the sprite and painting it.

The important part here is the sprite and the refresh cycle are independent. Want the character to walk faster, change the sprite duration, want the character walk slower, changed the sprite duration, the refresh cycle doesn't need be altered (or any other entity)

So, starting with...

Sprite Sheet

Same cycle, first over 1 second, second over 5 seconds

FastwalkSlowWalk

You can also have a look at something like How to create a usable KeyReleased method in java, which demonstrates the use of key bindings and a centralised Set as a "action" repository

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I thought I can deal with this easily, but I was wrong :) it’s too early to play around doing that stuff, cause everything seems confusing to me. As I understand it clearly , compiler get image from frame , but where is setter for that Image ? How JVM know which image to use? – Gipsy King Jan 28 '19 at 23:14
  • In my code, there is no "setter" for the image, the image is passed via the constructor, as there should be no need to change the image after the `Sprite` object is created. The "frame to use" is based on a number of inputs; the amount of time which has passed since the animation was started; the number of available frames. From this, the `Sprite` class can make a determination about the which "frame" to return, this makes it very dynamic – MadProgrammer Jan 28 '19 at 23:56
  • @VladimirLabliuk The example above attempts to answer the question, "how to handle variable animation cycles". The example is running at roughly 200fps, but the walk animation actually wants to take about 1 second to play (200 frames), but you might have other animation that needs 0.5 second or 5 seconds or some other value. The example attempts to decouple the fps from the animation cycle in a re-usable way. The example is not perfect and needs some tweaking for your needs, personally, I'd like to pass the duration as a double (in seconds), so you could have a 0.5 second animation ;) – MadProgrammer Jan 29 '19 at 00:07
  • Ok I got it. When I set frame properties, like JFrame frame = new JFrame(“/resources/images/img1.png”), I should mention image as an argument – Gipsy King Jan 29 '19 at 03:54