Java游戏循环(绘画)冻结了我的窗口

时间:2015-04-20 10:48:59

标签: java swing

我正在改变"观点" with cardLayout(此类有一个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时,一切正常,但现在我希望有更多的视图,所以我需要在其他课程中JFrame。)

感谢。

3 个答案:

答案 0 :(得分:3)

前体: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 发布绘制事件,但它们永远不会被处理并且程序似乎冻结了。

这就是&#34;阻止&#34;美国东部时间。

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

  • 使用Thread(或SwingWorker)。

    使用线程的好处是处理是在关闭 EDT完成的,所以如果进行密集处理,GUI仍然可以同时更新。

  • 使用javax.swing.Timer

    使用计时器的好处是处理在 on 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);
            }
        }
    }
}

答案 1 :(得分:2)

更新有什么作用?您可能不应该在EDT上致电var_dump($prices[105]); # array(1) { # [0]=> # int(7) # } 。你正在EDT上运行一个循环,你的重绘不会被调用,因为重绘在EDT上排队一个事件,它似乎很忙。尝试将game.loop()移动到另一个帖子

game.loop()

这样你就不会阻止EDT,而重绘仍然在EDT上执行。

答案 2 :(得分:1)

将game.loop()方法调用移动到类似:

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

感谢。