JPanel Drawing Glitch

时间:2014-08-26 01:58:38

标签: java swing jpanel paintcomponent

我正在使用JPanel将我的游戏画上,使用固定的游戏循环和双缓冲;但是我在屏幕上的某个地方出现了故障。毛刺是一个屏幕撕裂的视觉神器,横跨X轴延伸,高约20像素。

我已在1个类中重新创建了该问题,如下所示。要重新创建问题,您可以运行代码并使用箭头键移动方块,当方块移动到发生视觉撕裂的位置时,您应该看到效果。 (视觉撕裂的位置似乎是随机的)

我重新创建了图像撕裂的单帧,但是在运行时,它会显示闪烁效果。

enter image description here

package main;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Panel {

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

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

                JFrame frame = new JFrame("Game!");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public interface View {

        public BufferedImage switchBuffers();
        public int getWidth();
        public int getHeight();

    }

    public enum KeyState {
        UP, DOWN, LEFT, RIGHT;
    }

    @SuppressWarnings("serial")
    public class TestPane extends JPanel implements View {

        private Engine engine;

        private BufferedImage active;
        private BufferedImage update;

        private ReentrantLock lckBuffer;

        public TestPane() {
            lckBuffer = new ReentrantLock();
            initBuffers();
            engine = new Engine(this);
            engine.gameStart();

            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up_pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down_pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "left_pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right_pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up_released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down_released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "left_released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right_released");

            ActionMap am = getActionMap();
            am.put("up_pressed", new AddState(engine, KeyState.UP));
            am.put("up_released", new RemoveState(engine, KeyState.UP));
            am.put("down_pressed", new AddState(engine, KeyState.DOWN));
            am.put("down_released", new RemoveState(engine, KeyState.DOWN));
            am.put("left_pressed", new AddState(engine, KeyState.LEFT));
            am.put("left_released", new RemoveState(engine, KeyState.LEFT));
            am.put("right_pressed", new AddState(engine, KeyState.RIGHT));
            am.put("right_released", new RemoveState(engine, KeyState.RIGHT));
        }

        protected void initBuffers() {
            if (getWidth() > 0 && getHeight() > 0) {
                try {
                    lckBuffer.lock();
                    active = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
                    update = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
                } finally {
                    lckBuffer.unlock();
                }
            }
        }

        @Override
        public void invalidate() {
            super.invalidate();
            initBuffers();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            try {
                lckBuffer.lock();
                if (active != null) {
                    g2d.drawImage(active, 0, 0, this);
                }
            } finally {
                lckBuffer.unlock();
            }
            g2d.dispose();
        }

        @Override
        public BufferedImage switchBuffers() {
            try {
                lckBuffer.lock();
                BufferedImage tmp = active;
                active = update;
                update = tmp;
                repaint();
            } finally {
                lckBuffer.unlock();
            }
            return update;
        }

    }

    public static class Engine {

        public static final int MAP_WIDTH = 15 * 4;
        public static final int MAP_HEIGHT = 9 * 4;
        public static final int X_DELTA = 8;
        public static final int Y_DELTA = 8;

        //This value would probably be stored elsewhere.
        public static final double GAME_HERTZ = 60.0;
        //Calculate how many ns each frame should take for our target game hertz.
        public static final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
        //We will need the last update time.
        static double lastUpdateTime = System.nanoTime();
        //Store the last time we rendered.
        static double lastRenderTime = System.nanoTime();

        //If we are able to get as high as this FPS, don't render again.
        final static double TARGET_FPS = GAME_HERTZ;
        final static double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;

        //Simple way of finding FPS.
        static int lastSecondTime = (int) (lastUpdateTime / 1000000000);

        public static int fps = 60;
        public static int frameCount = 0;

        public int x, y;

        private boolean isGameFinished;

        private View view;

        private Set<KeyState> keyStates;

        public Engine(View bufferRenderer) {
            keyStates = new HashSet<>(4);
            this.view = bufferRenderer;
        }

        public void gameStart() {

            x = (800/2) - (60/2);
            y = (800/2) - (60/2);
            Thread gameThread = new Thread() {
                // Override run() to provide the running behavior of this thread.
                @Override
                public void run() {
                    gameLoop();
                }
            };
            gameThread.setDaemon(false);
            // Start the thread. start() calls run(), which in turn calls gameLoop().
            gameThread.start();
        }

        public void gameLoop() {
            BufferedImage buffer = view.switchBuffers(); // initial buffer...
            while (!isGameFinished) {
                double now = System.nanoTime();
                lastUpdateTime += TIME_BETWEEN_UPDATES;
                gameUpdate(buffer);
                renderBuffer(buffer);
                buffer = view.switchBuffers(); // Push the buffer back
                frameCount++;
                lastRenderTime = now;
                int thisSecond = (int) (lastUpdateTime / 1000000000);
                if (thisSecond > lastSecondTime) {
                    fps = frameCount;
                    frameCount = 0;
                    lastSecondTime = thisSecond;
                }
                while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
                    Thread.yield();
                    try { Thread.sleep(1);
                    } catch (Exception e) {}
                    now = System.nanoTime();
                }
            }
        }

        protected void renderBuffer(BufferedImage buffer) {
            if (buffer != null) {
                Graphics2D g2d = buffer.createGraphics();
                g2d.setColor(Color.BLACK);
                g2d.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
                g2d.setColor(Color.WHITE);
                g2d.fillRect(x, y, 60, 60);
                g2d.setColor(Color.WHITE);
                g2d.drawString("FPS: "+Engine.fps, 0, 10);
                g2d.dispose();
            }
        }

        protected void gameUpdate(BufferedImage buffer) {

            if (keyStates.contains(KeyState.DOWN)) {
                y = y + Y_DELTA;
            } else if (keyStates.contains(KeyState.UP)) {
                y = y - Y_DELTA;
            }
            if (keyStates.contains(KeyState.RIGHT)) {
                x = x + X_DELTA;
            } else if (keyStates.contains(KeyState.LEFT)) {
                x = x - X_DELTA;
            }
        }

        public void addKeyState(KeyState state) {
            keyStates.add(state);
        }

        public void removeKeyState(KeyState state) {
            keyStates.remove(state);
        }

    }

    @SuppressWarnings("serial")
    public class AddState extends AbstractAction {

        private Engine engine;
        private KeyState state;

        public AddState(Engine engine, KeyState state) {
            this.engine = engine;
            this.state = state;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            engine.addKeyState(state);
        }

    }

    @SuppressWarnings("serial")
    public class RemoveState extends AbstractAction {

        private Engine engine;
        private KeyState state;

        public RemoveState(Engine engine, KeyState state) {
            this.engine = engine;
            this.state = state;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            engine.removeKeyState(state);
        }

    }

}

