Java » Java ME

GameCanvas - czyli dalej rysujemy po ekranie

Kontynuacja artykułu J2ME - Rysujemy po ekranie



Omówiona we wspomnianym artykule klasa Canvas wystarczy w zupełności do prostych zastosowań. Jednak przy pisaniu gier na nowoczesne telefony może stać się kulą u nogi. Przede wszystkim, jest ona za wolna. Dlatego przy częstym wywoływaniu metody repaint(), gdy jej wykonanie trwa długo, można zauważyć irytujące migotanie ekranu. Zresztą spójrz na taki kod:

public void paint(Graphics g)
{
 g.setColor (0,0,0);
 g.fillRect (0,0,getWidth(), getHeight());
 
 g.setColor (255,255,0);
 for (int i=0; i<100; i++) 
  for (int j=0; j<100; j++) g.drawLine (i,j,i,j);
}

Częste wywoływanie tego rysowania spowoduje, że kod nie zdąży się wykonać w całości przed kolejnym wyrysowaniem ekranu (pamiętamy, że metoda repaint() przerzuca rysowanie do tła, jako osobny wątek). W naszym przykładzie, program narysuje kwadrat o wymiarach 100x100 piksel po pikselu, zatem wykona 10 000 operacji graficznych. Jeżeli uruchomisz ten kod na telefonie wywołując go cyklicznie za pomocą Timera lub choćby z klawiatury, zauważysz zapewne, że kwadrat nie zdąży narysować się całkowicie.

Jednym z rozwiązań jest zbudowanie tzw. muteksu. Mutex w dużym uproszczeniu możemy zdefiniować jako niepodzielną część kodu. W Javie wystarczy dodać słowo kluczowe synchronized w definicji metody:
public synchronized void paint(Graphics g)

Kod taki co prawda zlikwiduje irytujące miganie, ale... spowoduje niezwykłą ociężałość kodu. Na emulatorze będzie się wręcz zawieszać.

Innym sposobem jest utworzyć obraz w pamięci, i dopiero w gotowej postaci wysłać go do bufora ekranu. Metoda ta nosi nazwę podwójnego buforowania. W naszym przypadku najprościej byłoby utworzyć tymczasowy obiekt klasy Graphics i to na nim wykonać operacje graficzne, a na końcu wszystko hurtem wyświetlić za pomocą drawImage().

Jest coś, co zrobi to za nas, a tym czymś jest klasa GameCanvas.

Klasa GameCanvas


Klasa GameCanvas jest klasą dziedziczącą bezpośrednio po Canvas. Jej definicja zawarta jest w pakiecie javax.microedition.lcdui.game. Wystarczy dodać go zatem do naszego programu:
import javax.microedition.lcdui.game.GameCanvas;

albo
import javax.microedition.lcdui.game.*;

Niestety, cały ten pakiet dostępny jest wyłącznie w MIDP 2.0, dlatego (niestety) zapomnij o pisaniu gier z GameCanvas pod SonyEricssona T610 czy któregoś z jego klonów. Niemniej jednak większość osób posiada nowe telefony, obsługujące MIDP 2.0, dlatego nie stanowi to dużego problemu. Jeżeli jednak twój telefon nie obsługuje MIDP 2.0, musisz zadowolić się emulatorem.

Dlatego zawczasu zmień ustawienia swojego projektu na MIDP 2.0 (profil Custom albo JTWI, MIDP 2.0, CLDC obojętnie jaki, rozszerzenia też nie mają znaczenia).

Klasa GameCanvas, w odróżnieniu od zwykłej Canvas, nie wymaga definiowania abstrakcyjnej metody paint(Graphics). Wymaga jednak wywołania swojego konstruktora za pomocą słowa kluczowego super:
 super (true);

albo
 super (false);

Parametr przekazywany przez konstruktor jest określany w manualu jako suppressKeyEvents. Określa on sposób obsługi klawiatury. Gdy jest on równy true, podstawiane są domyślne wartości dostępne poprzez stałe (ściślej: zmienne statyczne finalne) {DOWN|UP|LEFT|RIGHT|...}_PRESSED. Jeżeli chcesz mieć dostęp do klawiatury "po staremu", musisz przekazać wartość false.

Tak jak poprzednio, możesz wywołać setFullScreenMode klasy bazowej w celu ustawienia pełnego ekranu.

Operacje rysujące umieszczasz w dowolnej metodzie, byle o konstrukcji takiej, jak ta:
public void myPaintMethod()
{
 Graphics g = getGraphics();
 // Tu operacje rysowania
 flushGraphics();
}

Wywołanie metody flushGraphics() powoduje przeniesienie obrazu na ekran.
Niestety, nasza metoda nie wywoła się sama przy starcie, no bo niby skąd kompilator ma to wiedzieć, zwłaszcza, że możemy takich metod naprodukować ile chcemy. Musimy ją zatem jawnie wywołać. W naszym przypadku idealnym miejscem będzie metoda startApp() głównej klasy MIDletu:
public void startApp()
{
 mc.myPaintMethod();
}

W takim przypadku oczywiście deklaracja obiektu mc musi znaleźć się poza konstruktorem - w dowolnym miejscu klasy, byle nie w środku jakiejś metody:
 private MyCanvas mc;

a w konstruktorze wyrzucamy słowo MyCanvas na początku instrukcji tworzenia obiektu:
mc = new MyCanvas();


Sprawdź teraz sam, jak działa nasz program, wypełnij go odpowiednią treścią. Jeszcze tylko klasa Timer i możesz ruszać z produkcją gier :-).