3

我正在使用 Java Swing 框架开发游戏。有谁知道基于 Swing 的好框架?我主要关心重绘的性能。

4

2 回答 2

4

Swing 适用于简单的游戏,但如果您真的关心重绘的性能,您可能应该看看基于 OpenGL 的框架之一。例子:

特别是,如果你想做更复杂的效果(例如很多颜色、阴影、透明效果),那么你可能需要 OpenGL。

于 2012-12-06T09:08:33.183 回答
3

这个简单的固定时间步长游戏循环(我改编自作者的参考资料)从未让我失望。

它将允许以精确的 60 fps(或任何您制作的速度)绘制,也可以更改赫兹,它还可以通过 Graphics2D 和一些其他效果实现抗锯齿。

原始作者的示例包括插值检查,但我发现它在我的游戏中给我带来了一些问题,比如图片在他们的位置闪烁,所以我保留了它,但如果你遇到问题,至少你会知道是什么原因造成的):

在此处输入图像描述

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class GameLoopTest implements ActionListener {

    private GamePanel gamePanel;
    private JButton startButton;
    private JButton quitButton;
    private JButton pauseButton;
    private boolean running = false;
    private boolean paused = false;

    public GameLoopTest() {
        JFrame frame = new JFrame("Fixed Timestep Game Loop Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        gamePanel = new GamePanel(500, 500);
        startButton = new JButton("Start");
        quitButton = new JButton("Quit");
        pauseButton = new JButton("Pause");
        pauseButton.setEnabled(false);

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(1, 2));

        startButton.addActionListener(this);
        quitButton.addActionListener(this);
        pauseButton.addActionListener(this);

        buttonPanel.add(startButton);
        buttonPanel.add(pauseButton);
        buttonPanel.add(quitButton);
        frame.add(gamePanel);
        frame.add(buttonPanel, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new GameLoopTest();
            }
        });
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Object s = e.getSource();
        if (s == startButton) {
            running = !running;
            if (running) {
                startButton.setText("Stop");
                pauseButton.setEnabled(true);
                runGameLoop();
            } else {
                startButton.setText("Start");
                pauseButton.setEnabled(false);
            }
        } else if (s == pauseButton) {
            paused = !paused;
            if (paused) {
                pauseButton.setText("Unpause");
            } else {
                pauseButton.setText("Pause");
            }
        } else if (s == quitButton) {
            System.exit(0);
        }
    }

    //Starts a new thread and runs the game loop in it.
    public void runGameLoop() {
        Thread loop = new Thread(new Runnable() {
            @Override
            public void run() {
                gameLoop();
            }
        });
        loop.start();
    }

    //Only run this in another Thread!
    private void gameLoop() {
        //This value would probably be stored elsewhere.
        final double GAME_HERTZ = 30.0;
        //Calculate how many ns each frame should take for our target game hertz.
        final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
        //At the very most we will update the game this many times before a new render.
        //If you're worried about visual hitches more than perfect timing, set this to 1.
        final int MAX_UPDATES_BEFORE_RENDER = 5;
        //We will need the last update time.
        double lastUpdateTime = System.nanoTime();
        //Store the last time we rendered.
        double lastRenderTime = System.nanoTime();

        //If we are able to get as high as this FPS, don't render again.
        final double TARGET_FPS = 60;
        final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;

        //Simple way of finding FPS.
        int lastSecondTime = (int) (lastUpdateTime / 1000000000);

        while (running) {
            double now = System.nanoTime();
            int updateCount = 0;

            if (!paused) {
                //Do as many game updates as we need to, potentially playing catchup.
                while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {
                    updateGame();
                    lastUpdateTime += TIME_BETWEEN_UPDATES;
                    updateCount++;
                }

                //If for some reason an update takes forever, we don't want to do an insane number of catchups.
                //If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
                if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
                    lastUpdateTime = now - TIME_BETWEEN_UPDATES;
                }

                //Render. To do so, we need to calculate interpolation for a smooth render.
                float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES));
                drawGame(interpolation);
                lastRenderTime = now;

                //Update the frames we got.
                int thisSecond = (int) (lastUpdateTime / 1000000000);
                int frameCount = gamePanel.getFrameCount();
                if (thisSecond > lastSecondTime) {
                    System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
                    gamePanel.setFps(frameCount);
                    frameCount = 0;
                    lastSecondTime = thisSecond;
                }

                //Yield until it has been at least the target time between renders. This saves the CPU from hogging.
                while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
                    //allow the threading system to play threads that are waiting to run.
                    Thread.yield();

                    //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
                    //You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
                    //FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
                    //On my OS it does not unpuase the game if i take this away
                    try {
                        Thread.sleep(1);
                    } catch (Exception e) {
                    }

                    now = System.nanoTime();
                }
            }
        }
    }

    private void updateGame() {
        gamePanel.update();
    }

    private void drawGame(float interpolation) {
        gamePanel.setInterpolation(interpolation);

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                gamePanel.repaint();
            }
        });
    }
}

