Animacja wielowątkowa - gra zręcznościowa. I. UTWORZENIE SZKIELETU APLIKACJI 1. Uruchom środowisko programowania NetBeans. Utwórz aplikację typu Swing tworząc projekt o nazwie Projekt10 2. Dodaj do projektu klasę typu JFrame o nazwie Gra. We właściwości resizable ramki usuń zaznaczenie 3. W konstruktorze klasy Gra ustal rozmiar ramki na 500x800 4. W klasie Gra utwórz wewnętrzną klasę o nazwie Plansza dziedziczącą z klasy JPanel ; umieść w niej metodę paintcomponent () z instrukcjami: protected void paintcomponent(graphics g) { g.setcolor(color.darkgray); g.fillrect(0, 0, getwidth(), getheight()); 5. W klasie Gra zadeklaruj pole: Plansza plansza; 6. W konstruktorze klasy głównej utwórz obiekt plansza, dodaj go do ramki ( metoda add() ), ustal jego rozmiary i położenie (metoda setbounds() ) tak, by zajął całą powierzchnię ramki. 7. Uruchom aplikację i sprawdź położenie, rozmiar oraz kolor tła panelu. 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 Planszy. Będzie ona przechowywała wspólne cechy obiektów takie jak położenie na planszy, 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. 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). 1
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 (te z klauzulą abstract) 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 instrukcji 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 wyświetl liczbę 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. g.setcolor(color.yellow); g.fillarc(x-15, Y-15, 30, 30, 45, 270); g.setcolor(color.white); g.drawstring(punkty + "", X, Y-15); 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 Gra. Pod jej nagłówkiem umieść deklarację obiektu pacman klasy Pacman Pacman pacman; 9. W konstruktorze klasy Gra umieść instrukcję tworzenia obiektu pacman w pozycji (30, 40) pacman = new Pacman(30, 40); 2
10. W metodzie paintcomponent klasy Plansza umieść instrukcję rysowania obiektu Pacman pacman.rysuj(g); 11. Uruchom program i sprawdź czy figurka się wyświetla na ekranie IV. OBSŁUGA KLAWIATURY Zdefiniujemy metody obsługi zdarzeń klawiatury w celu umożliwienia zmiany pozycji obiektu graficznego na planszy z wykorzystaniem strzałek. 1. W klasie Gra zaimplementuj interfejs KeyListener. W tym celu doprowadź nagłówek tej klasy do postaci: public class Gra extends JFrame implements KeyListener { 2. Utwórz wszystkie jego abstrakcyjne metody, usuń w nich instrukcje throw wyrzucające wyjątki 3. 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. Na końcu odśwież panel. public void keypressed(keyevent e) { int keycode = e.getkeycode(); if (keycode == KeyEvent.VK_UP) { pacman.kierunek(0, -1); // w górę if (keycode == KeyEvent.VK_DOWN) { pacman.kierunek(0, 1); // w dół if (keycode == KeyEvent.VK_LEFT) { pacman.kierunek(-1, 0); // w lewo if (keycode == KeyEvent.VK_RIGHT) { pacman.kierunek(1, 0); // w prawo if (keycode == KeyEvent.VK_SPACE) { pacman.kierunek(0, 0); // stop plansza.repaint() ; 4. W konstruktorze klasy Gra dodaj instrukcje umożliwiającą obsługę klawiatury i instrukcje dodające nasłuch: setfocusable(true); addkeylistener(this); 5. 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); 6. Uruchom aplikację, sprawdź działanie klawiatury, naciskając strzałki 7. W klasie Gra dodaj metodę start() zawierającą instrukcje uruchamiające wątek pacmana private void start() { pacman = new Pacman(30, 40); pacman.kierunek(1, 0); new Thread(pacman).start(); 8. Metodę start() wywołaj w metodzie keypressed, wtedy gdy zostanie wciśnięty klawisz ENTER: if (keycode == KeyEvent.VK_ENTER) { start(); // start 9. Uruchom aplikację, sprawdź działanie klawiatury, naciskając klawisz ENTER, a następnie strzałki. 3
V. Definicja obiektu klasy Timer w klasie Gra; automatyczne odświeżanie grafiki 1. W klasie Gra zaimplementuj interfejs ActionListener - przed nawiasem rozpoczynającym definicję klasy Gra dopisz: ActionListener 2. Zaimportuj ten interfejs a następnie lewym przyciskiem myszy kliknij ikonę żarówki po lewej stronie nagłówka klasy Animacja i wybierz polecenie Implement all abstract metods -zostanie wygenerowana metoda actionperformed(), która automatycznie będzie się wykonywała, co czas określony przez obiekt klasy Timer 3. Usuń instrukcję odświeżania planszy w metodzie keypressed, a następnie przenieś ją do metody actionperformed(): public void actionperformed(actionevent e) { plansza.repaint(); 4. W klasie Gra zadeklaruj pole t klasy Timer. Zaimportuj klasę javax.swing.timer, wybierając odpowiednią klasę w dialogu Fix Imports. W konstruktorze klasy Gra dopisz instrukcję tworzącą zegar generujący zdarzenia w stałych odstępach czasu. Zegar uruchomimy na wciśnięcie klawisza Enter w kolejnym etapie aplikacji. t = new Timer(10, this); t.start(); 5. Uruchom program sprawdź jak teraz porusza się pacman 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. Klasa powinna dziedziczyć z klasy Figura. 2. Zaimplementuj wszystkie metody abstrakcyjne w klasie Prezent i usuń ich domyślnie wygenerowaną zawartość 3. 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; 4. 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(new Color(255-5*punkty, 5*punkty, 0)); g.filloval(x-10, Y-10, 20, 20); g.setcolor(color.white); g.drawstring(punkty + "", X-5, Y+5); 5. Metodę zmienstan() na razie pozostaw pustą. 6. W klasie Gra dodaj deklarację 10-elementowej tablicy obiektów typu Prezent oraz generator liczb losowych klasy Random Prezent[] prezenty = new Prezent[10]; Random r = new Random(); 7. W klasie Plansza utwórz metodę rozmieść() tworzącą wszystkie obiekty Prezent w tablicy void rozmieść() { int x,y,punkty; x= r.nextint(getwidth()-20); y= r.nextint(getheight()-20); punkty= r.nextint(25)+25; prezenty[i] = new Prezent(x,y,punkty); 4
8. Na końcu konstruktora klasy Gra wywołaj metodę rozmieść która rozmieści prezenty na obiekcie plansza: plansza.rozmieść(); 9. W treści metody paintcomponent klasy Plansza przed instrukcja rysowania Pacmana, dodaj instrukcje rysujące wszystkie obiekty typu Prezent z tablicy if (prezenty[i] == null) continue; prezenty[i].rysuj(g); 10. Sprawdź działanie programu. Na ekranie powinno się ukazać 10 obiektów graficznych typu Prezent. VI. INTERAKCJA POMIĘDZY OBIEKTAMI NA PLANSZY. 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 klasy Prezent a pacmanem boolean złap(prezent p) { return Math.sqrt((X - p.x)*(x - p.x) + (Y - p.y) * (Y - p.y)) < 15; 2. W metodzie paintcomponent 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. Uruchom program i postaraj się pozyskać maksymalną liczbę punktów VII. GRA NA CZAS Zmodyfikujemy czas życia prezentów na planszy 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 klasie Gra uzupełnij metodę start o instrukcje tworzące i uruchamiające wątki dla każdego z prezentów plansza.rozmieść(); new Thread(prezenty[i]).start(); 3. Uruchom program sprawdź zmianę liczby punktów na prezentach w czasie. VIII. ZADANIA DO SAMODZIELNEGO WYKONANIA 1. W metodzie zmieństan() klasy Pacman dodaj instrukcje, które spowodują odbicie pacmana od krawędzi panelu Wskazówka: Pole plansza zadeklaruj jako statyczne w klasie Gra. W metodzie zmienstan() dopisz instrukcje modyfikujące wartości X i Y po sprawdzeniu, że pacman znalazł się poza planszą. Dla zmiennej X instrukcje te mogą mieć postać: if (X < 0) { X = 0; dx = -dx; if (X > Gra.plansza.getWidth()) { X = Gra.plansza.getWidth(); dx = -dx; 2. Zaprogramuj zatrzymanie się gry w momencie, kiedy wszystkie prezenty zostaną zebrane lub gdy zostaną tylko te z zerową liczbą punktów. Po ukończeniu gry wyświetlaj komunikat z wynikiem 3. Zmodyfikuj instrukcje w metodzie paintcomponent() tak, aby nie rysowały się prezenty z zerową liczbą punktów 4. Umieść na ramce 2 pola tekstowe jtextfield i wyświetlaj w nich czas gry i liczbę punktów 5. Zastanów się jak w oknie Plansza zrealizować mechanizm tymczasowego zatrzymania gry (pauza). 6. Dopracuj rozmieszczanie prezentów na ekranie tak by się nie pokrywały 5