【问题标题】:Java 2D game graphicsJava 2D 游戏图形
【发布时间】:2009-12-26 14:22:00
【问题描述】:

下学期我们有一个模块,用于在团队中制作 Java 应用程序。该模块的要求是制作游戏。在圣诞假期里,我一直在做一些练习,但我不知道绘制图形的最佳方法。

我正在使用 Java Graphics2D 对象在屏幕上绘制形状,并每秒调用 30 次 repaint(),但这会严重闪烁。有没有更好的方法在 Java 中绘制高性能 2D 图形?

【问题讨论】:

    标签: java performance graphics


    【解决方案1】:

    您要做的是创建一个带有 BufferStrategy 的画布组件并对其进行渲染,下面的代码应该向您展示它是如何工作的,我已经通过here 从我自己编写的引擎中提取了代码。

    性能完全取决于您要绘制的内容,我的游戏主要使用图像。大约有 1500 个,我在 480x480 时仍然高于 200 FPS。在禁用帧限制时,我只有 100 张图像,达到 6k FPS。

    可以在here 找到我创建的一个小游戏(这个游戏在屏幕上一次有大约 120 个图像)(是的,下面的方法也可以作为小程序使用。)

    import java.awt.Canvas;
    import java.awt.Color;
    import java.awt.Graphics2D;
    import java.awt.GraphicsConfiguration;
    import java.awt.GraphicsEnvironment;
    import java.awt.Toolkit;
    import java.awt.Transparency;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    import java.awt.image.BufferStrategy;
    import java.awt.image.BufferedImage;
    
    import javax.swing.JFrame;
    import javax.swing.WindowConstants;
    
    public class Test extends Thread {
        private boolean isRunning = true;
        private Canvas canvas;
        private BufferStrategy strategy;
        private BufferedImage background;
        private Graphics2D backgroundGraphics;
        private Graphics2D graphics;
        private JFrame frame;
        private int width = 320;
        private int height = 240;
        private int scale = 1;
        private GraphicsConfiguration config =
                GraphicsEnvironment.getLocalGraphicsEnvironment()
                    .getDefaultScreenDevice()
                    .getDefaultConfiguration();
    
        // create a hardware accelerated image
        public final BufferedImage create(final int width, final int height,
                final boolean alpha) {
            return config.createCompatibleImage(width, height, alpha
                    ? Transparency.TRANSLUCENT : Transparency.OPAQUE);
        }
    
        // Setup
        public Test() {
            // JFrame
            frame = new JFrame();
            frame.addWindowListener(new FrameClose());
            frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
            frame.setSize(width * scale, height * scale);
            frame.setVisible(true);
    
            // Canvas
            canvas = new Canvas(config);
            canvas.setSize(width * scale, height * scale);
            frame.add(canvas, 0);
    
            // Background & Buffer
            background = create(width, height, false);
            canvas.createBufferStrategy(2);
            do {
                strategy = canvas.getBufferStrategy();
            } while (strategy == null);
            start();
        }
    
        private class FrameClose extends WindowAdapter {
            @Override
            public void windowClosing(final WindowEvent e) {
                isRunning = false;
            }
        }
    
        // Screen and buffer stuff
        private Graphics2D getBuffer() {
            if (graphics == null) {
                try {
                    graphics = (Graphics2D) strategy.getDrawGraphics();
                } catch (IllegalStateException e) {
                    return null;
                }
            }
            return graphics;
        }
    
        private boolean updateScreen() {
            graphics.dispose();
            graphics = null;
            try {
                strategy.show();
                Toolkit.getDefaultToolkit().sync();
                return (!strategy.contentsLost());
    
            } catch (NullPointerException e) {
                return true;
    
            } catch (IllegalStateException e) {
                return true;
            }
        }
    
        public void run() {
            backgroundGraphics = (Graphics2D) background.getGraphics();
            long fpsWait = (long) (1.0 / 30 * 1000);
            main: while (isRunning) {
                long renderStart = System.nanoTime();
                updateGame();
    
                // Update Graphics
                do {
                    Graphics2D bg = getBuffer();
                    if (!isRunning) {
                        break main;
                    }
                    renderGame(backgroundGraphics); // this calls your draw method
                    // thingy
                    if (scale != 1) {
                        bg.drawImage(background, 0, 0, width * scale, height
                                * scale, 0, 0, width, height, null);
                    } else {
                        bg.drawImage(background, 0, 0, null);
                    }
                    bg.dispose();
                } while (!updateScreen());
    
                // Better do some FPS limiting here
                long renderTime = (System.nanoTime() - renderStart) / 1000000;
                try {
                    Thread.sleep(Math.max(0, fpsWait - renderTime));
                } catch (InterruptedException e) {
                    Thread.interrupted();
                    break;
                }
                renderTime = (System.nanoTime() - renderStart) / 1000000;
    
            }
            frame.dispose();
        }
    
        public void updateGame() {
            // update game logic here
        }
    
        public void renderGame(Graphics2D g) {
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, width, height);
        }
    
        public static void main(final String args[]) {
            new Test();
        }
    }
    

    【讨论】:

    • 谢谢!!!这是非常有趣的。 FPS 也有限制。你制作的游戏非常好!
    • 有趣,从 EDT 外部调用 strategy.show() 安全吗?
    • 用第二个线程的简短测试说是的,它是安全的。对于 try/catch,这只是因为 Toolkit.getDefaultToolkit().sync() 在极少数情况下可能会抛出异常。
    • 谢谢,我在这里问了一个问题,关于我是否可以在 Swing 应用程序中使用它stackoverflow.com/questions/1966707/…
    • 我得到了一些奇怪的结果,屏幕没有正确清除。在调用我的绘制方法之前,我正在绘制一个全屏、填充的黑色矩形。但是,屏幕没有从最后一帧正确清除。有什么想法我可能做错了吗?
    【解决方案2】:

    闪烁是因为您直接在屏幕上书写。使用缓冲区进行绘制,然后一次性写入整个屏幕。这是您之前可能听说过的Double BufferingHere 是最简单的形式。

    public void paint(Graphics g)
    {
    
        Image image = createImage(size + 1, size + 1);
        Graphics offG = image.getGraphics();
        offG.setColor(Color.BLACK);
        offG.fillRect(0, 0, getWidth(), getHeight());
        // etc
    

    请参阅屏幕外图形offG 的使用。创建屏幕外图像的成本很高,因此我建议仅在第一次调用时创建它。

    还有其他方面可以进一步改进,例如creating a compatible image,使用clipping 等。要对动画进行更精细的控制,您应该查看active rendering

    我收藏了一个不错的页面,讨论游戏教程here

    祝你好运!

    【讨论】:

      【解决方案3】:

      Java OpenGL (JOGL) 是一种方式。

      【讨论】:

      • JOGL 不错,但我怀疑我能否说服团​​队的其他成员使用它。这些团队涵盖所有技能水平,虽然我是那种在业余时间制作游戏并编写并发代码以获得乐趣的人,但团队中的其他人会希望让事情尽可能简单(不幸的是)
      【解决方案4】:

      我认为您从 paint(Graphics g) 进行了覆盖?这不是好办法。使用相同的代码,但使用paintComponent(Graphics g) 而不是paint(Graphics g)

      您可以搜索的标签是doublebuffer。这将通过覆盖paintComponent 自动完成。

      【讨论】:

      • 所以我可以从字面上将代码从绘画复制到绘画组件,一切都会一样,除了它会被双缓冲?
      • 是的,这就是我的意思。盛宴的答案描述了正在发生的事情。但是 Java 已经内置了一个解决方案。 The Feast 所做的只是避免使用paintComponent 并制定自己的解决方案。
      【解决方案5】:

      有一种优化程序的简单方法。摆脱任何复杂的代码,只需使用JComponent 而不是Canvas 并在其上绘制您的对象。就这样。好好享受吧……

      【讨论】:

        猜你喜欢
        • 2020-07-16
        • 1970-01-01
        • 1970-01-01
        • 2014-01-21
        • 2018-08-19
        • 2018-01-07
        • 2012-02-15
        • 2010-10-18
        • 1970-01-01
        相关资源
        最近更新 更多