java 在不冻结 UI 线程的情况下实现游戏循环的最佳方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2861933/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 23:11:45  来源:igfitidea点击:

Best way to implement game loop without freezing UI thread

javamultithreadingswinggame-loop

提问by Matthew H

I'm trying to make a simple 2D game in Java.

我正在尝试用 Java 制作一个简单的 2D 游戏。

So far I have a JFrame, with a menubar, and a class which extends JPaneland overrides it's paintmethod. Now, I need to get a game loop going, where I will update the position of images and so on. However, I'm stuck at how best to achieve this. Should I use multi-threading, because surely, if you put an infinite loop on the main thread, the UI (and thus my menu bar) will freeze up?

到目前为止,我有一个JFrame带有菜单栏的 , 和一个扩展JPanel和覆盖它的paint方法的类。现在,我需要进行一个游戏循环,在那里我将更新图像的位置等等。但是,我被困在如何最好地实现这一目标上。我应该使用多线程吗,因为如果你在主线程上放置一个无限循环,UI(以及我的菜单栏)肯定会冻结吗?

Here's my code so far:

到目前为止,这是我的代码:

import java.awt.Color;
import java.awt.Graphics;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class GameCanvas extends JPanel {

    public void paint(Graphics g) {
        while (true) {
            g.setColor(Color.DARK_GRAY);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

@SuppressWarnings("serial")
public class Main extends JFrame {

    GameCanvas canvas = new GameCanvas();
    final int FRAME_HEIGHT = 400;
    final int FRAME_WIDTH = 400;

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

    public Main() {
        super("Game");

        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        JMenuItem startMenuItem = new JMenuItem("Pause");
        menuBar.add(fileMenu);
        fileMenu.add(startMenuItem);

        super.add(canvas);
        super.setVisible(true);
        super.setSize(FRAME_WIDTH, FRAME_WIDTH);
        super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        super.setJMenuBar(menuBar);
    }
}

Any pointers or tips? Where should I put my loop? In my main class, or my GameCanvas class?

任何指示或提示?我应该把我的循环放在哪里?在我的主类中,还是在我的 GameCanvas 类中?

采纳答案by Jay

You need one thread for you game loop and one thread to handle Swing UI events like mouse clicks and keypresses.

您需要一个线程用于游戏循环和一个线程来处理 Swing UI 事件,例如鼠标点击和按键。

When you use the Swing API, you automatically get an additional thread for your UI called the event dispatch thread. Your callbacks are executed on this thread, not the main thread.

当您使用 Swing API 时,您会自动为您的 UI 获得一个额外的线程,称为事件调度线程。您的回调在此线程上执行,而不是在主线程上执行。

Your main thread is fine for your game loop if you want the game to start automatically when the programs runs. If you want to start and stop the game with a Swing GUI, then have then main thread start a GUI, then the GUI can create a new thread for the game loop when the user wants to start the game.

如果您希望游戏在程序运行时自动启动,您的主线程非常适合您的游戏循环。如果你想用 Swing GUI 开始和停止游戏,然后让主线程启动一个 GUI,那么当用户想要开始游戏时,GUI 可以为游戏循环创建一个新线程。

No, your menu bar will not freeze up if you put your game loop in the main thread. Your menu bar will freeze up if your Swing callbacks take a long time to finish.

不,如果您将游戏循环放在主线程中,您的菜单栏不会冻结。如果您的 Swing 回调需要很长时间才能完成,您的菜单栏将冻结。

Data that is shared between the threads will need to be protected with locks.

线程之间共享的数据需要用锁保护。

I suggest you factor your Swing code into its own class and only put your game loop inside your main class. If you're using the main thread for your game loop, this is a rough idea of how you could design it.

我建议你将你的 Swing 代码分解到它自己的类中,并且只将你的游戏循环放在你的主类中。如果您在游戏循环中使用主线程,这是您如何设计它的粗略想法。

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

class GUI extends JFrame {
    GameCanvas canvas = new GameCanvas();
    final int FRAME_HEIGHT = 400;
    final int FRAME_WIDTH = 400;


    public GUI() {
        // build and display your GUI
        super("Game");

        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        JMenuItem startMenuItem = new JMenuItem("Pause");
        menuBar.add(fileMenu);
        fileMenu.add(startMenuItem);

        super.add(canvas);
        super.setVisible(true);
        super.setSize(FRAME_WIDTH, FRAME_WIDTH);
        super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        super.setJMenuBar(menuBar);
    }
}

public class Main {

    public static void main(String args[]) {
        GUI ui = new GUI(); // create and display GUI        

        gameLoop(); // start the game loop
    }

    static void gameLoop() {
        // game loop    
    }
}

回答by Bill K

Your game loop (model) should not be anywhere near any of your GUI classes (view). It uses your GUI classes--but even that you probably want to do through an intermediary (controller). A good way to ensure that you are doing it right is to check that your model doesn't have a single "include javax.swing.???".

您的游戏循环(模型)不应靠近任何 GUI 类(视图)。它使用您的 GUI 类——但即便如此,您也可能希望通过中介(控制器)来实现。确保您做对的一个好方法是检查您的模型是否没有单个“包含 javax.swing.???”。

The best thing you could do is to keep the game loop in it's own thread. Whenever you want to make a change in the GUI, use SwingWorker or whatever the young kids use now to force it onto the GUI thread for just that one operation.

你能做的最好的事情就是将游戏循环保持在它自己的线程中。每当您想在 GUI 中进行更改时,请使用 SwingWorker 或任何年轻的孩子现在使用的工具将其强制执行到 GUI 线程中以进行该操作。

This is actually awesome because it makes you think in terms of GUI Operations (which would constitute your controller). For instance, you might have a class called "Move" that would have the GUI logic behind a move. Your game loop might instantiate a "Move" with the right values (item to move, final location) and pass it to a GUI loop for processing.

这实际上很棒,因为它让您从 GUI 操作(这将构成您的控制器)方面进行思考。例如,您可能有一个名为“Move”的类,它在移动背后具有 GUI 逻辑。您的游戏循环可能会使用正确的值(要移动的项目、最终位置)实例化“移动”并将其传递给 GUI 循环进行处理。

Once you get to that point, you realize that simply adding a trivial "undo" for each GUI operation allows you to easily undo any number of operations. You will also find it easier to replace your Swing GUI with a web GUI...

一旦达到了这一点,您就会意识到只需为每个 GUI 操作添加一个简单的“撤消”就可以轻松撤消任意数量的操作。您还会发现用 Web GUI 替换 Swing GUI 更容易...

回答by bitc

Java is really suited for event-driven programming. Basically, set up a timer event and listen. At each 'tick-tock' you update your game logic. At each GUI-event you update your data structures that the game logic method will read.

Java 非常适合事件驱动编程。基本上,设置一个计时器事件并监听。在每次“滴答”时,您都会更新您的游戏逻辑。在每个 GUI 事件中,您更新游戏逻辑方法将读取的数据结构。