java - 即使我双缓冲,JPanel 仍然撕裂?

标签 java swing jpanel doublebuffered tearing

编辑:这是一个 SSCCE 来演示我的问题。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.Graphics2D;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

public class Main {
    public static BufferedImage map, tileSand, tileSea;
    public static void main(String[] args) {
        map = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
        for(int x = 0; x < 50 ; x++){
            for(int y = 0; y < 50 ; y++){
                boolean colour = Math.random() < 0.5;
                if (colour){
                    map.setRGB(x, y, -256);
                } else {
                    map.setRGB(x, y, -16776961);
                }
            }
        }
        tileSand = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
        Graphics g = tileSand.getGraphics();
        g.setColor(Color.YELLOW);
        g.fillRect(0, 0, 32, 32);
        tileSea  = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
        g.setColor(Color.BLUE);
        g = tileSea.getGraphics();
        g.fillRect(0, 0, 32, 32);
        Island test = new Main.Island();
        test.start();
        long start, sleep;
        while(true) {
            start  = System.currentTimeMillis();
            test.getCanvas().requestFocus();
            test.getCanvas().repaint();
            sleep = 15-(System.currentTimeMillis()-start);
            try {
                Thread.sleep(sleep > 0 ? sleep : 0);
            } catch (InterruptedException e) {
            }
        }
    }

    static class Island implements Runnable {

        private Tile[][] tiles;
        private JFrame frame;
        private JPanel panel;
        private Thread logicThread;
        private boolean running = false;
        private boolean paused = false;
        private Image image;
        private Player player;

        public Island() {
            image = new BufferedImage(1027, 768, BufferedImage.TYPE_INT_ARGB);
            player = new Player();
            tiles = new Tile[map.getWidth()][map.getHeight()];
            int rgb;
            for(int x = 0; x < map.getWidth(); x++) {
                for(int y = 0; y < map.getHeight(); y++) {
                    rgb = map.getRGB(x, y);
                    switch (rgb) {
                        case -16776961: tiles[x][y] = new Tile("sea");
                                        break;
                        case -256:  tiles[x][y] = new Tile("sand");
                                    break;
                    }
                }
            }
            makeMap();
            makeFrame();
            addBindings();
            logicThread = new Thread(this);
        }

        public JPanel getCanvas() {
            return panel;
        }

        public void start() {
            running = true;
            paused = false;
            logicThread.start();
        }

        public void run() {
            long sleep, before;
            while(running){
                before = System.currentTimeMillis();
                player.move();
                try {
                    sleep = 15-(System.currentTimeMillis()-before);
                    Thread.sleep(sleep > 0 ? sleep : 0);
                } catch (InterruptedException ex) {
                }
                while(running && paused);
            }
            paused = false;
        }

        private void makeFrame() {
            frame = new JFrame("Island");
            panel = new JPanel(){
                @Override
                public void paintComponent(Graphics g) {
                    super.paintComponent(g);
                    Graphics2D g2d = (Graphics2D) image.getGraphics();
                    g2d.setColor(Color.BLACK);
                    g2d.fillRect(0, 0, 1024, 768);
                    long xl, yl;
                    xl = player.getLocation().x-512;
                    yl = player.getLocation().y-384;
                    int x2, y2;
                    x2 = (int) Math.floor(xl / 32);
                    y2 = (int) Math.floor(yl / 32);
                    int xoffset, yoffset;
                    xoffset = (int) (xl % 32);
                    yoffset = (int) (yl % 32);
                    for(int x = x2; x < x2+40; x++) {
                        for(int y = y2; y < y2+40; y++) {
                            if (x < tiles.length && x > 0 && y < tiles[0].length && y > 0) {
                                g2d.drawImage(tiles[x][y].getContent(), (x-x2)*32 - xoffset, (y-y2)*32 - yoffset, null);
                            }
                        }
                    }
                    g.drawImage(image, 0, 0, null);
                }
            };
            panel.setPreferredSize(new Dimension(1024, 768));
            panel.setIgnoreRepaint(true);
            frame.add(panel);
            frame.pack();
            frame.setVisible(true);
        }

