1

I would like to make my app draw moving image a little smoother than how it is currently drawing them. I am not sure what to do to do that.

This is what my main game Thread looks like:

@Override
public void run(){
    int delay = 500; //milliseconds
    ActionListener taskPerformer = new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent evt){
            Car car = new Car();
            int speed = (int)(3 + Math.floor(Math.random() * (6 - 3)));
            car.setSpeed(speed);
            MainLoop.this.gameObjects.vehicles.add(car.create("/Media/Graphics/blueCar.png", width - 20, 78));
            car.driveTo(0, 78);
        }
    };
    new Timer(delay, taskPerformer).start();
    try{
        while(true){
            this.repaint();
            for(GameObject go : this.gameObjects.vehicles){
                // loops through objects to move them
                Vehicle vh = (Vehicle) go;
                this.moveVehicle(vh);
                if(vh.getX() <= vh.getDestX()){
                    vh.markForDeletion(true);
                }
            }
            this.gameObjects.destroyVehicles();
            Thread.sleep(1);
        }
    }catch(Exception e){
        e.printStackTrace();
    }
}

This is a method that calculates the items next x/y position

protected void moveVehicle(Vehicle vh){
    int cx = vh.getX();
    int dx = vh.getDestX();
    int cy = vh.getY();
    int dy = vh.getDestY();
    // move along x axis
    // getMaxSpeed() = Number between 3 and 6
    if(cx > dx && vh.movingX() == -1){
        vh.setX(cx - vh.getMaxSpeed());
    }else if(cx < dx && vh.movingX() == 1){
        vh.setX(cx + vh.getMaxSpeed());
    }else{
        vh.setX(dx);
    }

    // move along y axis
    // getMaxSpeed() = Number between 3 and 6
    if(cy > dy && vh.movingY() == -1){
        vh.setY(cy - vh.getMaxSpeed());
    }else if(cy < dy && vh.movingY() == 1){
        vh.setY(cy + vh.getMaxSpeed());
    }else{
        vh.setY(dy);
    }
}

This is my paint method:

@Override
public void paintComponent(Graphics graphics){
    super.paintComponent(graphics);
    Graphics2D g = (Graphics2D) graphics;

    for(GameObject go : gameObjects.vehicles){
        g.drawImage(go.getSprite(), go.getX(), go.getY(), this);
    }
}

This is possibly more info than needed, but I would like to know, what should I do to make items move from left -> right top -> bottom as smoothly as possible, without much of a performance loss?

Edit: Requested sscce:

package sscce;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Sscce extends JPanel implements Runnable{

    ArrayList<Square> squares = new ArrayList<>();

    public Sscce(){
        JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
        frame.add(this);
        Thread t = new Thread(this);
        t.start();
    }

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

    @Override
    public void run(){
        int delay = 500; //milliseconds
        ActionListener taskPerformer = new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent evt){
                Square squ = new Square();
                Sscce.this.squares.add(squ);
                squ.moveTo(0);
            }
        };
        new Timer(delay, taskPerformer).start();
        while(true){
            try{
                for(Square s : this.squares){
                    int objX = s.getX();
                    int desX = s.getDestX();
                    if(objX <= desX){
                        System.out.println("removing");
                        this.squares.remove(s);
                    }else{
                        s.setX(s.getX() - 10);
                    }
                }
                this.repaint();
                Thread.sleep(30);
            }catch(Exception e){
            }
        }
    }

    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        for(Square s : squares){
            g.setColor(Color.blue);
            g.fillRect(s.getX(), s.getY(), 50, 50);
        }
    }
}

class Square{

    public int x = 0, y = 0, destX = 0;

    public Square(){
        this.x = 400;
        this.y = 100;
    }

    public void moveTo(int destX){
        this.destX = destX;
    }

    public int getX(){
        return this.x;
    }
    public int getDestX(){
        return this.destX;
    }

    public void setX(int x){
        this.x = x;
    }

