Цикл игры Java (рисование) замораживает мое окно

Я меняю "представления" с помощью 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 ответа

Решение

Что делает обновление? Вы, вероятно, не должны звонить game.loop() на EDT. Вы выполняете цикл в EDT, ваш перерисовка никогда не будет вызываться, так как перерисовка ставит в очередь событие в EDT, и это кажется довольно занятым. Попробуй переехать game.loop() в другой поток

new Thread(new Runnable() {
    @override
    public void run() {
        game.loop();
    }
}).start();

Таким образом, вы не будете блокировать EDT, в то время как перерисовка все еще должна выполняться на EDT.

Предшественник: поток рассылки событий (EDT).

Качели однопоточные. Что это значит?

Вся обработка в программе 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...
    }
}

Поток диспетчеризации скрыт от нас через абстракцию: мы обычно пишем только обратные вызовы слушателя.

  • Нажатие кнопки публикует событие ( в собственном коде): когда событие обрабатывается, actionPerformed называется на EDT.
  • призвание repaint отправляет событие: когда событие обрабатывается, paintComponent называется на EDT.
  • призвание invokeLater отправляет событие: когда событие обрабатывается, run называется на EDT.
  • Все в Swing начинается с события.

Задачи события обрабатываются в последовательности, в порядке их размещения.

Следующее событие может быть обработано только тогда, когда текущая задача события возвращается. Вот почему у нас не может быть бесконечного цикла на EDT. actionPerformed (или же run, как в вашем редактировании) никогда не возвращается, поэтому вызовы repaint публикуйте события рисования, но они никогда не обрабатываются, и программа, кажется, зависает.

Вот что значит "блокировать" EDT.


Есть два основных способа сделать анимацию в программе Swing:

  • Использовать Thread (или SwingWorker).

    Преимущество использования потока заключается в том, что обработка выполняется вне EDT, поэтому при интенсивной обработке графический интерфейс пользователя может обновляться одновременно.

  • Использовать javax.swing.Timer,

    Преимущество использования таймера заключается в том, что обработка выполняется в EDT, поэтому нет необходимости беспокоиться о синхронизации и безопасно изменять состояние компонентов графического интерфейса.

Вообще говоря, мы должны использовать поток в программе Swing только в том случае, если есть причина не использовать таймер.

Для пользователя между ними нет заметной разницы.

Ваш звонок в revalidate указывает на то, что вы изменяете состояние компонентов в цикле (добавление, удаление, изменение местоположения и т. д.). Это не обязательно безопасно делать от EDT. Если вы изменяете состояние компонентов, это веская причина использовать таймер, а не поток. Использование потока без надлежащей синхронизации может привести к незначительным ошибкам, которые трудно диагностировать. См. Ошибки согласованности памяти.

В некоторых случаях операции над компонентом выполняются под блокировкой дерева (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();

Вот простой пример, демонстрирующий анимацию как с потоком, так и с таймером:

Простая анимация

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);
            }
        }
    }
}

Переместите вызов метода game.loop() в нечто вроде:

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

Благодарю.

Другие вопросы по тегам