Java 游戏循环(绘画)卡住了我的窗口

标签 java swing

我正在使用 cardLayout 更改“ View ”(此类有一个 JFrame 变量)。当用户点击新游戏按钮时,会发生这种情况:

public class Views extends JFrame implements ActionListener {

    private JFrame frame;
    private CardLayout cl;
    private JPanel cards;
    private Game game;

    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        if (command.equals("New game")) {
            cl.show(cards, "Game");

            game.init();
            this.revalidate();
            this.repaint();

            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    game.loop();
                }
            });
        }
    }
}

游戏的循环方式和类标题:

public class Game extends JPanel implements KeyListener {
    public void loop() {
        while (player.isAlive()) {
            try {
                this.update();
                this.repaint();
                // first class JFrame variable
                jframee.getFrame().repaint();
                // first class JFrame variable
                jframee.getFrame().revalidate();
                Thread.sleep(17);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void update() {
        System.out.println("updated");
    }
}

我正在使用 paintComponent() 绘画

public void paintComponent(Graphics g) {
    System.out.println("paint");
    ...
}

其实它并没有画任何东西。当我不调用 loop() 方法(所以它只绘制一次)时,所有图像都被正确绘制。但是当我调用 loop() 方法时,窗口中什么也没有发生。 (即使 JFrame 上的关闭按钮也不起作用。)

如何解决? (当我在游戏类中创建 JFrame 时,一切正常,但现在我想要更多 View ,所以我需要在其他类中使用 JFrame。)

谢谢。

最佳答案

前体:The Event Dispatch Thread (EDT) .

Swing 是单线程的。这是什么意思?

Swing 程序中的所有处理都以事件开始。 EDT 是一个线程,它按照以下几行(但更复杂)循环处理这些事件:

class EventDispatchThread extends Thread {
    Queue<AWTEvent> queue = ...;

    void postEvent(AWTEvent anEvent) {
        queue.add(anEvent);
    }

    @Override
    public void run() {
        while (true) {
            AWTEvent nextEvent = queue.poll();

            if (nextEvent != null) {
                processEvent(nextEvent);
            }
        }
    }

    void processEvent(AWTEvent theEvent) {
        // calls e.g.
        // ActionListener.actionPerformed,
        // JComponent.paintComponent,
        // Runnable.run,
        // etc...
    }
}

调度线程通过抽象对我们隐藏:我们通常只编写监听器回调。

  • 单击一个按钮会发布一个事件 ( in native code ):处理该事件时,将在 EDT 上调用 actionPerformed
  • 调用 repaint 会发布一个事件:处理该事件时,将在 EDT 上调用 paintComponent
  • 调用invokeLater 发布一个事件:处理事件时,run is called on the EDT .
  • Swing 中的一切都始于一个事件。

事件任务按发布顺序依次处理。

只有当当前事件任务返回时,才能处理下一个事件。这就是为什么我们不能在 EDT 上无限循环。 actionPerformed(或 run,在您的编辑中)永远不会返回,因此调用 repaint post 绘制事件但是它们从未被处理过,程序似乎卡住了。

这就是“阻止”EDT 的意思。


基本上有两种方法可以在 Swing 程序中制作动画:

  • 使用 Thread (或 SwingWorker )。

    使用线程的好处是处理是关闭 EDT 完成的,因此如果有密集处理,GUI 仍然可以并发更新。

  • 使用 javax.swing.Timer .

    使用计时器的好处是处理是在 EDT 上完成的,因此无需担心同步,并且可以安全地更改 GUI 组件的状态。

一般来说,如果有理由不使用定时器,我们应该只在 Swing 程序中使用线程。

对于用户来说,它们之间没有明显的区别。

您对 revalidate 的调用向我表明您正在修改循环中组件的状态(添加、删除、更改位置等)。这对于关闭 EDT 来说不一定是安全的。如果您正在修改组件的状态,那么使用计时器而不是线程是一个令人信服的理由。使用没有适当同步的线程会导致难以诊断的细微错误。参见 Memory Consistency Errors .

在某些情况下,组件上的操作是在 tree lock 下完成的(Swing 确保它们本身是线程安全的),但在某些情况下它们不是。


我们可以将一个循环变成如下形式:

while ( condition() ) {
    body();
    Thread.sleep( time );
}

进入以下形式的 Timer:

new Timer(( time ), new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent evt) {
        if ( condition() ) {
            body();

        } else {
            Timer self = (Timer) evt.getSource();
            self.stop();
        }
    }
}).start();

这是一个简单的示例,演示了同时使用线程和计时器的动画。绿色条在黑色面板上循环移动。

Simple Animation

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

class SwingAnimation implements Runnable{
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new SwingAnimation());
    }

    JToggleButton play;
    AnimationPanel animation;

    @Override
    public void run() {
        JFrame frame = new JFrame("Simple Animation");
        JPanel content = new JPanel(new BorderLayout());

        play = new JToggleButton("Play");
        content.add(play, BorderLayout.NORTH);

        animation = new AnimationPanel(500, 50);
        content.add(animation, BorderLayout.CENTER);

        // 'true' to use a Thread
        // 'false' to use a Timer
        if (false) {
            play.addActionListener(new ThreadAnimator());
        } else {
            play.addActionListener(new TimerAnimator());
        }

        frame.setContentPane(content);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    abstract class Animator implements ActionListener {
        final int period = ( 1000 / 60 );

        @Override
        public void actionPerformed(ActionEvent ae) {
            if (play.isSelected()) {
                start();
            } else {
                stop();
            }
        }

        abstract void start();
        abstract void stop();

        void animate() {
            int workingPos = animation.barPosition;

            ++workingPos;

            if (workingPos >= animation.getWidth()) {
                workingPos = 0;
            }

            animation.barPosition = workingPos;

            animation.repaint();
        }
    }

    class ThreadAnimator extends Animator {
        volatile boolean isRunning;

        Runnable loop = new Runnable() {
            @Override
            public void run() {
                try {
                    while (isRunning) {
                        animate();
                        Thread.sleep(period);
                    }
                } catch (InterruptedException e) {
                    throw new AssertionError(e);
                }
            }
        };

        @Override
        void start() {
            isRunning = true;

            new Thread(loop).start();
        }

        @Override
        void stop() {
            isRunning = false;
        }
    }

    class TimerAnimator extends Animator {
        Timer timer = new Timer(period, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                animate();
            }
        });

        @Override
        void start() {
            timer.start();
        }

        @Override
        void stop() {
            timer.stop();
        }
    }

    static class AnimationPanel extends JPanel {
        final int barWidth = 10;

        volatile int barPosition;

        AnimationPanel(int width, int height) {
            setPreferredSize(new Dimension(width, height));
            setBackground(Color.BLACK);

            barPosition = ( width / 2 ) - ( barWidth / 2 );
        }

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

            int width = getWidth();
            int height = getHeight();

            int currentPos = barPosition;

            g.setColor(Color.GREEN);
            g.fillRect(currentPos, 0, barWidth, height);

            if ( (currentPos + barWidth) >= width ) {
                g.fillRect(currentPos - width, 0, barWidth, height);
            }
        }
    }
}

关于Java 游戏循环(绘画)卡住了我的窗口,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29745778/

相关文章:

java - 如何提前加载Swing和AWT组件?

java - 如何使用 Mockito 验证一个方法在另一个方法中被调用

java - JRE 1.7u45 无法加载包含一些未签名条目的小程序

java - 请求调度程序转发后如何获取原始页面 url/uri

java - 为什么要在 Java 中的方法参数上使用关键字 "final"?

java - 增加 JPanel 的高度及其内容

java - 为什么我会收到响应代码 : Non HTTP response code: java.net.SocketException?

java - JButton 的通用属性

java - JDialog 中的 JTextField 在处理后保留值

java - 更改 JComboBox 中选择的颜色(选择后)