    public int getY(){
        return this.y;
    }
}
Get Off My Lawn
  • 27,770
  • 29
  • 134
  • 260
  • 1
    You also take a look at [this](http://stackoverflow.com/questions/14886232/swing-animation-running-extremely-slow/14902184#14902184) and [this](http://stackoverflow.com/questions/13540534/how-to-make-line-animation-smoother/13547895#13547895) and [this](http://stackoverflow.com/questions/14593678/multiple-bouncing-balls-thread-issue/14593761#14593761) and [this](http://stackoverflow.com/questions/13022754/java-bouncing-ball/13022788#13022788) and [this](http://stackoverflow.com/questions/12642852/the-images-are-not-loading/12648265#12648265) (just because I like it) – MadProgrammer Mar 10 '13 at 23:51
  • 1
    Lets start with, without a working example, it's not going to be possible to help you. But lets start with how pointless Thread.sleep(1); this is. And then go on to mention that it's possible for Swing to perform a repaint while you're updating the model – MadProgrammer Mar 10 '13 at 23:52
  • @MadProgrammer I added an sscce. If I don't put `Thread.sleep(1);` nothing gets drawn. – Get Off My Lawn Mar 10 '13 at 23:52
  • The problem with `Thread.sleep(1)` it's as close to `Thread.sleep(0)` as makes no difference. Trying to generate a progress bar? – MadProgrammer Mar 11 '13 at 00:04
  • The other problem with `Thread.sleep(1)` is that most people aren't even going to be able to see a difference. Roughly speaking, 25fps a second is more the sufficient (about 40 milliseconds). The problem revolves around the distance change more then the number of updates... – MadProgrammer Mar 11 '13 at 00:14
  • I figured it was a distance thing, but do you know of a way to make it as smooth as possible from going from point **A** to point **B**? – Get Off My Lawn Mar 11 '13 at 00:35
  • It depends. Any animation is an illusion. To slow and it won't like right, to fast and will stagger – MadProgrammer Mar 11 '13 at 00:42

1 Answers1

1

First note, for some reason, I had issues with the JPanel implementing Runnable when running it under MacOS - I have no idea why, but that's why I moved it out.

It is possible for your squares to be update while it is been used to paint which will cause an exception (also, removing elements from a list while you're iterating it isn't a good idea either ;))

Instead, I have two lists. I have a model, which the list can modify and then the paint list which the paint method can use. This allows the thread to modifiy the model, while a paint process is underway.

To prevent any clash, I've added in a lock, which prevents one thread from modifying/accessing the paint list, while another thread has it locked.

Now. Down to the real problem. The main issue you're having isn't the amount of time between the updates, but the distance you are moving. Reduce the distance (to make it slower) and standarise the updates.

Most people won't notice anything much over 25fps, so trying to do much more then that is just wasting CPU cycles and starving the repaint manager, preventing it from actually updating the screen.

It's a balancing act to be sure...

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestAnimation11 extends JPanel {

    private ArrayList<Square> squares = new ArrayList<>();
    private ReentrantLock lock;

    public TestAnimation11() {
        lock = new ReentrantLock();
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }
                JFrame frame = new JFrame();
                frame.setSize(500, 500);
                frame.setLocationRelativeTo(null);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
                frame.add(TestAnimation11.this);
                Thread t = new Thread(new UpdateEngine());
                t.start();
            }
        });
    }

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

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Square[] paint = null;
        lock.lock();
        try {
            paint = squares.toArray(new Square[squares.size()]);
        } finally {
            lock.unlock();
        }
        for (Square s : paint) {
            g.setColor(Color.blue);
            g.fillRect(s.getX(), s.getY(), 50, 50);
        }
    }

    public class UpdateEngine implements Runnable {

        private List<Square> model = new ArrayList<>(squares);

        @Override
        public void run() {
            int ticks = 0;
            List<Square> dispose = new ArrayList<>(25);
            while (true) {
                ticks++;
                dispose.clear();
                for (Square s : model) {
                    int objX = s.getX();
                    int desX = s.getDestX();
                    if (objX <= desX) {
                        dispose.add(s);
                    } else {
                        s.setX(s.getX() - 2);
                    }
                }
                model.removeAll(dispose);
                if (ticks == 11) {
                    Square sqr = new Square();
                    sqr.moveTo(0);
                    model.add(sqr);
                } else if (ticks >= 25) {
                    ticks = 0;
                }
                lock.lock();
                try {
                    squares.clear();
                    squares.addAll(model);
                } finally {
                    lock.unlock();
                }
                repaint();
                try {
                    Thread.sleep(40);
                } catch (Exception e) {
                }
            }
        }
    }

    class Square {

        public int x = 0, y = 0, destX = 0;

        public Square() {
            this.x = 400;
            this.y = 100;
        }

        public void moveTo(int destX) {
            this.destX = destX;
        }

        public int getX() {
            return this.x;
        }

        public int getDestX() {
            return this.destX;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return this.y;
        }
    }
}
MadProgrammer
  • 323,026
  • 21
  • 204
  • 329