JPanel绘图故障 [英] JPanel Drawing Glitch

查看:106
本文介绍了JPanel绘图故障的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用JPanel,使用固定的游戏循环和双缓冲来绘制我的游戏;但是我在屏幕上的某个地方出现了故障.毛刺是在屏幕上撕裂的视觉伪像,延伸到X轴,大约20像素高.

I am using a JPanel to draw my game onto, using a fixed game loop, and double buffering; however I get a glitch somewhere on the screen. The glitch is a screen tearing visual artifact that stretches across X axis, and is about 20 pixels tall.

我已经在1节课中重新创建了该问题,如下所示.要重现此问题,您可以运行代码并使用箭头键在正方形周围移动,当正方形移到发生视觉撕裂的位置时,您应该会看到效果. (视觉撕裂的位置似乎是随机的)

I have recreated the problem in 1 class, as shown below. To recreate the problem, you can run the code and move the square around with the arrow keys, when the square moves over the place where the visual tearing occurs, you should see the effect. (The location of the visual tearing seems to be random)

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

I recreated a single frame of what the image tearing looks like, however when running, it shows a flickering effect.

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

    }

}

很有可能不会出现屏幕撕裂的情况,(我相信这是因为帧与屏幕同步)

There is a small chance that the screen tearing doesn't occur, (I believe this is because the frames are in sync with the screen)

Edit2:有人告诉我,他们的硬件上没有发生此问题,如何为我的硬件解决此问题?

Some people have told me that this problem doesn't occur on their hardware, how could I fix this problem for my hardware?

推荐答案

这是一项测试,而不是答案

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

This is the most simplistic Swing based example I can come up with. I paints directly to the JPanel and will try and update every 16 milliseconds (60fps)

我建议您使用X_DELTAY_DELTA值,并在其中内外注释掉Toolkit.getDefaultToolkit().sync();,以查看是否有区别...

I would suggest that you play with the X_DELTA and Y_DELTA values as well as commenting out Toolkit.getDefaultToolkit().sync(); in and out to see if it makes a difference...

nb-两个示例都使用直接"绘画过程.也就是说,与其使用BufferedImage(或其他后备缓冲区),而是直接将其绘制到Graphics上下文

nb- Both examples use a "direct" painting process. That is, rather the using a BufferedImage (or other backing buffer), they paint directly to the Graphics context

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

Included a Canvas/BufferStrategy example as well. Some deal, play with the X/Y_Delta values and enable/disable 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) {
                }
            }
        }

    }

}

这篇关于JPanel绘图故障的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