Java - 如何调整 2D 游戏的大小?

Java - how do I resize my 2D Game?

我正在开发一款 2D 游戏,但在调整大小时卡住了。因此,正如您在下面的代码中看到的那样,我将所有游戏内容绘制到一个 BufferedImage 中。之后,我从 JPanel 获取图形并在其上绘制图像。

所以我的问题是现在如何调整我的游戏大小?我认为有很多可能性:1 是图像也只是用 jpanel 调整大小,另一种方法可能是如果你有一个 2d 世界并调整框架的大小,游戏将仍然是相同的大小但是你现在可以看到更多的 2d 世界,比如你的观看距离增加了。

有人知道我的代码的解决方案吗?

package de.avarion.theenchanter;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Game implements Runnable{

    public static int WIDTH = 800;
    public static int HEIGHT = 600;
    public static int FPS = 30;

    private JFrame frame;
    private JPanel panel;

    private BufferedImage image;
    private Graphics2D g;

    private Thread thread;
    private boolean running


    public Game() {
        panel = new JPanel();
        panel.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        panel.setFocusable(true);
        panel.requestFocus();

        frame = new JFrame("The Enchanter - pre Alpha");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(panel, BorderLayout.CENTER);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setResizable(true);
        frame.setVisible(true);

        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        g = (Graphics2D) image.getGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        running = true;

        thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() { //game loop
        while(running) {
            update();
            render();
            try {
                Thread.sleep(1000/FPS);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    private void update() {
        if(frame.getWidth() != WIDTH || frame.getHeight() != HEIGHT) {
            frame.pack();
        }
    }

    private void render() {
        g.setColor(Color.black);//drawing on my image
        g.fillRect(0, 0, WIDTH, HEIGHT);

        Graphics g2 = panel.getGraphics(); // get graphics from panel
        g2.drawImage(image, 0, 0, null); //drawing on my panel
        g2.dispose();       
    }   
}

让我们从 Graphics g2 = panel.getGraphics(); 开始 - 这不是在 Swing 中完成绘画的方式。 Swing 使用被动渲染算法,这意味着绘制周期可能会在任何时间以任何原因发生,其中许多是您无法控制或不知道的。

使用您当前的方法,充其量,您 运行 有引入闪烁的风险,因为您的渲染循环和 Swing 相互竞争,或者由于您的线程和 Swing 之间的线程竞争条件而进行部分更新美国东部时间。

你也不应该 dispose 不是你自己创建的 Graphics 上下文,在某些系统上,这实际上可以防止绘制任何东西。

查看 Painting in AWT and Swing, Performing Custom Painting and Concurrency in Swing 了解更多详情。

一个简单的解决方案是覆盖 Swing 组件的 paintComponent 方法,例如 JPanel 并直接在其中执行自定义绘制。这使您不仅可以确定组件的当前大小,而且可以在调整组件大小时自动调用 paintComponent 方法。

默认情况下,Swing 组件是双缓冲的,因此您可以免费自动获得它

您可以使用 Swing Timer 作为 "game loop",更新游戏的当前状态并使用 RepaintManager 安排 repaint 请求,用于例如...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private int xPos, yPos;
        private int xDelta = 2;

        public TestPane() {
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    int x = (int) (getWidth() * 0.1);
                    int y = (int) (getHeight() * 0.1);
                    int width = getWidth() - (x * 2);
                    int height = getHeight()- (y * 2);

                    xPos += xDelta;
                    yPos = y + ((height - 5) / 2);

                    if (xPos + 10 > x + width) {
                        xPos = x + (width - 10);
                        xDelta *= -1;
                    } else if (xPos < x) {
                        xPos = x;
                        xDelta *= -1;
                    }

                    repaint();
                }
            });
            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            int x = (int) (getWidth() * 0.1);
            int y = (int) (getHeight() * 0.1);
            g2d.drawRect(x, y, getWidth() - (x * 2), getHeight() - (y * 2));
            g2d.setColor(Color.RED);
            g2d.drawOval(xPos, yPos, 10, 10);
            g2d.dispose();
        }

    }

}

如果您想要能够更好地控制渲染过程的东西,那么您需要考虑使用 BufferStrategy 之类的东西。这允许您使用一个活跃的绘画过程,在您想要的时候绘画到缓冲区,而不用担心其他线程也绘画到它。

有关详细信息,请参阅 BufferStrategy and BufferCapabilities