[Pl][Java] Dux, czyli backend'owiec robi pierwszą grę

Posted by Rizn on 27/07/2016

Article originally written in Polish. Click here for automatic translation.

Kod gry na GitHub.

Postanowiłem się pobawić w robienie (bardzo prostych) gier, aby zobaczyć czy dużo różni się od programowania backend'owego.

Wybrałem JAVA, jako, że jest najbliższa PHP'owi jeśli chodzi o składnie. Zdecydowałem się na nieużywanie framework'ów typu LibGDX, aby po prostu poznać same podstawy konstrukcji gier „od podszewki”. Postanowiłem użyć bazowej klasy Applet (dosyć już antyczna technologia) z implementacją Runnable, KeyListener. Kiedyś wałkowałem tutoriala na Kilobolt, więc parę pomysłów było stamtąd zapożyczonych.

A jako grę, postanowiłem zrobić kopię jeszcze bardziej antycznej mini-gry, która była ukrytą niespodzianką w Lotus 2 Turbo Challenge (jedną z moich ulubionych wyścigówek) na Amigę.


Po wpisaniu "DUX" w menu głównym gry, możemy wejść do tajemniczej komnaty.


Prosta bonusowa gra.

Java Applet ma fajne metody [init(), start(), run(), update(), paint(), repaint()], które w miarę łatwo pozwalają na „wieczne” odświeżanie rysowanego obrazu co oczywiście jest potrzebne do platformo-podobnych gier. A do tego KeyListener, który wysłuchuje wejścia z klawiatury.

Asset'y typu dźwięk i grafikę wyciągnąłem z dux'a pod emulatorem i zostały zainicjowane w metodzie init(). Czcionka użyta to Digital-7. Cała reszta (kolizje i ruch) praktycznie odbywa się w run(). Na końcu metody, użyty jest repaint(), aby wszystko przerysować z aktualnymi współrzędnymi.


Implementacja Dux używając java applet.

Zasady gry są dosyć proste:

  • mamy kilka typów stworków: sowa, królik, kaczka, punkty (dodające ekstra strzały).
  • stworki przemieszczają się z jednej strony do drugiej i z powrotem, w trzech rzędach
  • kaczki są trochę wredne, i jak się ich nie zestrzeli to w kolejnym rzędzie zmieniają kolor i mają ekstra życie. A jak kaczka dojdzie niezestrzelona do końca trzeciego rzędu to tracimy pięć cennych naboi
  • gdy zestrzelimy wszystkie stworki, to przechodzimy do kolejnego poziomu, który jest powtórzeniem poprzedniego, tylko, że wszystko dzieje się szybciej
  • gdy się skończą naboje lub czas to mamy „game over”

Klasy w kodzie są zorganizowane odpowiednio:

dux:

  • Assets – inicjalizacja dźwięku, obrazków
  • AssetsSetup – inicjalizacja początkowego ułożenia stworków
  • Main – tu wszystko inicjujemy, powtarzamy i rysujemy
  • Bullet – naboje, zarządzanie nabojami
  • Gun – broń
  • Projectile – zarządzanie wystrzałami i sprawdzanie kolizji wystrzału ze stworkami
  • Sound – klasa ze statycznymi metodami odpowiedzialna za muzykę i efekty dźwiękowe
  • Status – tutaj zmieniamy stan gry, e.g. „playGame”, „gameOver”, „loadLevel”. Także tu trzymamy punkty, high score, etc.
  • Timer – odpowiedzialna klasa za odliczanie czasu

dux.creatures:

  • Creature – bazowa klasa zawierająca wspólne zachowanie stworków
  • Blank – niewidzialny slot na stworka
  • Duck – kaczka, główny „przestępca” w grze
  • Owl – sowa
  • Rabbit – królik
  • Points – punkty. Po trafieniu dostajemy ekstra naboje

dux.utils:

  • ImageTools – statyczne metody pozwalające na konwersje obrazków (lustrzane odbicie, zmiana kolorów)
  • LoaderTools – ładowarka obrazków

Spostrzeżenia na temat JAVY/robienia gier z punktu PHP:

Robienie gier opiera się na prostych zasadach: rozkaz (e.g. ruch/strzał), zmiana współrzędnych obiektów, sprawdzenie kolizji, wyrenderowanie i wyświetlenie klatki obrazu... i tak w kółko. Jest to trochę inna filozofia co do backend'u, gdzie się czeka pasywnie aż ktoś łaskawie wywoła kontroler/komendę i zazwyczaj po dokonaniu paru zmian w bazie danych i „wypluciem” danych użytkownikowi, znów idziemy „spać”.

Czasem się zastanawiałem po co taki nacisk jest na programowanie obiektowe w backend'zie. Przetwarzamy jakieś dane, zazwyczaj używając jakichś formularzy, skomplikowanych serwisów, cache'owanie danych. Czasem mi się wydaje, że OOP w server-side'owych technologiach jest trochę na siłę (np. obiektowa abstrakcja danych z bazy). Równie dobrze (a nawet i lepiej) można zastosować inne paradygmaty programowania (lub ich hybrydy) np. programowanie funkcjonalne. Jeśli chodzi o robienie gier, to akurat programowanie obiektowe pasuje jak ulał, ponieważ większość rzeczy są rzeczywistymi obiektami jak w życiu (broń, naboje, postacie, etc).

Jeśli chodzi o język JAVA to brakuje mi trochę rzeczy z PHP'a. Nie mogłem znaleźć prostego sposobu na domyślną wartość przekazaną funkcji. W PHP da radę zrobić coś takiego, ale w JAVA już nie:


function creature ($speedX = 10) {}

Natomiast w JAVIE jest dużo fajnych rzeczy, których nie ma w PHP'ie, np. wątki. Jeden wątek może odliczać czas, drugi grać muzykę, etc. W PHP'ie da radę emulować wątki, np. poprzez forki procesów, ale to nie to samo.

ArrayList, czyli tablica pewnego typu obiektów. Fajna rzecz, która potrafi uporządkować wiele rzeczy. W PHP'ie niestety nie ma takiego wynalazku, tylko zwykłe tablice do których można wrzucić co się chce. Oczywiście można sobie stworzyć własną implementacje (lub użyć jakiegoś ArrayCollection z symfony/doctrine), ale by się przydał taki typ danych z automatu w PHP.

Jako, że gra została zrobiona w javowym applecie, za bardzo tego nie ma jak pokazać w akcji, jako, że większość przeglądarek nie ma zainstalowanej javy lub nawet nie pozwala na to.

Aczkolwiek to nie problem aby skompilować kod i odpalić w appletviewer via Eclipse czy IntelliJ Idea. Po prostu sklonuj kod i zaimportuj projekt. O czym nie należy zapomnieć to ustawienie katalogu „resources” jako resources root. Po zrobieniu tego kroku należy kliknąć prawym przyciskiem na klase „Main.java” -> „Run Main” (IntelliJ Idea) lub „Run As” -> „Java Applet” (Eclipse). Jeśli chodzi o wersje JAVY, to użyta została wersja JDK 1.8. Nie testowałem na innych wersjach.


W IntelliJ Idea, katalog „resources” można ustawić klikając prawym przyciskiem myszy i ustawienie jako „Resources root”.


W Eclipse należy wejść do „Properties” -> „Java Build Path” i w zakładce „Source” dodać katalog „resources”.

Jeśli jesteś programistą i znalazłeś błędy w strukturze kodu, to please podziel się pisząc mi na priva lub przez pull request'a w GitHub'ie :)