2

So, I have been working on a 2d rpg for some time now and I can't seem to fix this one problem. The graphics seem to "jump" or stutter every few seconds for an unknown reason. This is getting quite annoying because I don't know what is causing it.

Here is a very basic program I wrote that just has a red square that moves from one side of the screen to the other side. Even in this very basic program the square still stutters every few updates and I really can't figure it out for the life of me.

public class Main extends JPanel {

int x=0, y=0;

public JFrame window = new JFrame("Window");

public Main(){
    window.setSize(1000, 500);
    window.setVisible(true);
    window.add(this);
}

public void paintComponent(Graphics g){
    super.paintComponent(g);
    g.setColor(Color.red);
    g.fillRect(x, y, 500, 500);
    x+=3;
    if(x>900){
        x=0;
    }
}

public void start(){
    while(true){
        repaint();
        try {
            Thread.sleep(16);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] args){
    Main game = new Main();

    game.start();
}

}

If you run the class you will see what the problem is graphically. Obviously my game is made up of many more classes and is far more complex than this, but the same principal applies. If any one has any insight to my problem I would love to hear it. Thanks in advance.

Updated

Here are my two main classes:

Main Class:
package com.ultimatum.Main;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;

import com.ultimatum.Mangers.ImageLoader;
import com.ultimatum.Mangers.KeyStates;
import com.ultimatum.Mangers.ScreenUpdater;
import com.ultimatum.Mangers.UserInput;

@SuppressWarnings("serial")
public class Ultimatum extends JPanel {

    /**
     * @param x This is the start width of the screen and can be adjusted manually, but will not go any lower than this integer
     * @param y This is the start height of the screen and can be adjusted manually, but will not go any lower than this integer
     * @param contentPlaneX This is how much the width of the content plane is (Frame-Borders)
     * @param contentPlaneY This is how much the height of the content plane is (Frame-Borders)
     */
    public int x=850, y=610, contentPlaneX, contentPlaneY, middleX, middleY, tileSize=90;

    public Dimension minimumSize = new Dimension(x, y);

    public JFrame window = new JFrame("Ultimatum");//This makes the JFrame for the game
    public KeyStates keyStates = new KeyStates();
    public UserInput input = new UserInput(keyStates);
    public ImageLoader imageLoader = new ImageLoader();
    public static Ultimatum ultimatum;//Makes the object of this class
    public static ScreenUpdater su;//This is creating the object that is going to be making changes to the screen. For example, the animation.
    private BufferedImage screenImage;

    public boolean isWindowInFullscreenMode;

    private boolean imagesLoaded;

    public void initializeUltimatum(){
        toWindowedMode();

        addMouseListener(input);
        addMouseMotionListener(input);

        contentPlaneX=window.getContentPane().getWidth();
        contentPlaneY=window.getContentPane().getHeight();
        middleX=(int)contentPlaneX/2;
        middleY=(int)contentPlaneY/2;
        su = new ScreenUpdater(ultimatum, keyStates, imageLoader, "Test", tileSize);

        imageLoader.loadImages();
    }

    public void toFullscreenMode(){
        window.dispose();
        window.setUndecorated(true);
        window.setVisible(true);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setBounds(0,0,Toolkit.getDefaultToolkit().getScreenSize().width,Toolkit.getDefaultToolkit().getScreenSize().height);
        addListenersAndClassToWindow();
        isWindowInFullscreenMode=true;
    }

    public void toWindowedMode(){
        window.dispose();
        window.setUndecorated(false);
        window.setSize(x,y);
        window.setMinimumSize(minimumSize);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setVisible(true);
        window.setLocationRelativeTo(null);
        addListenersAndClassToWindow();
        isWindowInFullscreenMode=false;
    }

    public void addListenersAndClassToWindow(){
        window.add(ultimatum);//This connects paintComponent and the frame to this class
        window.addKeyListener(input);
    }

    public void paintComponent(Graphics g){
        if(imagesLoaded){
            super.paintComponent(g);
            //su.updateScreen(g);
            g.drawImage(screenImage, 0, 0, contentPlaneX, contentPlaneY, null);
        }else imagesLoaded = true;
    }

    public void update(){
        screenImage = su.updateScreen();
    }