编辑:屏幕撕裂的可能性很小,(我相信这是因为帧与屏幕同步)

Edit2:有些人告诉我这个问题在他们的硬件上没有发生,我怎么能为我的硬件解决这个问题呢?

2 个答案:

答案 0 :(得分:1)

这是一项测试 - 不是答案

这是我能提出的最简单的基于Swing的示例。我直接绘制到JPanel并尝试每16毫秒(60fps)更新

我建议您使用X_DELTAY_DELTA值以及评论Toolkit.getDefaultToolkit().sync();进出,看看它是否有所作为......

nb-这两个例子都使用&#34; direct&#34;绘画过程。也就是说,使用BufferedImage(或其他后备缓冲区),它们直接绘制到Graphics上下文

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class BasicAnimation {

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

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

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public enum KeyState {

        UP, DOWN, LEFT, RIGHT;
    }

    public static class TestPane extends JPanel {

        private Set<KeyState> keyStates;

        private Point player = new Point(200, 200);

        public static final int X_DELTA = 4;
        public static final int Y_DELTA = 4;

        public TestPane() {
            keyStates = new HashSet<>(4);
            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up_pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down_pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "left_pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right_pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up_released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down_released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "left_released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right_released");

            ActionMap am = getActionMap();
            am.put("up_pressed", new AddState(KeyState.UP));
            am.put("up_released", new RemoveState(KeyState.UP));
            am.put("down_pressed", new AddState(KeyState.DOWN));
            am.put("down_released", new RemoveState(KeyState.DOWN));
            am.put("left_pressed", new AddState(KeyState.LEFT));
            am.put("left_released", new RemoveState(KeyState.LEFT));
            am.put("right_pressed", new AddState(KeyState.RIGHT));
            am.put("right_released", new RemoveState(KeyState.RIGHT));

            Timer timer = new Timer(16, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    updateState();
                    repaint();
                    Toolkit.getDefaultToolkit().sync();
                }
            });
            timer.start();
        }

        protected void updateState() {

            if (keyStates.contains(KeyState.UP)) {
                player.y -= Y_DELTA;
            } else if (keyStates.contains(KeyState.DOWN)) {
                player.y += Y_DELTA;
            }

            if (keyStates.contains(KeyState.LEFT)) {
                player.x -= X_DELTA;
            } else if (keyStates.contains(KeyState.RIGHT)) {
                player.x += X_DELTA;
            }

        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.BLACK);
            g2d.fillRect(0, 0, getWidth(), getHeight());
            g2d.setColor(Color.WHITE);
            g2d.fillRect(player.x - 25, player.y - 25, 50, 50);
            g2d.dispose();
        }

        public void addKeyState(KeyState state) {
            keyStates.add(state);
        }

        public void removeKeyState(KeyState state) {
            keyStates.remove(state);
        }

        @SuppressWarnings("serial")
        public class AddState extends AbstractAction {

            private final KeyState state;

            public AddState(KeyState state) {
                this.state = state;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                addKeyState(state);
            }

        }

        @SuppressWarnings("serial")
        public class RemoveState extends AbstractAction {

            private final KeyState state;

            public RemoveState(KeyState state) {
                this.state = state;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                removeKeyState(state);
            }

        }
    }

}

