Tutorial Space Invaders în Java

Descarca proiect

Salutare tuturor. Revin după ceva timp cu un mic proiect ce l-am făcut pentru cursul “Programare Orientată pe Obiecte”. Mai jos este documentația proiectului și codul acestuia. Subliniez că tot proiectul nu respectă standardele de coding și este mai mult orientativ, pentru a familiariza eventualii curioși de modul în care se realizează animațiile și logica generală din spate.

Introducere

Space Invaders este un joc video arcade lansat de către Tomohiro Nishikado în 1978. Este unul din primele jocuri shooter, iar scopul jocului este să învingi valurile de extratereștri folosindu-te de o armă laser pentru a acumula cât mai multe puncte.

Spre deosebire de versiunea originală a jocului, franciza Space Invaders a îmbrăcat forme tot mai variate aducându-se contribuții la fiecare nouă apariție. În acest proiect, voi încerca să mă depărtez puțin de ideea originală a jocului, în care o “matrice” de inamici trebuie distruși într-un timp limitat și să merg pe o variantă puțin mai dinamică, unde inamicii pot veni aleator. De asemenea, în jocul inițial nu exista o finalitate efectivă a jocului, nivelele devenind din ce în ce mai dificile până în punctul în care jucătorului îi era imposibil să câștige, scopul fiind ca scorul să fie cât mai ridicat. Totuși, în cadrul acestui proiect, m-am decis să aleg varianta unui joc finit, în principal datorită complexității suplimentare care ar fi apărut.

Obiective

  1. Obiectiv I

Elaborarea claselor principale, la nivel abstract, cu scopul găsirii relațiilor dintre clase pentru a vedea precis ce elemente sunt reutilizabile. O atenție sporită se va acorda si găsirii modelului optim pentru a implementa structura generală a jocului.

  1. Obiectiv II

În prima fază a proiectului mă voi axa pe realizarea animațiilor generale: mișcarea liniară a obiectelor, eventual cu accelerație constantă, constructia unor elemente temporare ce se vor putea mișca cu viteza constantă (ele vor deveni ulterior fasciculele laser care merg spre inamici). În final, voi realiza triggere la tastatura pentru a putea controla aceste animații.

  1. Obiectiv III

Realizarea obiectelor din mediul de joc, împreună cu extratereștrii inamici și găsirea unei metode aleatoare de generare a acestora fără a obstrucționa mersul fluid al jocului.

  1. Obiectiv IV

Crearea unui mod primitive de stocare a scorului, pe perioada jocului, împreună cu timpul efectiv de joc. În funcție de aceste două variabile se va genera un scor.

 

Functionarea jocului

Jucătorul controlează o navă cu ajutorul mouse-ului. Mișcarea navei este restricționată pe axa Ox, de aceea se folosește componenta orizontală a mișcării. La acțiunea oricărui clic se inițializează o nouă lovitură, existând totuși un timp minim între lovituri.

Scopul jocului este să distrugem grupul de extratereștri ce se mișcă in zig-zag, iar la final să distrugem nava Boss, navă care necesită lovituri multiple din partea noastră. Jucătorul trebuie să fie permanent atent la loviturile inamice, dar și la o eventuală trecere a inamicilor de linia de demarcație. Odată ce inamicii trec de această linie jocul este practic terminat, iar pe consolă se afișează în repetate rânduri ‘Ceva rău se întâmplă!’.

La fiecare lovitură înscrisă de către jucător punctajul său crește cu 5, iar dacă un inamic îl lovește punctajul acestuia va scădea cu 20.

Scorul maxim este 200. Din considerente personale, am ales să elimin noțiunea de vieți și să adaug punctaje negative.

imagine1

Armata de extratereștri și loviturile acestora

 

imagine2

Inamicul final “Boss”

 

imagine3

Mesajul final, după înfrângerea inamicilor

 

Diagrama UML a claselor

uml

Descrierea metodelor și ai algoritmilor

  1. Problema flickering-ului. Double buffering