    /**
     * This main class sets up the program. The while loop that keeps the game running is also contained inside this class. Most of this class is easily
     * readable so i'm not going to comment that much.
     */
    public static void main(String[] args){
        ultimatum = new Ultimatum();
        ultimatum.initializeUltimatum();

        final int FPS=60, TARGET_TIME=1000/FPS;

        long start, elapsed, wait;

        while(true){//This loops purpose is to keep the game running smooth on all computers 
            start = System.nanoTime();

            ultimatum.update();
            ultimatum.repaint();//This calls the paintComponent method

            elapsed = System.nanoTime() - start;

            wait = TARGET_TIME-elapsed/1000000;
            if(wait<0) wait = TARGET_TIME;


        try{//Catches the error in case the tries to give an error (which it won't)
            Thread.sleep(wait);//This is how long it waits it till the screen gets repainted
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
}

Screen Updater:

package com.ultimatum.Mangers;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;

import com.ultimatum.Engine.BuildingGenerator;
import com.ultimatum.Engine.TextBoxGenerator;
import com.ultimatum.Entities.Character.Player;
import com.ultimatum.Gamestates.Buildings.HealingCenter;
import com.ultimatum.Gamestates.Menus.EscapeScreen;
import com.ultimatum.Gamestates.Menus.StartScreen;
import com.ultimatum.Gamestates.Menus.TitleScreen;
import com.ultimatum.Gamestates.Routes.RouteSuperClass;
import com.ultimatum.Gamestates.Towns.TownSuperClass;
import com.ultimatum.Main.Ultimatum;

public class ScreenUpdater {

    public Ultimatum ul;
    public Resizer rs;//This is the object that captures the resize in two integers
    public KeyStates ks;
    public ImageLoader loader;
    public Fader fader;
    public TextBoxGenerator textBox;
    public Initializer initer;
    public TileMap tm;
    public Player p;
    public BuildingGenerator bg;

    //Menus
    public TitleScreen titleScreen;
    public StartScreen startScreen;
    public EscapeScreen escScreen;

    //Towns
    public TownSuperClass towns;

    //Routes
    public RouteSuperClass routes;

    //Buildings
    public HealingCenter healingCenter;

    public final int TITLE_SCREEN=0, START_SCREEN=TITLE_SCREEN+1, LOAD=START_SCREEN+1, TOWN_ONE=LOAD+1, 
            ROUTE_ONE=TOWN_ONE+1, HEALING_CENTER=ROUTE_ONE+1, ESC_MENU=HEALING_CENTER+1;

    public int screenNo = TITLE_SCREEN;

    public int prevScreen=0;
    public boolean prevMenuState, menuState;//These variables are for the checkEsc method
    public boolean isMouseVisible=true, prevIsMouseVisible;//Simple boolean for setting the mouse from visible to invisible and vice versa

    public ScreenUpdater(Ultimatum ultimatum, KeyStates keyStates, ImageLoader imageloader, String location, 
            int tileSize){

        ul = ultimatum;
        ks = keyStates;
        loader = imageloader;
        fader = new Fader(ul, this);
        textBox = new TextBoxGenerator(loader, ks, ul);
        initer = new Initializer(fader, textBox);
        fader.sendIniterData(initer);

        p = new Player(ul, fader, loader, ks, initer, this);
        fader.sendPlayerData(p);

        tm = new TileMap(tileSize, loader, p);
        fader.sendTileMapData(tm);

        rs = new Resizer(ul, p);

        bg = new BuildingGenerator(ul, p, loader, tm);

        //Below are the game states being loaded

        //Menus
        titleScreen = new TitleScreen(ul, this, loader, ks, fader);
        startScreen = new StartScreen(ul, this, fader, loader, ks, textBox);
        escScreen = new EscapeScreen(ul, fader, loader, ks);
        rs.sendEscapeScreenData(escScreen);

        //Towns
        towns = new TownSuperClass(p, fader, bg, tm, this);

        //Routes
        routes = new RouteSuperClass(p, fader, bg, tm, this);

        //Buildings
        healingCenter = new HealingCenter(ul, fader, loader, ks, textBox);
    }

    public void clearScreen(Graphics g){
        g.setColor(Color.black);
        g.fillRect(0, 0, ul.contentPlaneX, ul.contentPlaneY);
    }

    public void checkEsc(Graphics g){
        if(ks.escReleased&&screenNo>LOAD&&!fader.fadingOut&&fader.fadingIn){
            if(screenNo<HEALING_CENTER&&!p.isMoving){


        menuState=true;
            prevScreen=screenNo;
        }
        else if(screenNo==ESC_MENU) menuState=false;
    }

    if(prevMenuState!=menuState){
        int toScreen;
        boolean mouseVisiblity;
        if(menuState){
            toScreen=ESC_MENU;
            mouseVisiblity=true;
        }
        else{
            toScreen=prevScreen;
            mouseVisiblity=false;
        }

        fader.FadeOut(g, 255, toScreen, false, "", 0, 0, false, 0, mouseVisiblity);//The zeros don't matter because the boolean is set to false
        if(!fader.fadingOut){
            prevMenuState=menuState;
            initer.initFader();
        }
    }
}

public void checkForF11(){
    if(ks.isF11PressedThenReleased){
        if(ul.isWindowInFullscreenMode) ul.toWindowedMode();
        else ul.toFullscreenMode();
    }
}

public void setMouseVisible(){
    ul.window.setCursor(ul.window.getToolkit().createCustomCursor(loader.cursor, new Point(0, 0),"Visible"));
}

public void setMouseInvisble(){
    ul.window.setCursor(ul.window.getToolkit().createCustomCursor(new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB), new Point(0, 0),"Clear"));
}

public void checkMouseState(){
    if(isMouseVisible!=prevIsMouseVisible){
        if(isMouseVisible) setMouseVisible();
        else setMouseInvisble();
        prevIsMouseVisible=isMouseVisible;
    }
}

public BufferedImage updateScreen(){
    BufferedImage screenImage = new BufferedImage(ul.contentPlaneX, ul.contentPlaneY, BufferedImage.TYPE_INT_ARGB);
    Graphics2D screenGraphics = screenImage.createGraphics();
    Color oldColor = screenGraphics.getColor();
    screenGraphics.setPaint(Color.white);
    screenGraphics.fillRect(0, 0, ul.contentPlaneX, ul.contentPlaneY);
    screenGraphics.setColor(oldColor);

    checkForF11();
    clearScreen(screenGraphics);
    switch(screenNo){
        case TITLE_SCREEN:
            titleScreen.titleScreen(screenGraphics);
            break;
        case START_SCREEN:
            startScreen.startScreen(screenGraphics);
            break;
        case TOWN_ONE:
            towns.townOne(screenGraphics);
            break;
        case ROUTE_ONE:
            routes.routeOne(screenGraphics);
            break;
        case HEALING_CENTER:
            healingCenter.healingCenter(screenGraphics);
            break;
        case ESC_MENU:
            escScreen.escapeScreen(screenGraphics);
            break;
    }
    checkEsc(screenGraphics);
    rs.checkForResize();
    ks.update();
    checkMouseState();

    //g.drawImage(screenImage, 0, 0, ul.contentPlaneX, ul.contentPlaneY, null);
    //screenGraphics.dispose();
    return screenImage;
}
}
Indie Man
  • 21
  • 4

1 Answers1

4

Don't update the state in the paintComponent method, painting can happen for any number reasons, many of which you don't initiate or will be notified about. Instead, the state should only be updated by your "main loop"

See Painting in AWT and Swing for more details about how painting works in Swing

Updated

Swing Timer based solution...

The example allows you to animate 1-10, 000 sprites, each sprite moves and spins independently. Obviously, I don't have collision detection, but the animation as a whole moves well

Lots of ponies

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                PaintPane pane = new PaintPane();

                JSlider slider = new JSlider(1, 10000);
                slider.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        try {
                            pane.setQuantity(slider.getValue());
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }
                    }
                });
                slider.setValue(1);

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(pane);
                frame.add(slider, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class PaintPane extends JPanel {

        private static final int SPOOL_DELTA = 100;

        private List<Sprite> pool;
        private List<Sprite> sprites;
        private int quantity;

        public PaintPane() {
            try {
                BufferedImage img = ImageIO.read(getClass().getResource("/resources/Pony.png"));

                pool = new ArrayList<>(128);
                sprites = new ArrayList<>(128);
                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {

                        if (sprites.size() < quantity) {
                            List<Sprite> toAdd = new ArrayList<>(SPOOL_DELTA);
                            int required = quantity - sprites.size();
                            if (pool.isEmpty()) {
                                for (int index = 0; index < Math.min(SPOOL_DELTA, required); index++) {
                                    int x = (int)(Math.random() * getWidth());
                                    int y = (int)(Math.random() * getHeight());
                                    toAdd.add(new Sprite(img, new Point(x, y)));
                                }
                            } else {
                                toAdd.addAll(pool.subList(0, Math.min(SPOOL_DELTA, pool.size())));
                                pool.removeAll(toAdd);
                            }
                            sprites.addAll(toAdd);
                        } else if (sprites.size() > quantity) {
                            List<Sprite> toRemove = new ArrayList<>(SPOOL_DELTA);
                            int required = sprites.size() - quantity;
                            if (sprites.size() > required) {
                                toRemove.addAll(sprites.subList(0, Math.min(SPOOL_DELTA, required)));
                                sprites.removeAll(toRemove);
                                pool.addAll(toRemove);
                            }
                        }

                        for (Sprite sprite : sprites) {
                            sprite.update(getSize());
                        }
                        repaint();
                    }
                });
                timer.start();
            } catch (IOException ex) {
                ex.printStackTrace();
            }

            setFont(getFont().deriveFont(Font.BOLD, 18f));
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            for (Sprite sprite : sprites) {
                sprite.draw(g2d, this);
            }
            String text = NumberFormat.getNumberInstance().format(sprites.size());
            FontMetrics fm = g2d.getFontMetrics();
            int x = getWidth() - fm.stringWidth(text);
            int y = (getHeight() - fm.getHeight()) + fm.getAscent();
            g2d.drawString(text, x, y);
            g2d.dispose();
        }

        public void setQuantity(int value) throws IOException {
            this.quantity = value;
        }

    }

    public static class Sprite {

        private BufferedImage img;
        private Point location;
        private double angle;

        private Point delta;
        private double angleDelta;

        public Sprite(BufferedImage cache, Point location) {
            img = cache;
            this.location = new Point(location);
            delta = new Point(rnd(), rnd());
            while (angleDelta == 0) {
                angleDelta = (Math.random() * 5) - 2.5;
            }
        }

        protected int rnd() {
            int value = 0;
            while (value == 0) {
                value = (int) (Math.random() * 9) - 4;
            }
            return value;
        }

        public void update(Dimension size) {
            location.x += delta.x;
            location.y += delta.y;

            if (location.x < 0) {
                location.x = 0;
                delta.x *= -1;
            }
            if (location.x + img.getWidth() > size.width) {
                location.x = size.width - img.getWidth();
                delta.x *= -1;
            }
            if (location.y < 0) {
                location.y = 0;
                delta.y *= -1;
            }
            if (location.y + img.getHeight() > size.height) {
                location.y = size.height - img.getHeight();
                delta.y *= -1;
            }

            angle += angleDelta;
        }

        public void draw(Graphics2D g2d, JComponent parent) {
            Graphics2D g = (Graphics2D) g2d.create();
            AffineTransform at = AffineTransform.getTranslateInstance(location.x, location.y);
            at.rotate(Math.toRadians(angle), img.getWidth() / 2, img.getHeight() / 2);
            g.transform(at);
            g.drawImage(img, 0, 0, parent);
            g.dispose();
        }

    }

}

