Java basic 2d game animation stutter

后端 未结 1 408
我在风中等你
我在风中等你 2020-12-12 03:47

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 re

1条回答
  •  無奈伤痛
    2020-12-12 04:41

    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

    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 pool;
            private List 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 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 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

    • Moving a square from a starting point to the position of a mouse click at a fixed speed
    • JPanel image flies from the screen
    • Java image move along points in list and use linear interpolation

    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

    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();
                }
            });
        }
    
    }
    

    0 讨论(0)
提交回复
热议问题