Java 2D 游戏图形

16

下学期我们将学习在团队中制作Java应用程序的模块。该模块的要求是制作一个游戏。在圣诞假期期间,我进行了一些练习,但我无法找到绘制图形的最佳方法。

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

5个回答

19
你需要创建一个带有BufferStrategy的画布组件并在其上进行渲染。下面的代码可以展示这个过程,我从自己编写的引擎中提取了这段代码,你可以在这里查看。
性能完全取决于你想要绘制的内容,我的游戏主要使用图像。即使有大约1500个图像,我在480x480分辨率下仍然能达到200 FPS以上。而当禁用帧限制时,只有100张图像就可以达到6k FPS。
我创建了一个小游戏(屏幕上同时有大约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();
    }
}

有趣的是,strategy.show() 可以安全地从 EDT 之外调用吗? - Pool
使用第二个线程进行短暂测试,结果是安全的。至于try/catch,那只是因为Toolkit.getDefaultToolkit().sync()在极少数情况下可能会引发异常。 - Ivo Wetzel
我在屏幕清除方面遇到了一些奇怪的结果。在调用绘制方法之前,我会先绘制一个填充满的黑色矩形以覆盖整个屏幕。然而,屏幕上仍然没有完全清除上一帧的内容。你有任何想法我可能做错了什么吗? - Martin
嗯,我对上面的代码没有问题,可能是你的代码改变了宽度/高度变量的值? - Ivo Wetzel
你也可以使用AWT的Frame来实现相同的功能,这样就不会违反“不要混合使用Swing和AWT”的建议。 - Diogo Schneider
显示剩余3条评论

8
闪烁是因为你直接写在屏幕上。使用缓冲区绘制,然后一次性写入整个屏幕。这就是所谓的“双缓冲”,可能你以前听说过。这里提供了最简单的方法,点击这里
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。 创建离屏图像很昂贵,因此建议仅在第一次调用时创建它。
还有其他方面可以进一步改善,例如创建兼容图像,使用剪辑等等。要更精细地控制动画,您应该研究主动渲染
我已经收藏了一个不错的页面,讨论游戏教程这里
祝你好运!

3

JOGL很好,但我怀疑我能否说服团队的其他成员使用它。这些团队在各种技能水平上都有人才,虽然我是那种业余时间制作游戏并为乐趣编写并发代码的人,但组内的其他人将希望尽可能保持简单(不幸的是)。 - Martin

2

我想你在paint(Graphics g)中进行了覆盖?这不是正确的方法。请改用相同的代码,但将其放在paintComponent(Graphics g)中而不是paint(Graphics g)中。

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


所以我只需要从画图工具复制代码到画图组件中,一切都会保持相同,除了它将是双缓冲的? - Martin
是的,这就是我的意思。The Feast的回答描述了发生了什么。但是Java已经内置了一个解决方案。The Feast所做的只是避免使用paintComponent并制定自己的解决方案。 - Martijn Courteaux

0
有一种简单的方法来优化你的程序。摆脱任何复杂的代码,只使用JComponent代替Canvas并在其上绘制你的对象。这就是全部。享受它...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接