You could also use a "time" based animation, instead of linear based animation, for example

And if you're feeling really brave, Moving JLabel to other JLabels - GUI and Move image in a spiral fashion in java which are examples of key-frame based animations (time based)

Updated

This is an update to the original posted code from the question that is using a time based animation and adds in some rotation to the object (and some other graphical updates).

You'll note that I've used a ReentrantLock around the critical points where the shape is either updated or painted, this should prevent possible race conditions or dirty read/writes from occurring

The following is the same animation at 10, 5, 2 and 1 second durations

10 Seconds5Seconds2Seconds1second

One thing I did note, was, the smaller the update range (ie window), te better the animation, so you might consider using something like repaint(Rectangle) to reduce the amount of area the component tries to update

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Main extends JPanel {

    double x = 0, y = 0;
    private Rectangle2D shape;
    private double angel = 0;

    private ReentrantLock updateLock = new ReentrantLock();

    public JFrame window = new JFrame("Window");

    public Main() {
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setSize(1000, 500);
        window.add(this);
        window.setVisible(true);
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g2d.setColor(Color.red);

        updateLock.lock();
        try {
            g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(angel),
                    shape.getCenterX(),
                    shape.getCenterY()));
            g2d.fill(shape);
        } finally {
            updateLock.unlock();
        }
        g2d.dispose();
    }

    public void start() {
        shape = new Rectangle2D.Double(x, y, 50, 50);
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                long startTime = System.nanoTime();
                long runTime = TimeUnit.NANOSECONDS.convert(10, TimeUnit.SECONDS);
                System.out.println(runTime);

                double rotateFrom = 0;
                double rotateTo = 720;
                while (true) {

                    long now = System.nanoTime();
                    long diff = now - startTime;
                    double progress = diff / (double) runTime;
                    if (progress > 1.0d) {
                        progress = 0d;
                        startTime = System.nanoTime();
                    }

                    x = (getWidth() * progress);

                    updateLock.lock();
                    try {
                        angel = rotateFrom + ((rotateTo - rotateFrom) * progress);
                        shape.setRect(x, y, 50, 50);
                    } finally {
                        updateLock.unlock();
                    }

                    repaint();
                    try {
                        Thread.sleep(8);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.setDaemon(true);
        t.start();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                Main game = new Main();

                game.start();
            }
        });
    }

}
Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I see what you are saying. I have encountered the program calling repaint when it wasn't told to (by me at least). What do you mean by "the state should only be updated by your "main loop"". Do you mean that I should only update the state using a while loop else where and using the paint method only for painting the image? – Indie Man Mar 14 '16 at 11:33
  • Yes, you should only update the `x` (in this case) in your `while-loop` (or within `start` method AKA "main" or "game" loop), painting should only paint the current state and do nothing else – MadProgrammer Mar 14 '16 at 11:35
  • I have updated my code so that the state only gets updated by the while loop and it still seems to jump. I just don't get it. I know that java is easily capable to animate basic games, and yet I can't seem to get a square moving without it "lagging". I am willing to rewrite my code to go another way, but I don't understand why this square isn't moving like it should. – Indie Man Mar 14 '16 at 15:16
  • Here is the new code: public void paintComponent(Graphics g){ super.paintComponent(g); g.setColor(Color.red); g.fillRect(x, y, 500, 500); } public void start(){ while(true){ x+=3; if(x>900){ x=0; } repaint(); try { Thread.sleep(16); } catch (InterruptedException e) { e.printStackTrace(); } } } – Indie Man Mar 14 '16 at 15:16
  • Because your updating the value from another thread you're running the risk of dirty reads/writes – MadProgrammer Mar 14 '16 at 20:09
  • What do you mean? Isn't there only one thread in that example (I think). Could you propose a way to fix it. – Indie Man Mar 14 '16 at 20:44
  • You have one thread, the EDT has one thread. You might consider using a Swing `Timer` or an `AtomicInteger`. You might also consider using a time based solution over a linear based solution – MadProgrammer Mar 14 '16 at 20:48
  • I have tired the timer before, didn't work so well. I looked up atomic integer and it seems interesting. How would I add this to my program in a way that would fix the problem. Also what do you mean by time based vs linear based solution? – Indie Man Mar 14 '16 at 21:07
  • Just replace `int x` with `AtomicInteger x = new AtomicInteger(0)`. Your current approach is a linear approach, meaning that the object must move from a to b at a given step (so it might take 1 second one run and 2 seconds on the second run), but animation is the illusion of change over time, so instead, you could move a to b over a given period of time, where the delta becomes variable between updates. So the animation will always take n seconds. – MadProgrammer Mar 14 '16 at 21:23
  • Because it's impossible to tell when a paint operation has taken place (in response to your change), you could update x several times before a paint, which is one reason you might be getting update issues, it's also possible for Swing to consolidate the repaint requests down into smaller units of work (10 to 5 updates for example). I've animated over 10, 000 moving and rotating sprites using a Swing `Timer` before – MadProgrammer Mar 14 '16 at 21:25
  • I did some research on the AtomicInteger class, and have fully implemented it in to the program however the results remain unaltered. You mention that repaint might skip a few updates, so how do I find a way to fix it. There has to be a way. – Indie Man Mar 14 '16 at 21:33
  • BufferStrategy, or as suggested, time based animation – MadProgrammer Mar 14 '16 at 22:57
  • Doesn't jpanel automatically double buffer – Indie Man Mar 14 '16 at 23:09
  • Yes, but it uses a passive rendering engine, which you have little control over, BufferStratgey gives you complete control over the rendering process – MadProgrammer Mar 14 '16 at 23:29
  • Could you provide an example – Indie Man Mar 14 '16 at 23:31
  • I know this is probably asking a lot, but it would mean the world to me if you could write a program (or class) that does the same thing as the code I gave earlier but with smoothness. If I can see a working example of animation that isn't choppy I think that I should be able to learn from it and integrate it into my own game. I usually don't ask people to write out code for me, but I have spent countless hours trying to fix it and I have gotten absolutely nowhere. – Indie Man Mar 15 '16 at 01:47
  • So I did a simple Swing `Timer` example which can animate up to 10, 000 sprites, which seems to work okay. I ramped it up and can get around 20-30k sprites before I start running into to much trouble. I've also added links to time based animation examples – MadProgrammer Mar 15 '16 at 03:06
  • Thank you. I will look closely at the code when I get home. – Indie Man Mar 15 '16 at 12:45
  • Alright I have a couple of problems. First, could I have the pony.png so I can run the program. Secondly, the class you gave me seems overcomplicated for what I am doing. Basically I have a tile map class that reads from a file to generate the map in a buffered image. The map gets printed to the screen. Only the map moves (the player stay in the direct center of the screen. When the map moves it seems to "skip" every few frames. It does the exact same thing the square does just on a larger scale. I haven't gotten home yet, but I will try what you gave me. – Indie Man Mar 15 '16 at 16:22
  • The code is used to demonstrate just how much you can get done in a simple (move/rotate) based animation. It uses a pool so as to overcome the new object creation overhead, which is probably the most complicated part. You could use a fixed number of entries. You can use whatever image you want (I used a transparent png). Based on your description, it seems that either you are repainting the title map from scratch, which is unrecommended and it should be cached to a single bitmap, and/or you're having a floating point conversion issue – MadProgrammer Mar 15 '16 at 20:27
  • The way I set up my tile map class is I have it read a map file every time the player goes to a new area. The method then calls another method that makes a buffered image to which the map is drawn to. So the tile map is just a single image being drawn to the screen. There really shouldn't be any math problems because I only used ints for the x and y (of the map). Have you run the block moving from side to side? It's not like the lag is unbearable, but it's noticeable. The example I gave Is exactly the problem with my game. Why is that lag happening? – Indie Man Mar 15 '16 at 23:35
  • Well, I corrected most of the problem by moving the "move" logic to the thread instead of `paintComponent` – MadProgrammer Mar 16 '16 at 00:14
  • Yes, the thread deals with updating the `x` value and calling repaint. It doesn't fully eliminate the issue, but significantly reduces it – MadProgrammer Mar 16 '16 at 00:17
  • I have updated my code. I also posted my actual code as an edit in my question. When I had the main while update the x/y the problem got a little worse. Is there something wrong with my code? – Indie Man Mar 16 '16 at 00:31
  • It could be a thread read/write issue, where the EDT is missing updates to the variable :P. I've been playing around with the code, and while I can "see" the issue, I don't think it's actually an issue with Swing (per say - there's probably a refresh update issue involved), but an optical illusion. I've taken your original based code and wrapped a time based animation around, adding in some rotation which seems to have helped a little – MadProgrammer Mar 16 '16 at 22:29
  • Ahh, now I see what you meant by "time" based animation (I was thinking about it the wrong way. Your code seems to run pretty smooth, and overall I am very happy with it. Do you think the results would differ with a buffered image instead of a square? – Indie Man Mar 17 '16 at 00:43
  • @IndieMan No, in fact, I think they might be better – MadProgrammer Mar 17 '16 at 00:58
  • Why do you say that? – Indie Man Mar 17 '16 at 00:58
  • Part of the problem, I think, is it's an illusion, because the shape is regular, it's much easier for it to look like it's in two places at once (or like it's stopped moving), where as it hard for an irregular shape (because there's so much that has changed), hence the reason I added in the rotation. If the `BufferedImage` is using a compatible color model, it should render just as well, if not faster – MadProgrammer Mar 17 '16 at 01:00
  • I did an animated sequence of multiple layers of images, where each layer was running at a different speed (the output is [demonstrated here](http://stackoverflow.com/questions/34735361/jframe-flash-when-updating-graphics/34755462#34755462)), which was using (largish) `BufferedImage`s. I originally had problems getting to paint at all, which come down to the images not having a compatible color model with the screen, was I fixed that, I was able to get to run at rough 200fps on a transparent window with little issue – MadProgrammer Mar 17 '16 at 01:04
  • Well I will have to adapt it into my program, but currently I am testing to see if I can get it to work the way that I want it to. – Indie Man Mar 17 '16 at 01:06
  • I think the "critical" part is actually the `ReentrantLock` around the critical paint/update areas of the code. I'd also have a play with `repaint(Rectangle)`, as this would reduce the amount of space you have to paint – MadProgrammer Mar 17 '16 at 01:07
  • Yah it seems to work much better. It is going to take a little while because I have my game painting system set up a completely different way. But, I think it will work better. – Indie Man Mar 17 '16 at 01:12
  • So far so good. I have implemented some of it, but it is getting late over here so i'm going to hit the sack. I going to work on it some more tomorrow. – Indie Man Mar 17 '16 at 02:07