Încă de la început am preferat această abordare, de îndată ce am înțeles principala problema a animațiilor în Java și anume flickering-ul. Acesta apare de obicei atunci când este folosită clasa Timer, și apelăm metoda repaint(). Metoda repaint() apelează metoda update(), care șterge conținutul panoului în care au loc animațiile, iar apoi apelează metoda paint() cu care se desenează elementele noi.

Aceasta ștergere completă a panoului cauzeaza acel flicker și de aceea se folosește adesea metoda double buffering, in care scriem orice modificare pe un panou temporar, ce transferăm apoi pe panoul principal. După cum ne-am aștepta, această metoda este pretențioasă din punct de vedere al memoriei consumate, dar în contextul actual, la majoritatea aplicațiilor această tehnică nu pune probleme.

  1. Thread-uri

În cadrul unui joc care conține animații, sunt o mulțime de mici operații care trebuie executate concomitent. În jocul Space Invaders, avem o serie de operații paralele dintre care menționăm:

  • Mișcarea navei controlate de jucător
  • Mișcarea automată a navelor inamice
  • Animarea loviturilor, atât din partea jucătorului, cât și din partea inamicilor etc.

Tocmai din aceste motive, mi s-a părut rezonabilă ideea de a folosi Thread-uri, ce sunt foarte optimizate în arhitectura Java și permit folosirea cu ușurintă a unor procese paralele.

Toate clasele prezente în proiect utilizează un Thread implementând interfața Runnable împreună cu metoda obligatorie run().

În interiorul metodei run() ne definim metoda ce am dori să o executăm pe tot parcursul jocului.

 

Mai sus este funcția run() din clasa Main(). Ea este foarte similara cu metodele run() din toate celelalte clase, singurele elemente de diferențiere fiind funcțiile din corpul principal al funcției. Toate metodele run() conțin un bloc try {…} catch(InterruptedException ie) {..} deoarece în lipsa acestuia procesele s-ar bloca de fiecare dată. În felul acesta putem să “adormim” executarea continuă a procesului.

 

În principal, acest proiect a folosit intens Thread-urile tocmai fiindcă permit realizarea animațiilor cu ușurință. De aceea majoritatea funcțiilor din corpul lui run() sunt de mișcare(ex.  Mutarea cu 2px intr-o direcție a loviturii inamicului).

 

Descrierea claselor

  1. Clasa SpaceInvaders

Reprezintă clasa principală a jocului, iar aici sunt inițializate toate celelalte componente. Principial, componentele sunt inițializate în constructor, iar apoi tot ce trebuie să facem este să “ascultăm”, folosindu-ne de MouseListeners, eventualele modificări care apar din partea jucătorului (modificări de poziție, o nouă lovitură etc.). Există și unele acțiuni care se petrec automat.

Tot aici are se apelează toate metodele de desenare în interiorul metodei paint(). Metoda update() suprascrie metoda implicită și apelează direct paint(). De aceea, la apelarea lui repaint() în interiorul Thread-ului, nu mai avem flickering.

O caracteristică esențială a acestei clase este că pe lângă implementarea interefeței Runnable, tot aici se extinde JFrame. Acest lucru este de o importanță deosebită, fiindcă orice desenare va avea loc pe un obiect grafic de tipul clasei SpaceInvaders.

  1. Clasa Nava

În această clasă se generează, după cum sugerează și numele nava folosită de jucător care se generează pe un obiect Graphics reprezentat de obiectul SpaceInvaders. Tot aici, se generează un vector de tip Shot, care conține toate loviturile active date de către user. La fiecare clic situat în interiorul ferestrei se va crea o noua lovitura ce va fi adăugată la acest vector.

Desenarea navei este trivială, ea reducându-se la utilizarea metodei drawImage. Totuși, tot aici se desenează loviturile după ce li se verifică în prealabil starea.

  1. Clasa Shot

Aceasta este clasa în care se desenează loviturile. Unul dintre cei mai importanți parametri este stareLovitura, parametru ce este folosit îndeosebi în clasa Nava. Acolo, ne folosim de această proprietate pentru a ști dacă mai desenăm sau nu mai desenăm lovitura.

Intuitiv, ne dăm seamă că în această clasă trebuie să implementăm un Thread, deoarece vorbim de un proces ce ne dorim să îl executăm în paralel. Acest proces se rezumă la mișcarea pe verticală în sus a loviturii.

  1. Clasa ShotExtraterestru

