Canvas 双缓冲图形不工作

Canvas double buffered graphics not working

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

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

}

我最近 post 提出了一个关于这段代码的问题,如果你愿意的话,你可以检查最后一个 post 的键盘 java,但是请帮我解决这个问题java 编程新手(不过我还有一些编程经验),谢谢

你的问题的答案很复杂。

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

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

其他一些值得关注的领域:

  • 不要在 paint 方法中执行 Thread.sleep(1000);,在 paint 方法 returns 之前不会呈现任何内容。您想要将 "update pass" 与 "paint pass" 分开。绘画对颜料没有任何作用。你所有的决策都应该作为你的 "main loop" 的 "update pass" 的一部分来完成,它应该在事件调度线程之外执行(在这种情况下),以防止可能的问题,但随后会引发一堆其他问题
  • MouseInfo.getPointerInfo().getLocation() 不是您跟踪鼠标位置的方式。 Swing 已经提供了许多用于跟踪鼠标事件的机制。有关详细信息,请参阅 How to write a Mouse Listener and How to Write a Mouse-Motion Listener
  • 基于每一件事,我也很关心您如何跟踪键盘输入,并强烈建议您查看 How to Use Key Bindings 以了解管理键盘输入的最常用推荐方法来自用户。

以下示例仅使用 JPanel 作为主要渲染表面。它利用预先存在的绘画过程并使用 Swing Timer 作为 "main loop" 机制,负责处理用户输入并在安排新的绘画过程之前更新状态。

请记住,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 充其量是 "guesstimate",我个人不会过分依赖它。我可能会考虑将 Timer 设置为使用 5 毫秒的延迟,并尽可能快地进行。

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

这将使您能够完全控制绘画过程。它更复杂,需要您考虑更多的边缘情况,但可以让您在绘制过程发生时完全控制调度,从而更好地控制 FPS。

我建议看一下 JavaDocs,因为示例更好。

I used the Thread.sleep(1000); to limit FPS only, It was 1000/60 but I changed it to this because I thought the problem may be in the speed of rendering

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