【问题标题】:Canvas double buffered graphics not working画布双缓冲图形不起作用
【发布时间】:2020-04-01 12:42:26
【问题描述】:

我尝试为我的画布制作双缓冲图形,但它总是在渲染后立即消失,有时甚至不渲染,代码如下:

package initilizer;
import java.awt.AWTException;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import input.Keyboard;

public class Main extends Canvas{

    static int width = 800;
    static int height = 600;
    int cx = width/2;
    int cy = height/2;
    boolean initilized = false;

    double FOV = 0.5 * Math.PI;

    Camera cam = new Camera(1.0, 5.0, 3.0);
    Camera cam1 = new Camera(10.0, 50.0, 30.0);
    long lastFpsCheck = System.currentTimeMillis();

    public static JFrame frame = new JFrame("3D Engine");

    Robot robot;

    static Keyboard keyboard = new Keyboard();

    Image img;


    public static void main(String[] args) {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Canvas canvas = new Main();
        canvas.setSize(width, height);
        canvas.addKeyListener(keyboard);
        canvas.setFocusable(true);
        canvas.setBackground(Color.black);
        frame.add(canvas);
        frame.pack();
        frame.setVisible(true);
        BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);

        // Create a new blank cursor.
        Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
            cursorImg, new Point(0, 0), "blank cursor");

        // Set the blank cursor to the JFrame.
        canvas.setCursor(blankCursor);
    }

    void init() {
        try {
            robot = new Robot();
        } catch (AWTException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    double[] rotate2D(double[] pos,double[] rot) {
        double x = pos[0];
        double y = pos[1];

        double s = rot[0];
        double c = rot[1];

        double[] result = {(x * c) - (y * s), (y * c) + (x * s)};

        return result;
    }


    public void paint(Graphics MainGraphics) {
        Point startMousePos = MouseInfo.getPointerInfo().getLocation();
        double startMouseX = startMousePos.getX();
        double startMouseY = startMousePos.getY();

        if(img == null)
        {
            img = createImage(width, height);
        }
        Graphics g = img.getGraphics();;

        // First run initialization
        if (initilized == false) {
            initilized = true;
            init();
        }

        // Storing start time for FPS Counting
        long startTime = System.currentTimeMillis();

        // Clearing Last Frame
        //g.clearRect(0, 0, width, height);

        // Drawing Crosshair
        g.setColor(Color.white);
        g.fillRect(cx - 8, cy - 1, 16, 2);
        g.fillRect(cx - 1, cy - 8, 2, 16);


        // Drawing Debugger Menu
        g.drawString("HI WASSUp", 0, 16);





        g.dispose();


        if (frame.isFocused() == true) {
            robot.mouseMove(cx, cy);
            Point endMousePos = MouseInfo.getPointerInfo().getLocation();
            double endMouseX = endMousePos.getX();
            double endMouseY = endMousePos.getY();
            double[] rel = {startMouseX - endMouseX, startMouseY - endMouseY};
            cam.mouseMotion(rel);
        }




        // Limiting FPS
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // Calculating FPS
        long endTime = System.currentTimeMillis();
        double delta_time = (endTime - startTime);
        if ((lastFpsCheck + 1000) < endTime) {
            lastFpsCheck = endTime;
            frame.setTitle("3D Engine - FPS: " + (int) (1000/delta_time));
        }

        // Controlling camera movement
        if (keyboard.getW() == true) {
            cam.update(delta_time, "W");
        }
        if (keyboard.getA() == true) {
            cam.update(delta_time, "A");
        }
        if (keyboard.getS() == true) {
            cam.update(delta_time, "S");
        }
        if (keyboard.getD() == true) {
            cam.update(delta_time, "D");
        }
        if (keyboard.getE() == true) {
            cam.update(delta_time, "E");
        }
        if (keyboard.getQ() == true) {
            cam.update(delta_time, "Q");
        }

        // Draw rendered frame
        MainGraphics.drawImage(img, 0,0, null);

        // Draw next frame
        repaint();
    }

}

我最近发布了一个关于这段代码的问题,如果你愿意,你可以从上一篇文章中检查键盘 java,但是请帮助我,我是 java 编程新手(我仍然有一些编程经验),谢谢你

【问题讨论】:

  • 好的,让我们从JPanel 开始,默认情况下是双缓冲的,所以你真的应该从那里开始。当你想控制绘制过程时,你想使用Canvas,这意味着你真的想使用BufferStrategy,但我会看JavaDocs,因为这个例子更好
  • 不要在paint 方法中执行Thread.sleep(1000);,直到paint 方法返回后才会呈现任何内容。您想将“更新通道”与“绘制通道”分开。绘画没有别的作用。您的所有决策都应该作为“主循环”的“更新传递”的一部分完成,它应该在事件调度线程之外执行(在这种情况下),以防止可能出现的问题,但这会引发一堆问题其他问题
  • 我使用了 Thread.sleep(1000);仅限制 FPS,它是 1000/60,但我将其更改为这个,因为我认为问题可能在于渲染速度
  • 另外,当我尝试使用 JPanel 时,它根本没有渲染,您能否将 JPanel 代码发送给我作为答案?
  • 没关系,我可以通过不使用paint()方法绘制图形来解决这个问题,无论如何感谢您的帮助

标签: java graphics awt double-buffering


【解决方案1】:

你的问题的答案很复杂。

  1. Java Swing JPanel(或JComponent)默认是双缓冲的
  2. Swing 已经有一个绘画机制,你无法控制它,所以你需要在它的功能范围内工作。
  3. 您使用java.awt.Canvas 的唯一真正原因是您想完全控制绘画过程

我建议您做的第一件事是查看Performing Custom PaintingPainting in AWT and Swing,以更好地了解Swing/AWT 中的绘画工作原理。这将使您更好地了解 API,以及您是想使用它还是定义自己的 API。

其他一些关注领域:

  • 不要在paint 方法中执行Thread.sleep(1000);,直到paint 方法返回之后才会呈现任何内容。您想将“更新通道”与“绘制通道”分开。绘画没有别的作用。您所做的所有决策都应该作为“主循环”中“更新传递”的一部分完成,这应该在事件调度线程之外执行(在这种情况下),以防止可能出现的问题,但这会引发一堆问题其他问题
  • MouseInfo.getPointerInfo().getLocation() 不是您应该跟踪鼠标位置的方式。 Swing 已经提供了许多跟踪鼠标事件的机制。有关详细信息,请参阅 How to write a Mouse ListenerHow to Write a Mouse-Motion Listener
  • 基于每件事,我还关心您如何跟踪键盘输入,并强烈建议您查看How to Use Key Bindings,了解最常用的管理用户键盘输入的方法。

以下示例仅使用JPanel 作为其主要渲染表面。它利用了预先存在的绘制过程,并使用 SwingTimer 作为“主循环”机制,负责处理用户输入并在安排新的绘制过程之前更新状态。

请记住,Swing 不是线程安全的,您不应从事件调度线程的上下文之外更新 UI 或 UI 可能依赖的任何内容。

import java.awt.Dimension;
import java.awt.EventQueue;
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.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
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;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                Main main = new Main();
                frame.add(main);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                main.start();
            }
        });
    }

    // Decouple the input from the implementation
    enum Input {
        UP, DOWN, LEFT, RIGHT
    }

    public class Main extends JPanel {

        boolean initilized = false;

        double FOV = 0.5 * Math.PI;

        private Instant lastFpsCheck = Instant.now();
        private Point mousePosition;
        private Timer timer;

        private Set<Input> input = new HashSet<>();

        public Main() {
            MouseAdapter mouseHandler = new MouseAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    // This is within the components coordinate space
                    mousePosition = e.getPoint();
                }

                @Override
                public void mouseEntered(MouseEvent e) {
                    mousePosition = e.getPoint();
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    mousePosition = null;
                }
            };

            addMouseMotionListener(mouseHandler);
            addMouseListener(mouseHandler);

            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");

            actionMap.put("Pressed.up", new InputAction(input, Input.UP, true));
            actionMap.put("Released.up", new InputAction(input, Input.UP, false));
            actionMap.put("Pressed.down", new InputAction(input, Input.DOWN, true));
            actionMap.put("Released.down", new InputAction(input, Input.DOWN, false));
            actionMap.put("Pressed.left", new InputAction(input, Input.LEFT, true));
            actionMap.put("Released.left", new InputAction(input, Input.LEFT, false));
            actionMap.put("Pressed.right", new InputAction(input, Input.RIGHT, true));
            actionMap.put("Released.right", new InputAction(input, Input.RIGHT, false));

            timer = new Timer(15, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent evt) {
                    update();
                }
            });
        }

        public void start() {
            startTime = Instant.now();
            timer.start();
        }

        public void stop() {
            timer.stop();
        }

        // The start time of a given cycle
        private Instant startTime;
        // The estimated number of frames per second
        private double fps = 0;
        // The number of acutal updates performed
        // within a given cycle
        private int updates = 0;

        protected void update() {

            if (startTime == null) {
                startTime = Instant.now();
            }

            if (input.contains(Input.UP)) {
                //cam.update(delta_time, "W");
            }
            if (input.contains(Input.LEFT)) {
                //cam.update(delta_time, "A");
            }
            if (input.contains(Input.DOWN)) {
                //cam.update(delta_time, "S");
            }
            if (input.contains(Input.RIGHT)) {
                //cam.update(delta_time, "D");
            }
            // Don't know what these do, so you will need to devices
            // your own action
            //if (input.contains(Input.UP)) {
            //cam.update(delta_time, "E");
            //}
            //if (input.contains(Input.UP)) {
            //cam.update(delta_time, "Q");
            //}

            Instant endTime = Instant.now();
            Duration deltaTime = Duration.between(startTime, endTime);
            if (lastFpsCheck.plusSeconds(1).isBefore(endTime)) {
                System.out.println(deltaTime.toMillis());
                lastFpsCheck = endTime;
                fps = updates;
                updates = 0;
                startTime = Instant.now();
            }

            updates++;
            repaint();
        }

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

        double[] rotate2D(double[] pos, double[] rot) {
            double x = pos[0];
            double y = pos[1];

            double s = rot[0];
            double c = rot[1];

            double[] result = {(x * c) - (y * s), (y * c) + (x * s)};

            return result;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            //Point startMousePos = MouseInfo.getPointerInfo().getLocation();
            //double startMouseX = startMousePos.getX();
            //double startMouseY = startMousePos.getY();

            Graphics2D g2d = (Graphics2D) g.create();

            // Drawing Debugger Menu
            g2d.drawString("HI WASSUp", 0, 20);

            if (mousePosition != null) {
                g2d.drawString(mousePosition.x + "x" + mousePosition.y, 0, 40);
                // Your old code is broken, because MouseInfo.getPointerInfo 
                // doesn't give you the position of the mouse from within
                // the components coordinate space, but in the screen space
                // instead
                //robot.mouseMove(cx, cy);
                //Point endMousePos = MouseInfo.getPointerInfo().getLocation();
                //double endMouseX = endMousePos.getX();
                //double endMouseY = endMousePos.getY();
                //double[] rel = {startMouseX - endMouseX, startMouseY - endMouseY};
                //cam.mouseMotion(rel);
            }

            g2d.drawString(Double.toString(fps), 0, 60);

            StringJoiner sj = new StringJoiner(", ");
            for (Input item : input) {
                switch (item) {
                    case DOWN:
                        sj.add("down");
                        break;
                    case UP:
                        sj.add("up");
                        break;
                    case LEFT:
                        sj.add("left");
                        break;
                    case RIGHT:
                        sj.add("right");
                        break;
                }
            }

            g2d.drawString(sj.toString(), 0, 80);
            g2d.dispose();
        }

        public class InputAction extends AbstractAction {

            private final Set<Input> input;
            private final Input direction;
            private final boolean add;

            public InputAction(Set<Input> input, Input direction, boolean add) {
                this.input = input;
                this.direction = direction;
                this.add = add;
            }

            @Override
            public void actionPerformed(ActionEvent evt) {
                if (add) {
                    input.add(direction);
                } else {
                    input.remove(direction);
                }
            }

        }

    }
}