还包括Canvas / BufferStrategy示例。有些交易,使用X/Y_Delta值并启用/停用Toolkit.getDefaultToolkit().sync();

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class BasicAnimation {

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

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

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new GameView());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public enum KeyState {

        UP, DOWN, LEFT, RIGHT;
    }

    public static class GameView extends Canvas {

        private Set<KeyState> keyStates;

        private Point player = new Point(200, 200);

        public static final int X_DELTA = 4;
        public static final int Y_DELTA = 4;

        private GameThread gt;

        public GameView() {
            keyStates = new HashSet<>(4);
            gt = new GameThread(this);

            setFocusable(true);

            addKeyListener(new KeyAdapter() {

                @Override
                public void keyPressed(KeyEvent e) {
                    switch (e.getKeyCode()) {
                        case KeyEvent.VK_UP:
                            addKeyState(KeyState.UP);
                            break;
                        case KeyEvent.VK_DOWN:
                            addKeyState(KeyState.DOWN);
                            break;
                        case KeyEvent.VK_LEFT:
                            addKeyState(KeyState.LEFT);
                            break;
                        case KeyEvent.VK_RIGHT:
                            addKeyState(KeyState.RIGHT);
                            break;
                    }
                }

                @Override
                public void keyReleased(KeyEvent e) {
                    switch (e.getKeyCode()) {
                        case KeyEvent.VK_UP:
                            removeKeyState(KeyState.UP);
                            break;
                        case KeyEvent.VK_DOWN:
                            removeKeyState(KeyState.DOWN);
                            break;
                        case KeyEvent.VK_LEFT:
                            removeKeyState(KeyState.LEFT);
                            break;
                        case KeyEvent.VK_RIGHT:
                            removeKeyState(KeyState.RIGHT);
                            break;
                    }
                }

            });
        }

        @Override
        public void addNotify() {
            super.addNotify();
            createBufferStrategy(2);
            gt.start();
            requestFocusInWindow();
        }

        @Override
        public void removeNotify() {
            gt.stop();
            super.removeNotify();
        }

        public void updateState() {

            if (keyStates.contains(KeyState.UP)) {
                player.y -= Y_DELTA;
            } else if (keyStates.contains(KeyState.DOWN)) {
                player.y += Y_DELTA;
            }

            if (keyStates.contains(KeyState.LEFT)) {
                player.x -= X_DELTA;
            } else if (keyStates.contains(KeyState.RIGHT)) {
                player.x += X_DELTA;
            }

        }

        public void paintState(Graphics2D g2d) {
            g2d.setColor(Color.BLACK);
            g2d.fillRect(0, 0, getWidth(), getHeight());
            g2d.setColor(Color.WHITE);
            g2d.fillRect(player.x - 25, player.y - 25, 50, 50);
        }

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

        public void addKeyState(KeyState state) {
            keyStates.add(state);
        }

        public void removeKeyState(KeyState state) {
            keyStates.remove(state);
        }

        @SuppressWarnings("serial")
        public class AddState extends AbstractAction {

            private final KeyState state;

            public AddState(KeyState state) {
                this.state = state;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                addKeyState(state);
            }

        }

        @SuppressWarnings("serial")
        public class RemoveState extends AbstractAction {

            private final KeyState state;

            public RemoveState(KeyState state) {
                this.state = state;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                removeKeyState(state);
            }

        }
    }

    public static class GameThread implements Runnable {

        private volatile boolean keepRunning = true;
        private GameView view;
        private Thread currentThread;

        public GameThread(GameView view) {
            this.view = view;
        }

        public void start() {
            if (currentThread == null) {
                keepRunning = true;
                currentThread = new Thread(this);
                currentThread.start();
            }
        }

        public void stop() {
            keepRunning = false;
            if (currentThread != null) {
                try {
                    currentThread.join();
                } catch (InterruptedException ex) {
                }
            }
        }

        @Override
        public void run() {
            while (keepRunning) {
                view.updateState();
                BufferStrategy bs = view.getBufferStrategy();
                do {
                    Graphics2D g2d = (Graphics2D) bs.getDrawGraphics();
                    view.paintState(g2d);
                    g2d.dispose();
                } while (bs.contentsLost());
                bs.show();
                Toolkit.getDefaultToolkit().sync();
                try {
                    Thread.sleep(16);
                } catch (InterruptedException ex) {
                }
            }
        }

    }

}

答案 1 :(得分:0)

尝试从paintComponent(g)方法移动g2d.dipose()。在绘制活动图像后立即进入try {} catch()语句。