        private void addBindings() {
            InputMap inputMap = panel.getInputMap();
            ActionMap actionMap = panel.getActionMap();
            inputMap.put(KeyStroke.getKeyStroke("Q"),"hold-sprint");
            actionMap.put("hold-sprint", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.sprint(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released Q"),"release-sprint");
            actionMap.put("release-sprint", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.sprint(false);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("RIGHT"),"hold-right");
            actionMap.put("hold-right", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.right(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released RIGHT"),"release-right");
            actionMap.put("release-right", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.right(false);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("DOWN"),"hold-down");
            actionMap.put("hold-down", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.down(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released DOWN"),"release-down");
            actionMap.put("release-down", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.down(false);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("LEFT"),"hold-left");
            actionMap.put("hold-left", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.left(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released LEFT"),"release-left");
            actionMap.put("release-left", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.left(false);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("UP"),"hold-up");
            actionMap.put("hold-up", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.up(true);
                }
            });
            inputMap.put(KeyStroke.getKeyStroke("released UP"),"release-up");
            actionMap.put("release-up", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    player.up(false);
                }
            });
        }

        private void makeMap() {
            for(int x = 0; x < tiles.length; x++) {
                for(int y = 0; y < tiles[0].length; y++) {
                    switch (tiles[x][y].getType()) {
                        case "sea": tiles[x][y].setContent(tileSea);
                                    break;
                        case "sand":    tiles[x][y].setContent(tileSand);
                                        break;
                    }
                }   
            }
        }
    }

    static class Player{
        private Point location;
        private int xspeed, yspeed;
        private boolean left, up, right, down, sprint;

        public Player() {
            location = new Point(0, 0);
            left = false;
            up = false;
            right = false;
            down = false;
            sprint = false;
            xspeed = yspeed = 0;
        }
        public void left(boolean state){
            left = state;
        }

        public void up(boolean state){
            up = state;
        }

        public void right(boolean state){
            right = state;
        }

        public void down(boolean state){
            down = state;
        }

        public void sprint(boolean state) {
            sprint = state;
        }

        public void move() {
            if (sprint) {
                if (left) {
                    if(xspeed>-10)
                        xspeed--;
                } if (right) {
                    if(xspeed<10)
                        xspeed++;
                } if (up) {
                    if(yspeed>-10)
                        yspeed--;
                } if (down) {
                    if(yspeed<10)
                        yspeed++;
                }
            } else {
                if (left) {
                    if(xspeed>-5)
                        xspeed--;
                } if (right) {
                    if(xspeed<5)
                        xspeed++;
                } if (up) {
                    if(yspeed>-5)
                        yspeed--;
                } if (down) {
                    if(yspeed<5)
                        yspeed++;
                }
            }
            if (!sprint) {
                if (xspeed > 5) {
                    xspeed--;
                } if (xspeed < -5) {
                    xspeed++;
                }   if (yspeed > 5) {
                    yspeed--;
                } if (yspeed < -5) {
                    yspeed++;
                }
            }
            if (!left && !right) {
                if (xspeed > 0) {
                    xspeed--;
                } if (xspeed < 0) {
                    xspeed++;
                }
            } if (!up && !down) {
                if (yspeed > 0) {
                    yspeed--;
                } if (yspeed < 0) {
                    yspeed++;
                }
            }
            location.x = location.x + xspeed;
            location.y = location.y + yspeed;
        }

        public Point getLocation() {
            return location;
        }
    }

    static class Tile {
        private String type;
        private BufferedImage tile;

        public Tile(String type) {
            this.type = type;
        }

        public String getType() {
            return type;
        }

        public void setContent(BufferedImage newTile) {
            tile = newTile;
        }

        public BufferedImage getContent() {
            return tile;
        }
    }
}

我有一个带有修改过的paintComponent方法的JPanel。在此方法中,我根据玩家位置将相关图 block 从二维数组绘制到屏幕上。我将所有图 block 绘制到 BufferedImage,然后在绘制方法结束时将其应用到屏幕。理论上这不应该造成撕裂,因为我是双缓冲的?这是适当的代码,图像只是具有屏幕尺寸的 BufferedImage:

    public void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) image.getGraphics();
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, 1024, 768);
        long xl, yl;
        xl = player.getLocation().x-512;
        yl = player.getLocation().y-384;
        int x2, y2;
        x2 = (int) Math.floor(xl / 32);
        y2 = (int) Math.floor(yl / 32);
        int xoffset, yoffset;
        xoffset = (int) (xl % 32);
        yoffset = (int) (yl % 32);
        for(int x = x2; x < x2+40; x++) {
            for(int y = y2; y < y2+40; y++) {
                if (x < tiles.length && x > 0 && y < tiles[0].length && y > 0) {
                    g2d.drawImage(tiles[x][y].getContent(), (x-x2)*32 - xoffset, (y-y2)*32 - yoffset, null);
                }
            }
        }
        g.drawImage(image, 0, 0, null);
    }

我认为撕裂可能是由于玩家的位置在绘制方法的持续时间内发生变化导致瓷砖不匹配而引起的,玩家走得越快,效果越明显。不过,我在开始时获取了玩家位置并将其存储在两个长整型中,我的印象是长整型是通过值而不是引用传递的,因此在方法运行时玩家位置应该保持不变。

我很高兴提供更多代码:),并提前致谢。

最佳答案

如果人们发现我的问题并想知道我最终做了什么来“修复”它,请在深入研究项目之前切换到 C++ 和 SDL 或其他一些图像库,这对于这种类型来说更好事物。

关于java - 即使我双缓冲,JPanel 仍然撕裂?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16386504/

相关文章:

java - 仅重复 3 个字母最多 6 次

java - 抽象构造函数java

java - 检测文档中的样式文本

java - 显示具有两个面板的 GUI,每个面板包含三个按钮

java - 将 .jar 文件添加到 netbeans 中的项目后,如何将其中的类导入到 netbeans 中的项目中?

java - windowClosing() 方法没有被调用

Java GridBagLayout

java - KeyListener 不改变 JPanel

java - Java如何获取一个月的最后一个星期四