Strange stuttering on moving objects

Hi,

I’ve started to mess around with Java 2D and making a demo of sprite animations, I noticed something weird. I have an object that has two frames of animation which I move frome left to right and right to left when it hits the borders of the window. If I let this run without touching the keyboard, the object stutters a lot while moving in it’s designated path. If I hit any button and keep pressing it, the animation becomes silky smooth. How can that be? I thought this was caused by my use of a KeyAdapter to check key presses, so I changed this. But I still get the stuttering.

I’m running this on Java SDK 7, for your information. Here’s my current code. I’m aware that this is far from perfect but this behavior is very strange to me. (sorry I’m still new to Java)

Anybody knows what is causing this?


package metroidvania.test;

import metroidvania.enums.Dir;
import metroidvania.enums.Direction;
import metroidvania.objects.RipperII;
import metroidvania.objects.Samus;
import metroidvania.utils.SpriteSheetLoader;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Map;

public class AnimationTest extends JPanel {

    private static final String PRESSED = "pressed";
    private static final String RELEASED = "released";

    private Timer timer;
    private ArrayList<Image[]> samusSpriteAnimations;
    private ArrayList<Image[]> ripperSpriteAnimations;
    private Samus samus;
    private RipperII ripper;
    private Map<Dir, Boolean> dirMap = new EnumMap<>(Dir.class);
    private boolean lastRKeyPress = false;

    private int zoomFactor = 1;

    private int ripperPosX = 600;
    private int ripperPosY = 240;
    private boolean ripperActivated = true;

    public AnimationTest()
    {
        setFocusable(true);
        setBackground(Color.BLACK);
        setDoubleBuffered(true);

        setKeyBindings();

        initialize();

        timer = new Timer(5, new AnimationListener());
        timer.start();
    }

    private void setKeyBindings() {
        for (Dir dir : Dir.values()) {
            dirMap.put(dir, Boolean.FALSE);
        }

        int condition = WHEN_IN_FOCUSED_WINDOW;
        InputMap inputMap = getInputMap(condition);
        ActionMap actionMap = getActionMap();

        for (Dir dir : Dir.values()) {
            KeyStroke keyPressed = KeyStroke.getKeyStroke(dir.getKeyCode(), 0, false);
            KeyStroke keyReleased = KeyStroke.getKeyStroke(dir.getKeyCode(), 0, true);

            inputMap.put(keyPressed, dir.toString() + PRESSED);
            inputMap.put(keyReleased, dir.toString() + RELEASED);

            actionMap.put(dir.toString() + PRESSED, new DirAction(dir, PRESSED));
            actionMap.put(dir.toString() + RELEASED, new DirAction(dir, RELEASED));
        }

    }

    public void initialize()
    {
        samus = new Samus();
        ripper = new RipperII();
        samusSpriteAnimations = new ArrayList<Image[]>();
        ripperSpriteAnimations = new ArrayList<Image[]>();
        loadSprites();
    }

    private void loadSprites() {
        //samus
        addAnimationFrames(samusSpriteAnimations, "E:\\Java Source\\Metroidvania\\Images\\Samus Standing Left.png", 26, 43, 1, 4); //Idle left
        addAnimationFrames(samusSpriteAnimations, "E:\\Java Source\\Metroidvania\\Images\\Samus Standing Right.png", 26, 43, 1, 4); //Idle right
        addAnimationFrames(samusSpriteAnimations, "E:\\Java Source\\Metroidvania\\Images\\Samus Turning Left.png", 24, 46, 1, 3); //Turning left
        addAnimationFrames(samusSpriteAnimations, "E:\\Java Source\\Metroidvania\\Images\\Samus Turning Right.png", 24, 46, 1, 3); //Turning right
        addAnimationFrames(samusSpriteAnimations, "E:\\Java Source\\Metroidvania\\Images\\Samus Running Left.png", 35, 43, 1, 10); //Running left
        addAnimationFrames(samusSpriteAnimations, "E:\\Java Source\\Metroidvania\\Images\\Samus Running Right.png", 35, 43, 1, 10); //Running left

        //ripper
        addAnimationFrames(ripperSpriteAnimations, "E:\\Java Source\\Metroidvania\\Images\\Ripper II Left.png", 35, 14, 1, 2); //Running left
        addAnimationFrames(ripperSpriteAnimations, "E:\\Java Source\\Metroidvania\\Images\\Ripper II Right.png", 35, 14, 1, 2); //Running left
    }