class GamePanel extends JPanel {

    float interpolation;
    float ballX, ballY, lastBallX, lastBallY;
    int ballWidth, ballHeight;
    float ballXVel, ballYVel;
    float ballSpeed;
    int lastDrawX, lastDrawY;
    private int frameCount = 0;
    private int fps = 0;
    int width, height;

    public GamePanel(int width, int height) {
        super(true);
        ballX = lastBallX = 100;
        ballY = lastBallY = 100;
        ballWidth = 25;
        ballHeight = 25;
        ballSpeed = 25;
        ballXVel = (float) Math.random() * ballSpeed * 2 - ballSpeed;
        ballYVel = (float) Math.random() * ballSpeed * 2 - ballSpeed;
        this.width = width;
        this.height = height;
    }

    public void setInterpolation(float interp) {
        interpolation = interp;
    }

    public void update() {
        lastBallX = ballX;
        lastBallY = ballY;

        ballX += ballXVel;
        ballY += ballYVel;

        if (ballX + ballWidth / 2 >= getWidth()) {
            ballXVel *= -1;
            ballX = getWidth() - ballWidth / 2;
            ballYVel = (float) Math.random() * ballSpeed * 2 - ballSpeed;
        } else if (ballX - ballWidth / 2 <= 0) {
            ballXVel *= -1;
            ballX = ballWidth / 2;
        }

        if (ballY + ballHeight / 2 >= getHeight()) {
            ballYVel *= -1;
            ballY = getHeight() - ballHeight / 2;
            ballXVel = (float) Math.random() * ballSpeed * 2 - ballSpeed;
        } else if (ballY - ballHeight / 2 <= 0) {
            ballYVel *= -1;
            ballY = ballHeight / 2;
        }
    }

    public int getFrameCount() {
        return frameCount;
    }

    public void setFrameCount(int frameCount) {
        this.frameCount = frameCount;
    }

    void setFps(int fps) {
        this.fps = fps;
    }
    private final static RenderingHints textRenderHints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    private final static RenderingHints imageRenderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    private final static RenderingHints colorRenderHints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    private final static RenderingHints interpolationRenderHints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    private final static RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

    public void applyRenderHints(Graphics2D g2d) {
        g2d.setRenderingHints(textRenderHints);
        g2d.setRenderingHints(imageRenderHints);
        g2d.setRenderingHints(colorRenderHints);
        g2d.setRenderingHints(interpolationRenderHints);
        g2d.setRenderingHints(renderHints);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;

        //applys effects like anti alising for images and tetx, as well as sets the renderinf value to quality etc
        applyRenderHints(g2d);

        g2d.setColor(Color.RED);
        int drawX = (int) ((ballX - lastBallX) + lastBallX - ballWidth / 2);
        int drawY = (int) ((ballY - lastBallY) + lastBallY - ballHeight / 2);
        g2d.fillOval(drawX, drawY, ballWidth, ballHeight);

        lastDrawX = drawX;
        lastDrawY = drawY;

        g2d.setColor(Color.BLACK);
        g2d.drawString("FPS: " + fps, 5, 10);

        frameCount++;
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }
}

更新:

我已经开始了自己的Swing 游戏库,如果不使用它可能会有用,然后只是从中借用一些概念。

参考:

于 2012-12-06T09:04:08.850 回答