现在,由于 Swing 绘制过程的工作方式,FPS 充其量只是一个“猜测”,我个人不会过分依赖它。我可能会考虑将Timer 设置为使用 5 毫秒的延迟,并尽可能快地进行。

现在,如果您绝对、肯定地必须毫无疑问地完全控制绘画过程,那么您需要从 java.awt.Canvas 开始并使用 BufferStrategy API。

这将使您完全控制绘画过程。它更复杂,需要您考虑更多的边缘情况,但会为您提供对绘制过程发生时的调度的完全控制,从而更好地控制 FPS。

我建议你看看JavaDocs,因为这个例子更好。

我使用了 Thread.sleep(1000);仅限制 FPS,它是 1000/60,但我将其更改为这个,因为我认为问题可能在于渲染速度

坦率地说,这是一种幼稚的方法,表明对绘画过程的工作原理缺乏了解 - 没有冒犯性,你必须从某个地方开始。但是更好的起点是阅读我在上面提供的可用文档,这样您就可以更好地了解 API 的实际工作原理,并就是否要使用它做出更好的决定(即JPanel)或自己动手(即Canvas

【讨论】:

  • 感谢您的回答,我已经弄清楚了如何使用 BufferStrategy 并在我的画布中使用它。
猜你喜欢
  • 1970-01-01
  • 2016-07-27
  • 2012-07-29
  • 2012-01-28
  • 2013-11-30
  • 2012-09-01
  • 1970-01-01
  • 2021-04-28
  • 2014-04-07
相关资源
最近更新 更多