În ceea ce privește metodele, ea este identică cu clasa Shot, singura diferență fiind că mișcarea este orientată în jos. La nivelul proprietăților elementele diferite se rezumă la alte imagini sau lățimi.

  1. Clasa Extraterestru

În această clasă se desenează un extraterestru, dacă acesta nu a fost lovit. Proprietatea stareLovitura ne ajută deosebit de mult în această privintă, deoarece pe măsură ce lovitura jucătorului avansează, ea va ajunge la o serie de coordonate ce vor coincide cu ale extraterestrului și în felul acesta vom putea testa printr-o inegalitate dacă vărful loviturii este la o valoare mai mică în înălțime ca înăltimea bazei extraterestrului. Atunci când această instrucțiune returnează true putem actualiza doua proprietăti(a loviturii jucătorului si a extraterestrului).

Alte două metode importante sunt cele care ne returnează poziția curentă pe axa X și Y. Ele ne ajută să determinăm poziția extraterestrului în interiorul armatei și la mișcarea acesteia.

  1. Clasa Armata

Ea lucrează cu o colecție de obiecte de tip Extraterestru ce le grupează într-o matrice dreptunghiulară cu înălțimea 3 și lățimea 10.

Pornim de la o serie de întrebări simple ce se regăsesc și în această clasă:

  • Cum putem ști care este marginea armatei noastre?
  • Cum putem mișca cei 30 de extratereștri ca o colecție unitară?
  • Cum vom genera atacurile extratereștrilor?

La prima întrebare, răspunsul este să verificăm pentru fiecare rând și coloană din matrice dacă extratestrul a fost lovit. Cei care nu sunt loviți vom verifica de exemplu în cazul unei mișcări la dreapta daca getXPos si getYPos sunt mai mici ca marginile ecranului. Daca acest lucru este adevărat pentru toți extratereștri ce nu au fost loviți atunci ne putem continua mișcarea spre dreapta. În caz contrar, ne modificăm starea de mișcare prin intermediul unei variabile, iar acum ne vom schimba direcția de deplasare și vom face aceleași verificări.

 

Loviturile date de către toți extratereștrii vor fi memorate într-un vector de lovituri. Tot ce ne mai rămâne este să generăm la fiecare iterație din Thread-ul principal SpaceInvaders un număr aleator. Dacă extraterestrul de pe linia i și coloana random nu a fost lovit atunci vom genera în acel loc un nou obiect de tip Shot.

  1. Clasa Boss

Este identică structural cu clasa Extraterestru, cu excepția că aceasta conține și câteva proprietăți suplimentare pentru mișcarea pe orizontală și generarea de shot-uri cu frecvență mare. Obiectul de tip Boss este inițializat de indată ce proprietatea stareArmata ajunge la false, iar acest lucru se întâmplă când toți extratereștrii au fost loviți.

 

Concluzii

Jocul SpaceInvaders, cu implementarea sa în Java, reprezintă o adeziune interesantă între algoritmi fundamentali, noțiuni elementare de animații, Thread-uri și ActionListeners.

 

Deși jocul este functional, o mai mare atenție ar trebui acordată la reutilizarea codului, deoarece cel puțin 2 clase nu sunt decât variațiuni, mai mici sau mai mari ale aceluiași tip de obiect. De aceea, o eventuală versiune viitoare ar încerca să fie scrisă într-un mod mai optimal și abstract.

Postare asemănătoare

Ștefănescu Marian

Pasionat de științe exacte, drumeții.

2 Comments

  1. Balaita   •  

    Interesant topic! Ai putea sa mai faci astfel de tutoriale? 😀
    Sunt incepator in limbajul Java si doresc sa progresez. Felicitari pentru munca depusa!

    • Stefanescu Marian   •     Author

      Salut. Multumesc de apreciere. Mai sunt cateva proiecte Java pe contul meu de Github. Orice imbunatatire e binevenita. De exemplu, codul din acest articol nu respecta deloc vreun pattern arhitectural, dar cred ca e de ajutor ca si algoritmica.

Lasa un comentariu