    private void addAnimationFrames(ArrayList<Image[]> spriteAnimations, String spriteSheetFilename, int width, int height, int rows, int columns)
    {
        SpriteSheetLoader ssl = new SpriteSheetLoader(spriteSheetFilename, width, height, rows, columns);
        spriteAnimations.add(ssl.sprites());
    }

    private class DirAction extends AbstractAction {

        private String pressedOrReleased;
        private Dir dir;

        public DirAction(Dir dir, String pressedOrReleased) {
            this.dir = dir;
            this.pressedOrReleased = pressedOrReleased;
        }

        @Override
        public void actionPerformed(ActionEvent evt) {
            if (pressedOrReleased.equals(PRESSED)) {
                dirMap.put(dir, Boolean.TRUE);
            } else if (pressedOrReleased.equals(RELEASED)) {
                dirMap.put(dir, Boolean.FALSE);
            }
        }
    }

    private class AnimationListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            samus.validateKeyPresses(dirMap);

            for(Dir dir : Dir.values())
            {
                if(dir.getKeyCode() == KeyEvent.VK_1 && dirMap.get(dir))
                    zoomFactor = 1;
                else if(dir.getKeyCode() == KeyEvent.VK_2 && dirMap.get(dir))
                    zoomFactor = 2;
                else if(dir.getKeyCode() == KeyEvent.VK_3 && dirMap.get(dir))
                    zoomFactor = 3;
                else if(dir.getKeyCode() == KeyEvent.VK_4 && dirMap.get(dir))
                    zoomFactor = 4;
                else if(dir.getKeyCode() == KeyEvent.VK_5 && dirMap.get(dir))
                    zoomFactor = 5;
                else if(dir.getKeyCode() == KeyEvent.VK_R)
                {
                    if(dirMap.get(dir)) if(!lastRKeyPress) ripperActivated = !ripperActivated;
                    lastRKeyPress = dirMap.get(dir);
                }

            }

            if(ripperActivated)
            {
                if(ripper.getDirection() == Direction.LEFT)
                {
                    if(ripperPosX > 0)
                        ripperPosX--;
                    else{
                        ripper.setDirection(Direction.RIGHT);
                        ripperPosX++;
                    }
                }
                else
                {
                    if(ripperPosX < (640 - 35))
                        ripperPosX++;
                    else {
                        ripper.setDirection(Direction.LEFT);
                        ripperPosX--;
                    }
                }
            }

            repaint();
        }
    }

    public void paint(Graphics g)
    {
        super.paint(g);

        Graphics2D g2d = (Graphics2D) g;

        String s = "Current frame: " + Integer.toString(samus.getCurrentFrame());
        Font small = new Font("Helvetica", Font.BOLD, 14);

        g2d.setColor(Color.white);
        g2d.setFont(small);
        g2d.drawString(s, 1, 20);

        Image frameImage = samusSpriteAnimations.get(samus.getCurrentAnimation().getCode())[samus.getCurrentFrame()];
        g2d.drawImage(frameImage, 50, 50, frameImage.getWidth(this) * zoomFactor, frameImage.getHeight(this) * zoomFactor, this);

        if(ripperActivated) {
            if (ripper.getDirection() == Direction.LEFT)
                frameImage = ripperSpriteAnimations.get(0)[ripper.getCurrentFrame()];
            else
                frameImage = ripperSpriteAnimations.get(1)[ripper.getCurrentFrame()];

            g2d.drawImage(frameImage, ripperPosX, ripperPosY, frameImage.getWidth(this) * zoomFactor, frameImage.getHeight(this) * zoomFactor, this);
        }
    }
}