Wątki w Grze opartej o AWT/Swing.

0

Cześć.
Ostatnio postanowiłem w ramach relaksu napisać sobie grę.
Prostego Ponga w Javie.
I wszystko fajnie pięknie działa tylko mam małą zagadkę.
Otóż, jakiego mechanizmu powinienem użyć, jeśli mam taką sytuację:

Buduję sobie okienko, rozszerzam JFrame, przeładowuje w nim metodę paint - w tej metodzie wyrysowuję wszystkie elementy.
Jednocześnie mam wątek implementujący Runnable. W nim mam metodę run, która wykonuje się w kółko - przelicza pozycję elementów, wywołuje Thread.sleep() i wywołuje metodę repaint() mojego okna, a ta odrysowuje zmiany na ekranie.
Do okna dodaję dwa listenery - rozszerzam KeyAdapter. Wiadomo po naciśnięciu strzałki w górę, paletka ma przemieścić się w górę, po naciśnięciu w dół ma przemieścić się w dół. Gra na raziejest dwuosobowa, więc drugą paletką sterują klawisze A i Z.
I teraz pytanie: zdarza się w trakcie gry, że któraś z paletek "się zawiesi" czyli naciskam A a Paletka stoi w miejscu.
Podejrzewam, że coś namieszałem z wątkiem.

Jeśli ktoś pisłą ponga, czy coś podobnego, to proszę o radę, czy nie powinienem użyć np. SwingWorkera.
Podejrzewam też sam listener, może przyciskając klawisz A przepełniam bufora klawiatury i dlatego zamiera?
Jeśli tak, to jak się przed tym bronić?
Dodam, że przeciążyłem metody keyPressed i keyReleased.

Proszę o rady.
Pozdrawiam.

0

Co do problemu nie znam odpowiedzi, ale ten wątek co jakiś czas odrysowujący to ja bym zrobił nie na pętli i sleepie tylko na obiekcie Timer ;)

0

Właśnie problem polega na tym, że javax.swing.Timer odświeża co delay wyrażony w sekundach. Co powoduje, że ruch odbywa się w ślimaczym tempie.

0
Black007 napisał(a)

Właśnie problem polega na tym, że java.util.Timer odświeża co delay wyrażony w sekundach.

Co, prostytutka?

http://download.oracle.com/javase/6/docs/api/java/util/Timer.html#scheduleAtFixedRate%28java.util.TimerTask,%20long,%20long%29

delay - delay in milliseconds before task is to be executed.
period - time in milliseconds between successive task executions.

0

Dokumentacja twierdzi, że opóźnienie jest w milisekundach. Podobnie jak javax.swing.Timer.
Edit: Za późno.

0

Sorry mój błąd.

Nie wiem dlaczego ale wcześniej przy użyciu new javax.swing.Timer(5, gameThread) łaził, jak popier....
Teraz doczytałem dokumentację i wyszło, że to są milisekundy. Zmieniłem i zasuwa równo.
Pewnie jakiś czeski błąd.

0

Witam.
Niestety po kilku godzinach odpalam program a tam kicha.
new Timer(3, gameThread) i działa strasznie wolno...
Naprawdę, powinien zasuwać szybko a wszystko się ślimaczy.
Ktoś ma jakiś pomysł?

0
Black007 napisał(a)

new Timer(3, gameThread)
Nie maczegoś takiego...

0

Skąd masz tę pewność, że to "klawiatura się tnie"? Co ma wspólnego ActionListener z Thread?
Swing aktualizuje ekran asynchronicznie w odpowiedzi na zdarzenia (na przykład odkrycie okna z grą przez inne okno) lub wymuszenie repaint(). Potrzeby gry wymuszają odrysowanie albo co jakiś sztywny odcinek czasu, albo jako skutek koniecznej aktualizacji ekranu gry (np. piłka się poruszyła). To musisz zrobić. Natomiast jedyne co powinien robić listener, to aktualizować położenie paletek (bez jakiegokolwiek rysowania!) i natychmiast się stamtąd wynosić (wracać do Swinga - czyli zakończyć procedurę).
To w Twojej pętli wymuszającej odświeżanie ekranu najprawdopodobniej jest coś nie w porządku. Bardzo ważne jest aby repaint wywoływać za pomocą asynchronicznego invokeLater() jeżeli masz to wywołanie w osobnym wątku. I nie za szybko bo wtedy pętla Swinga łączy zdarzenia jeżeli się nie wyrobi. Jeżeli wywoła się bezpośrednio, to może się zdarzyć cokolwiek - łącznie z efektami jakie masz. Repaint co 33 ms powinien być zupełnie wystarczający. Natomiast zdarzenia z klawiatury mogą nadchodzić częściej (trzeba też uwzględniać jednoczesne naciśnięcie przycisków góra i dół - na dodatek po obu stronach). Pamiętaj, że część klawiatur nie potrafi wysłać zdarzenia o więcej jak 3 klawiszach jednocześnie. Jeżeli gracz po jednej stronie naciśnie górę i dół, a ten po drugiej zrobi to samo, to ostatnie naciśnięcie nie zostanie wygenerowane jako zdarzenie (może się pojawić dźwięk ostrzeżenia wygenerowany przez klawiaturę).

