Приостановка таймера

Я ударился о другую стену. После того, как мой ключевой ввод заработал, я часами ломал себе голову, я хочу создать функцию паузы, чтобы при повторном нажатии той же клавиши задача времени перестала работать (т.е. игра приостановлена)

JPanel component = (JPanel)frame.getContentPane();
    component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
    component.getActionMap().put("space", (new AbstractAction(){
        public void actionPerformed(ActionEvent e){

            Timer timer = new Timer();

            timer.scheduleAtFixedRate(new TimerTask(){
            public void run(){
                    grid.stepGame();
                }
            },250, 250);



        }}));


        }

Проблема в том, что я не могу использовать глобальную логическую переменную isRunning и переключать ее каждый раз, когда нажимается клавиша, потому что метод timerTask во вложенном классе (поэтому логическое isRunning должно быть объявлено окончательным для доступа ...). Любые идеи о том, как определить, нажата ли клавиша еще раз или игра уже запущена, чтобы я мог приостановить / отменить свой timerTask.

Большое спасибо Сэм


person Sam Palmer    schedule 22.10.2011    source источник
comment
Смотрите обновление с кодом. Снова используйте для этого таймер Swing, а не java.util.Timer.   -  person Hovercraft Full Of Eels    schedule 22.10.2011


Ответы (5)


Поскольку это игра Swing, вам следует использовать javax.swing.Timer или Swing Timer, а не java.util.Timer. Используя таймер Swing, вы гарантируете, что периодически вызываемый код вызывается в EDT, что является ключевой проблемой для приложений Swing, и у него также есть метод остановки, который приостанавливает таймер. Вы также можете предоставить своему анонимному классу AbstractAction частное логическое поле, чтобы проверять, нажимается ли клавиша в первый раз или нет.

Кроме того, похвалы и 1+ за использование привязок клавиш вместо KeyListener.

e.g.,

  JPanel component = (JPanel) frame.getContentPane();
  component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "space");
  component.getActionMap().put("space", (new AbstractAction() {
     private boolean firstPress = true;
     private int timerDelay = 250;
     private javax.swing.Timer keyTimer = new javax.swing.Timer(timerDelay , new ActionListener() {

        // Swing Timer's actionPerformed
        public void actionPerformed(ActionEvent e) {
           grid.stepGame();
        }
     });

     // key binding AbstractAction's actionPerformed
     public void actionPerformed(ActionEvent e) {
        if (firstPress) {
           keyTimer.start();
        } else {
           keyTimer.stop();
        }

        firstPress = !firstPress;
     }
  }));

Другой полезный вариант - выполнить повторяющуюся задачу при нажатии клавиши и остановить ее при отпускании клавиши, и это можно легко сделать, получив нажатия клавиш при нажатии и отпускании:

KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true) // for key release
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false) // for key press

Например:

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

public class SwingTimerEg2 {
   private JFrame frame;
   private Grid2 grid = new Grid2(this);
   private JTextArea textarea = new JTextArea(20, 20);
   private int stepCount = 0;

   public SwingTimerEg2() {
      frame = new JFrame();

      textarea.setEditable(false);
      frame.add(new JScrollPane(textarea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));

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

   void setUpKeyBinding() {
      final int timerDelay = 250;
      final Timer keyTimer = new Timer(timerDelay, new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent e) {
            grid.stepGame();
         }
      });
      JPanel component = (JPanel) frame.getContentPane();
      final int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
      final String spaceDown = "space down";
      final String spaceUp = "space up";
      component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), spaceDown);
      component.getActionMap().put(spaceDown, (new AbstractAction() {
         public void actionPerformed(ActionEvent e) {
            keyTimer.start();
         }
      }));
      component.getInputMap(condition).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), spaceUp);
      component.getActionMap().put(spaceUp, (new AbstractAction() {
         public void actionPerformed(ActionEvent e) {
            keyTimer.stop();
         }
      }));

   }

   public void doSomething() {
      textarea.append(String.format("Zap %d!!!%n", stepCount));
      stepCount ++;
   }

   private static void createAndShowGui() {
      new SwingTimerEg2();
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }

}

class Grid2 {
   private SwingTimerEg2 stEg;

   public Grid2(SwingTimerEg2 stEg) {
      this.stEg = stEg;
   }

   void stepGame() {
      stEg.doSomething();
   }
}
person Hovercraft Full Of Eels    schedule 22.10.2011

Самое простое и грязное решение:

final boolean[] isRunning = new boolean[1];

Вы не хотите этого делать, но это работает при условии надлежащей синхронизации.

Что было бы лучше

final AtomicBoolean isRunning = new AtomicBoolean();

Еще лучше было бы еще раз пересмотреть дизайн: глобальное состояние обычно означает «глобальные проблемы».

person alf    schedule 22.10.2011
comment
видеть обновления :) Если бы я писал игру, я бы выделил все состояние игры в отдельный объект, Façade, если хотите, и запросил текущее состояние. Но если вам только нужен флаг - см. Выше :) - person alf; 22.10.2011

Последнего требования к квалификатору можно легко избежать - замените свой внутренний метод (который имеет требование final) вызовом метода класса.

person mah    schedule 22.10.2011

Нет, вы неправильно поняли, ПОЧЕМУ вам нужен final для анонимных занятий! Final требуется только для локальных переменных (точнее, для любой переменной, время жизни которой может быть меньше, чем у данного объекта).

Следовательно, статическая переменная в классе прекрасна и будет отлично работать!

Изменить: пример:

public class Main {
    interface Test {
        void call();
    }

    public static volatile boolean running = true;

    public static void main(String[] args) {
        Test t = new Test() {
            @Override
            public void call() {
                System.out.println(Main.running);
            }
        };
        t.call();
        running = false;
        t.call();
    }
}
person Voo    schedule 22.10.2011
comment
Нет необходимости в статике или нестабильности. Все это делается в одном потоке, EDT, поэтому volatile снова не требуется. Также любое логическое поле может быть частным полем экземпляра его анонимного внутреннего класса, поэтому нет необходимости в статике. - person Hovercraft Full Of Eels; 22.10.2011
comment
@HovercraftFullOfEels Отказ от использования static сделает пример излишне сложным без какой-либо выгоды. И volatile, очевидно, необходим, если он использует java.util.Timer (или позже хочет остановить действие игровой логики, что, по моему опыту, необходимо рано или поздно) .. Также вся идея примера состоит в том, чтобы показать, что вам не нужно последнее ограничение, если компилятор может гарантировать, что время жизни переменной не меньше, чем у класса - именно это я написал в предыдущем абзаце: p - person Voo; 22.10.2011
comment
Ему не следует использовать java.util.Timer, поскольку это приложение Swing. Пожалуйста, посмотрите мой пример, чтобы узнать, как это сделать просто без использования статики. - person Hovercraft Full Of Eels; 22.10.2011

Сохраните ссылку на таймер где-нибудь, скажем, в своем игровом классе. Когда игра приостановлена, отключите таймер. Это отменит все запланированные на данный момент задачи. Затем, когда игра будет возобновлена, снова запланируйте таймер, как вы делали выше.

public class Game {

    private Timer timer;

    public void pause() {
        if (timer != null) {
            timer.pause();
        }
    }

    public void startOrResumeGame() {
        if (timer == null) {
            timer = new Timer();
        } else {
            // Just in case the game was already running.
            timer.cancel();
        }
        timer.scheduleAtFixedRate(new TimerTask() {
            public void run() {
                    grid.stepGame();
                }
            }, 250, 250);

    }
}
person Brian Coleman    schedule 22.10.2011