KeyListener + ActionListener nie reaguje

0

Aby trochę nauczyć się Javy postanowiłem napisać prostą grę i natrafiłem na pewien problem.
Kiedy w klasie menu wcisnę przycisk nowa gra, w grze nie mogę się poruszać,a nawet nie mogę zamknąć okna.
Żaden listener nie reaguje.
Co mam zrobić aby po rozpoczęciu gry można było się poruszać. Poniżej część kodu aplikacji:

public class Menu extends Canvas implements ActionListener {
	private static final int W = Stage.W;
	private static final int H = Stage.H;
	private JPanel m;
	private JButton bNewGame;
	private JButton bLevelEdit;
	private JButton bExit;
	private Tank tank;
	

	public Menu(Tank tank) {
		
		this.tank = tank;

		m = (JPanel) tank.okno.getContentPane();
		m.setPreferredSize(new Dimension(W, H));
		m.setLayout(null);
	
		bNewGame = new JButton("Nowa Gra");
		bLevelEdit = new JButton("Edytor Poziomów");
		JButton bScore = new JButton("201");
		bExit = new JButton("Wyjście");
		
		Font font = new Font("Comic sans ms", Font.BOLD, 16);
		bNewGame.setFont(font);
		bNewGame.setForeground(Color.RED);
		
		bNewGame.setBounds(W/H, 200, 150, 100);
		bLevelEdit.setBounds(200, 400, 150, 100);
		bExit.setBounds(200, 500, 150, 100);

		m.add(bNewGame);
		m.add(bLevelEdit);
		m.add(bExit);
		
		bLevelEdit.addActionListener(this);
		bExit.addActionListener(this);

	}

	@Override
	public void actionPerformed(ActionEvent e) {
		Object o = e.getSource();
		if(o == bNewGame){
			bNewGame.removeAll();
			bLevelEdit.removeAll();
			bExit.removeAll();
			m.removeAll();
			Game game = new Game(tank);
			
		}
		else if(o == bLevelEdit){
			System.out.println("W trakcie prac");
			
		}
		else if(o == bExit){
			System.exit(0);
			
		}
		
	}
	
		
		
		

	

}



package Core;

import javax.swing.JFrame;

import Game.Stage;
import Menu.Menu;

import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
 
public class Tank {
 
    public static final int W = Stage.W;
    public static final int H = Stage.H;
    public JFrame okno;
 
    public Tank(){
        okno = new JFrame(".:Tank:.");
        okno.setBounds(0, 0, W, H);
        okno.setVisible(true);
        okno.setLayout(null);
        okno.addWindowListener( new WindowAdapter() {
            public void windowClosing(WindowEvent e){
                System.exit(0);
            }
        });
        okno.setResizable(false);
    }
 
    public static void main(String[] args){
        Tank tank = new Tank();
        Menu menu = new Menu(tank);
 
    }
 
}



public Game(Tank tank) {
		spriteCache = new SpriteCache();
		soundCache = new SoundCache();
		JPanel panel = (JPanel) tank.okno.getContentPane();
		setBounds(0, 0, Stage.W, Stage.H);
		panel.setPreferredSize(new Dimension(Stage.W, Stage.H));
		panel.setLayout(null);
		panel.add(this);
		panel.setVisible(true);


		createBufferStrategy(2);
		strategia = getBufferStrategy();
		requestFocus();
		addKeyListener(this);
		game();
	}
(...)
	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
			System.exit(0);
		player.keyPressed(e);
	}

	public void keyReleased(KeyEvent e) {
		player.keyReleased(e);
	}

	public void keyTyped(KeyEvent e) {
	}
0

Nie podałeś kodu metody game która jest tu kluczowa. Zgaduje że twój problem polega na tym że twoja metoda game to jest jakieś while(true) i zawiesza jeden, jedyny wątek w tym całym programie. Odpal ją w innym wątku.

0

Metoda game z dodanym wątkiem. Teraz po naciśnięciu "nowa gra" nic się nie dzieje.

	public void game() {
		new Thread(
				new Runnable() {
				public void run() {

	
		usedTime = 1000;
		initWorld();
		while (isVisible() && gameEnded == false) {
			long startTime = System.currentTimeMillis();
			updateWorld();
			checkCollisions();
			paintWorld();
			usedTime = System.currentTimeMillis() - startTime;
			do {
				Thread.yield();
			} while (System.currentTimeMillis() - startTime < 30);
		}
		if (czyWygrana == false) {
			paintGameOver();
		} else {
			paintWin();
		}
				}
				}
				).start();
			
		
	}