0

Cześć. Dzięki za konkretną odpowiedź.

Też tak mam zrobione.

Głowna pętla gry uruchomiona w wątku Runnable:

		while(inGame){
			ball.move();
			blue.move();
			red.move();
			checkCollisions();
			try {
				Thread.currentThread().sleep(5);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			window.repaint();
		} 

Natomiast są jeszcze dwa listenery dodane do głównego okna, słuchające klawiatury.:

 public PongListener(Pong pong, int top, int bottom) {
		this.pong = pong;
		this.top = top;
		this.bottom = bottom;
	}

	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == pong.getUpKey()) {
			if (pong.getY() > top) {
				pong.setDy(-1);
			}
		}
		else if (e.getKeyCode() == pong.getDownKey()) {
			if (pong.getBounds().height + pong.getY() < bottom) {
				pong.setDy(1);
			}
		}
		e.consume();
	};

BTW: Włąsnie się kapłem, że może ten repaint() powinien być przed thread.sleep.
Co ty na to?

0

Jak widzę jest to klasyczne rozwiązanie pętli gry, w której UPS == FPS, przy czym liczba ta jest nie większa niż 200. W przypadku gdyby system dostał nagle zadyszki, to spowalnia (lub zatrzymuje się) zarówno renderowanie (repaint) jak i aktualizacja danych gry (to co przed try). Łatwo to sobie sprawdzić odpalając jakiś stress test oraz grę (można też odpalać na jakimś antycznym złomie w maksymalnej rozdzielczości).
Niestety jest to jedno ze słabszych rozwiązań dla gier (nawet tak prostych).

  1. Co do repaint, to na pewno nie zaszkodzi przesunąć przed sleep. Z tym, że jak napisałem wywołanie repaint powinno iść przez invokeLater() bo odpalasz to z zupełnie innego wątku. Co prawda AWT jest w przeciwieństwie do Swinga rzekomo wielowątkowe, ale ja wolę się ubezpieczać (narzut bardzo niewielki).
  2. Metoda setDy() powinna być synchronizowana (albo posiadać ręcznego mutexa).
  3. W keyPressed() powinna być sprawdzana flaga, czy jeszcze wciąż trwa obsługa (poprzedniego) zdarzenia, a następnie ustawiana, że trwa obsługa zdarzenia (na koniec procedury zdejmowana). Zabezpieczy to przed zbyt częstymi jak na bieżące możliwości przetwarzania komputera (a te mogą przecież spaść do zera) próbami obsługi zdarzeń klawiatury. Jest to o tyle istotne, że część klawiatur przy naciśniętym i przytrzymanym klawiszu sama generuje powtarzające się zdarzenia naciśnięcia klawisza.
    Przy okazji pojawienie się nowego zdarzenia gdy trwa jeszcze poprzednie powinno być alarmowane (np. w jakimś logu - najprościej println). To jest bardzo często skorelowane z obserwowaniem "cięcia się".
  4. Nie widzę obsługi keyReleased. Powinno być w niej wywołanie setDy(0) i również sprawdzanie flagi trwającej obsługi zdarzenia.
  5. W obsłudze klawiszy nie powinieneś sprawdzać warunków poprawności ustawiania kierunków ruchu paletki. Od tego jest zmienna pong i jej klasa (i red.move, blue.move). Obsługa eventów powinna być tak prosta, a wręcz prymitywna jak to możliwe. Przede wszystkim z powodu możliwych deadlocków lub konieczności synchronizacji (odczekania) na każdej odczytywanej danej spoza danych czystego eventu (u Ciebie pong.getBounds() i pong.getY()). A oczekiwanie, to nic innego jak "cięcie się".
0

Dzięki za sugestię, jak tylko będę miał chwilkę, to na pewno je zastosuje.
Release Key wygląda analogicznie, czyli też sprawdzanie warunków i setDy(0).

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