INSTRUKCJA DO ĆWICZENIA 13. Animacja wielowątkowa w aplikacjach JME. Gra logistyczna. I. UTWORZENIE SZKIELETU APLIKACJI 1. Uruchom środowisko programowania NetBeans. Utwórz aplikację typu JME zakładając projekt o nazwie JME_Gra 2. Dodaj do projektu klasę typu Midlet o nazwie Gra 3. Dodaj do projektu klasę Java o nazwie Plansza dziedziczącą z klasy Canvas umieszczając ją w osobnym pliku 4. Zaimportuj klasę Canvas oraz utwórz automatycznie metodę paint. Usuń domyślne wygenerowaną zawartość metody paint. 5. Zaimplementuj w klasie Plansza interfejs CommandListener oraz utwórz automatycznie metodę commandaction. Usuń z metody commandaction zbędne instrukcje. class Plansza extends Canvas implements CommandListener { 6. Zadeklaruj w klasie Plansza zmienne W, H typu całkowitego w celu przechowania wymiarów planszy, oraz generator liczb losowych Random o nazwie zamiennej r Random r = new Random(); int W, H; 7. Utwórz konstruktor klasy Plansza bez parametrów 8. W konstruktorze klasy Plansza zainicjuj zmienne W i H pobierając odpowiednio szerokość oraz wysokość planszy W = getwidth(); H = getheight(); 9. Zadeklaruj w klasie Plansza zmienną typu Command o nazwie start do uruchomienia gry Command start = new Command("Start", Command.OK, 0); 10. W konstruktorze dodaj obiekt start do menu ekranu za pomocą metody addcommand, oraz włącz nasłuchiwanie zdarzeń za pomocą metody setcommandlistener addcommand(start); setcommandlistener(this); 11. W metodzie paint klasy Plansza umieść instrukcje czyszczenia ekranu g.setcolor(0, 0, 0); g.fillrect(0, 0, W, H); 12. Otwórz plik z klasa Gra. Umieść pod nagłówkiem klasy deklarację zmiennej display typu Display, oraz zmiennej plansza typu Plansza Display display; Plansza plansza; 13. Wygeneruj pusty konstruktor klasy Gra i umieść w nim instrukcję tworzenia obiektu typu Plansza public Gra() { plansza = new Plansza();
14. W metodzie startapp klasy Gra zainicjuj zmienną display za pomocą metody Display.getDisplay oraz ustaw ekran plansza jako startowy display = Display.getDisplay(this); display.setcurrent(plansza); 15. Uruchom aplikację i sprawdź czy nie ma błędów. II. PROJEKTOWANIE ABSTRAKCYJNEJ KLASY FIGURA PRZECHOWUJĄCEJ WSPÓLNE CECHY OBIEKTÓW GRAFICZNYCH Klasa będzie stanowiła podstawę do rysowania obiektów graficznych na ekranie Planszy. Będzie ona przechowywała wspólne cechy obiektów takie jak położenie na ekranie, liczba uzbieranych punktów, czas animacji, a także odpowiadała za realizację wątków. 1. Utwórz w projekcie abstrakcyjną klasę o nazwie Figura umieszczając ją w osobnym pliku abstract class Figura { 2. Zadeklaruj w klasie Figura współrzędne X, Y, liczbę punktów punkty oraz interwał zmiany stanu obiektu podczas animacji delay public int X, Y, punkty; protected int delay; 3. Wygeneruj automatycznie konstruktor klasy Figura, za pomocą którego można będzie nadać początkowe wartości zmiennym X, Y public Figura(int X, int Y) { this.x = X; this.y = Y; 4. Zadeklaruj w klasie Figura abstrakcyjną metodę rysuj, która powinna zostać zdefiniowana w każdej klasie dziedziczącej z Figura, w celu rysowania pochodnych obiektów abstract void rysuj(graphics g); 5. Zadeklaruj w klasie Figura abstrakcyjną metodę zmienstan(), za pomocą której każda klasa pochodna będzie mogła zmieniać swoja pozycję, kolor, rozmiary etc w zależności od potrzeb animacji abstract void zmienstan(); 6. Zaimplementuj w klasie Figura interfejs Runnable. Pozwoli to na uruchomienie obiektów klas pochodnych w niezależnych równoległych wątkach bez konieczności ponownego zdefiniowania interfejsu Runnable. Zwróć uwagę, iż środowisko NetBeans nie proponuje automatycznego stworzenia metody run(). Zastanów się dlaczego tak się dzieje. 7. Utwórz metodę run() w klasie Figura. Wewnątrz metody umieść pętlę nieskończoną while(true). Następnie wewnątrz pętli wywołaj metodę zmienstan() oraz instrukcję oczekiwania Thread.sleep(delay). Dodaj obsługę wyjątków. Zwróć uwagę na to, że mimo iż zawartość metody zmienstan() nie jest jeszcze zdefiniowana, kompilator nie zgłasza błędu. Metoda zmienstan() zostanie zdefiniowana w każdej z pochodnych klas, w zależności od jej specyfiki. Wszystkie one przy tym będą korzystały z możliwości uruchomienia procesu (wątku). public void run() { while (true) { zmienstan(); try { Thread.sleep(delay);
catch (InterruptedException ex) { 8. Sprawdzamy czy aplikacja nadal się kompiluje. Jeśli nie, naprawiamy błędy. Nie uruchamiamy aplikacji ponieważ nie zostały wprowadzone żadne zmiany mające wpływ na jej wygląd. III. PROJEKTOWANIE KLASY PACMAN OBIEKTU RUCHOMEGO DZIEDZICZĄCEGO Z KLASY FIGURA W poprzednim rozdziale została stworzona klasa abstrakcyjna Figura, zawierająca uniwersalne rozwiązania wspólne dla wszystkich obiektów graficznych w programie. Część metod przy tym nie została zdefiniowana. W bieżącym rozdziale utworzymy klasę Pacman dziedziczącą z klasy Figura, i zrealizujemy brakujące elementy wyglądu oraz zachowania obiektu. 1. Dodaj do projektu nową klasę o nazwie Pacman w postaci osobnego pliku. Klasa powinna dziedziczyć z klasy Figura. 2. Wygeneruj automatycznie metody abstrakcyjne rysuj oraz zmienstan. Usuń ich domyślną zawartość. 3. Zadeklaruj zmienne klasy Pacman o nazwie dx, dy typu całkowitego. Będą one przechowywać kierunek ruchu obiektu (dx w poziomie, dy w pionie) int dx, dy; 4. Dodaj ręcznie konstruktor klasy Pacman o parametrach X, Y. Będzie on służył do ustawienia początkowych współrzędnych obiektu na ekranie, oraz czasu animacji. Na samym początku konstruktora wywołaj konstruktor klasy bazowej Figura za pomocą instrukcji super(x, Y). To pozwoli nie dublować w kodzie klasy Pacman instrukcje inicjowania zmiennych X, Y. public Pacman(int X, int Y) { super(x, Y); dx = 0; dy = 0; delay = 10; 5. W metodzie rysuj klasy Pacman umieść instrukcje do rysowania obiektu animowanego oraz liczby zgromadzonych przez niego punktów. Metoda fillarc rysuje wycinek koła ze środkiem (X,Y) oraz kącie 270 st. Liczba punktów jest wyświetlana za pomocą metody drawstring. Jej argumenty Graphics.HCENTER oraz Graphics.BOTTOM wzkazują metodę ulokowania tekstu względem współrzędnych (X,Y) g.setcolor(255, 255, 0); g.fillarc(x-15, Y-15, 30, 30, 45, 270); g.setcolor(255, 255, 255); g.drawstring(punkty + "", X, Y-15, Graphics.HCENTER Graphics.BOTTOM); 6. Dodaj w klasie Pacman metodę o nazwie kierunek(int dx, int dy) do ustawienia kierunków ruchu obiektu public void kierunek(int dx, int dy) { this.dx = dx; this.dy = dy; 7. W treści metody zmienstan() umieść instrukcje do zmiany współrzędnych (X,Y) obiektu o wartości przyrostowe dx, dy. X += dx; Y += dy;
8. Przejdź do klasy Plansza. Pod jej nagłówkiem umieść deklarację obiektu pacman klasy Pacman Pacman pacman; 9. W konstruktorze klasy Plansza umieść instrukcję tworzenia obiektu klasy Pacman w pozycji (30, 40) pacman = new Pacman(30, 40); 10. W metodzie paint klasy Plansza umieść instrukcję rysowania obiektu Pacman pacman.rysuj(g); 11. Uruchom program i sprawdź czy figurka się wyświetla na ekranie IV. ANIMACJA. OBSŁUGA KLAWIATURY W tym rozdziale zdefiniujemy metodę obsługi zdarzeń klawiatury w celu umożliwienia zmiany pozycji obiektu graficznego na ekranie. 1. Zadeklaruj w klasie Plansza metodę keypressed, która zostanie automatycznie wywołana przy każdym naciśnięciu klawisza na telefonie protected void keypressed(int keycode) { 2. W treści metody keypressed umieść instrukcje zmieniające kierunek ruchu pacmana zgodnie z kodem wciśniętego klawisza: -1 UP, -2 DOWN, -3 LEFT, -4 RIGHT, -5 CENTER if (keycode == -1) pacman.kierunek(0, -1); // w górę if (keycode == -2) pacman.kierunek(0, 1); // w dół if (keycode == -3) pacman.kierunek(-1, 0); // w lewo if (keycode == -4) pacman.kierunek(1, 0); // w prawo if (keycode == -5) pacman.kierunek(0, 0); // stop 3. W metodzie paint klasy Plansza umieść instrukcję zatrzymania wątku na czas 10 msek. Thread.sleep(10); 4. Wygeneruj automatycznie instrukcje try.. catch niezbędne do poprawnej obsługi metody Thread.sleep 5. Za nawiasem kończącym konstrukcję try catch umieść instrukcję repaint() w celu odświeżenia ekranu repaint(); 6. W metodzie commandaction klasy Plansza rozpoznaj polecenie start i umieść w odpowiadającym mu kodzie instrukcję tworzenia i uruchomiania wątku pacmana if (c == start) { new Thread(pacman).start(); 7. Sprawdź działanie programu. Po uruchomieniu aplikacji oraz kliknięciu [Start] można sterować ruchem obiektu po ekranie za pomocą strzałek na klawiaturze telefonu (oraz komputera)
V. DEFINIOWANIE PREZENTÓW W tym rozdziale stworzymy kolejną klasę obiektów graficznych Prezent. Umożliwi ona zdobywanie punktów przez pacmana w sytuacji, kiedy sterowany przez użytkownika obiekt zbliży się do celu na odpowiednią liczbę pikseli. 1. Dodaj do projektu nową klasę Prezent w osobnym pliku 2. Odziedzicz klasę Prezent z klasy Figura 3. Zaimplementuj wszystkie metody abstrakcyjne w klasie Prezent i usuń ich domyślnie wygenerowaną zawartość 4. Dodaj konstruktor klasy Prezent za pomocą którego można będzie ustawić współrzędne obiektu na ekranie oraz liczbę punktów za jego zdobycie public Prezent(int X, int Y, int punkty) { super(x, Y); this.delay = 1000; this.punkty = punkty; 5. W metodzie rysuj klasy Prezent umieść instrukcje rysowania obiektu w postaci kółka oraz instrukcję wyświetlającą liczbę punktów w postaci tekstu g.setcolor(255-5*punkty, 5*punkty, 0); g.fillarc(x-10, Y-10, 20, 20, 0, 360); g.setcolor(255, 255, 255); g.drawstring(punkty + "", X, Y+5, Graphics.HCENTER Graphics.BASELINE); 6. Metodę zmienstan na razie pozostaw pustą. 7. W klasie Plansza dodaj deklarację 10-elementowej tablicy obiektów typu Prezent Prezent[] prezenty = new Prezent[10]; 8. W konstruktorze klasy Plansza utwórz wszystkie obiekty Prezent w tablicy for (int i = 0; i < 10; i++) { prezenty[i] = new Prezent( r.nextint(w - 20), r.nextint(h - 20), r.nextint(25) + 25); 9. W treści metody paint klasy Plansza przed instrukcja rysowania Pacmana, dodaj instrukcje rysujące wszystkie obiekty typu Prezent z tablicy for (int i = 0; i < 10; i++) { if (prezenty[i] == null) continue; prezenty[i].rysuj(g); 10. Sprawdź działanie programu. Na ekranie powinny się ukazać 10 obiektów graficznych typu Prezent. VI. INTERAKCJA POMIĘDZY OBIEKTAMI NA EKRANIE. ZBIERANIE PREZENTÓW 1. Zadeklaruj w klasie Pacman metodę złap za pomocą której można będzie sprawdzić odległość pomiędzy wskazanym obiektem graficznym a pacmanem boolean złap(prezent p) { return Math.sqrt((X - p.x)*(x - p.x) + (Y - p.y) * (Y - p.y)) < 5; 2. W metodzie paint klasy Plansza po instrukcji rysowania obiektu prezenty[i].rysuj(g) umieść sprawdzenie czy pacman w danym momencie zbliżył się do tego obiektu na dystans nie przekraczający 5 pikseli (wartość przybliżona, można ją oczywiście zmienić). Jeśli tak, prezent zostanie usunięty z listy zaś użytkownikowi zostaną doliczone odpowiednie punkty.
if (pacman.złap(prezenty[i])) { pacman.punkty += prezenty[i].punkty; prezenty[i] = null; 3. Sprawdź działanie programu 4. Dopracuj metodę wyświetlająca pacmana tak, by można było jednoznacznie określić kierunek jego poruszania się. W tym celu wystarczy na podstawie wartości zmiennych dx, dy określić wartość początkową kąta alfa w metodzie fillarc. Zmień w metodzie rysuj klasy Pacman linijkę z kodem metody fillarc na int alfa = 45; if (dx < 0) alfa = 180 + 45; if (dx > 0) alfa = 45; if (dy < 0) alfa = 90 + 45; if (dy > 0) alfa = 270 + 45; g.fillarc(x-15, Y-15, 30, 30, alfa, 270); 5. Sprawdź działanie programu VII. GRA NA CZAS Zmodyfikujemy czas życia prezentów na ekranie w taki sposób, aby liczba punktów stopniowo malała z upływem czasu. 1. W klasie Prezent uzupełnij treść metody zmienstan o zmniejszenie liczby punktów if (punkty > 0) { punkty--; 2. W metodzie commandaction klasy Plansza w sekcji obsługi polecenia start dopisz instrukcje uruchamiające dla każdego z prezentów własny wątek if (c == start) { for (int i = 0; i < 10; i++) { new Thread(prezenty[i]).start(); new Thread(pacman).start(); 3. Uruchom program i postaraj się pozyskać maksymalną liczbę punktów ;) VIII. ZADANIA DO SAMODZIELNEGO WYKONANIA 1. Zaprogramuj wyjście z programu. Zwróć uwagę na to że ekran klasy Canvas nie posiada możliwości wywołania metody notifydestroyed() klasy MIDLet. W tym celu można przekazać wskaźnik na klasę główną midleta w konstruktorze Plansza. Drugie rozwiązanie polega na wprowadzeniu ekranu startowego typu Form, z którego rozpoczyna się gra, i do którego wraca się po jej ukończeniu. Przyciski [start] oraz [koniec] byłyby wówczas osadzone w Form. 2. Zaprogramuj zatrzymanie się gry w momencie, kiedy wszystkie prezenty zostaną zebrane. Wyświetl okno z wynikami gry i propozycją rozpoczęcia nowej gry. 3. Zastanów się jak w oknie Plansza zrealizować mechanizm tymczasowego zatrzymania gry (pauza). 4. Dopracuj rozmieszczanie prezentów na ekranie tak by się nie pokrywały