0

o_O wygrywasz plebiscyt tygodnia na nieczytelny kod...

            usedTime = System.currentTimeMillis() - startTime;
            do {
                Thread.yield();
            } while (System.currentTimeMillis() - startTime < 30);

Co to wg ciebie ma niby robić? I po co to tam jest?
Poza tym napisałeś to teraz tragicznie, wszystko upchnięte w jednej klasie, wszystko powiązane na jana. Aż dziw bierze że sie skompilowało. Wracając do tematu: debuger w dłoń i szukaj gdzie ci wisi coś. Ja obstawiam tą dziką pętlę...

0
usedTime = System.currentTimeMillis() - startTime;

To jest pozostałość po inaczej działającej pentli :)

Błąd nie tkwił w "dzikiej pętli", przypadkiem cofnąłem akcje i usuną się jeden listener. Na razie wszystko jest ok. Co w tej pętli jest dzikiego?

0

Bo generalnie zabawa w ręczne yieldowanie wątków jest dość ryzykowna, tak samo jak opieranie się na takim odliczaniu czasu. Szczególnie że przecież w tej aplikacji masz tylko ten wątek oraz wątek GUI więc zupełnie nie rozumiem po co miałbyś tak kombinować ;]

0

W poradniku tak każą.

Nasza gra W dużym stopniu zależy od szybkości komputera. Poprzez stałą ilość odespanego czasu czasem gra będzie działać bardzo szybko, czasem natomiast bardzo wolno. Dobrze byłoby, gdyby gra dostosowywała się do tego. Szybkość łatwo dostosować obserwując FPS-y. Znów przyda nam się wątek. Zmieńmy metodę game() :

1

To ten poradnik chyba już wiekowy jest bo już gdzieś w okolicy książek do animacji w Javie 6 odpalanie Thread.yield() może nie dawać kompletnie żadnych rezultatów (bywa operacją kompletnie pustą). Poza tym używanie System.currentTimeMillis() do obsługi pętli w grach jest kompletnym nieporozumieniem bo jest to nieodporne na przykład na zmiany (w tym korekcje) zegara systemowego takie jak aktualizacja daty lub czasu. W tym celu powstała metoda System.nanotime(), która nie ma tej wady i używa najwyższej możliwej dostępnej dla JVM rozdzielczości zegara systemowego.

Jeżeli chcesz reagować na spowolnienia systemu, lagi sieciowe itp. oraz odseparować szybkość aktualizacji gry (w tym częstotliwość otrzymywania danych do sterowania) od szybkości jej wyświetlania, to wygooglaj sobie active rendering. Nawet stare informacje, które sobie leżą w necie są wciąż przydatne bo pokazują mechanizm i jego przyczynę.
Materiały o grach, które wciąż proponują ręczne sterowanie za pomocą wątków raczej omijaj i traktuj jako historyczne. Dobrze zrobiona gra nie różni się wiele od dobrze zrobionego programu użytkowego, nawet jeżeli używa wysokopoziomowych mechanizmów zadań i wykonawców. Obecnie nie ma żadnego uzasadnienia używanie gołych i ręcznie przerabianych obiektów Thread w programowaniu gier.

W kodzie który pokazałeś metoda paintWorld powinna się odpalać warunkowo - zależnie od tego czy pozostało dla niej wystarczająco dużo czasu. Ten wystarczający czas oblicza się na podstawie wcześniejszych jej wykonań z korekcją trendu. Ewentualnie zawartość tej metody powinna warunkowo eliminować wykonanie opcjonalnych, ale czasochłonnych elementów.
Podobnie updateWorld() powinna być rozbita na dwie części z której jedna aktualizuje wejście (które raczej się nie opóźni), a druga aktualizuje rzeczy bardziej niepewne w czasie i dostępności takie jak dane z serwerów lub innych komputerów. Nieudane lub opóźnione rzeczy nie mogą wpływać negatywnie na inne. Na przykład lag z sieci nie może zatrzymywać bieżącego renderowania grafiki, a chwilowy brak zasobu czasu CPU nie może wpływać na zatrzymanie aktualizacji danych sterowania i gry, nawet jeżeli nie uda się zdążyć tego wyświetlić w tej samej klatce lub okienku czasowym.

1 użytkowników online, w tym zalogowanych: 0, gości: 1