2. týždeň

Red Alert: Reactor Overheat!

Reaktor, v ktorom nadmerne stúpne teplota, vie narobiť riadnu paseku.

Motivácia

Vitaj vo výcvikovom stredisku, kadet, kde z teba počas 12 týždňového tréningu spravíme ozajstného chlapa (rozumej objektového programátora)! O formu a podobu tvojho výcviku sa bude starať náš taktický a strategický tím, ktorý má s výcvikom kadetov tvojho formátu dlhoročné skúsenosti a rozhodne ťa nenechá vydýchnuť. Tvoj výcvik je totiž jeho prioritou.

Dnes to bude len zahrievacie kolo, v ktorom uvidíme, čo si zač, čo v tebe je a čo od teba teda môžeme očakávať. Aj keď sa jedná len o zahrievacie kolo, mnoho kadetov trhá tím a zostáva pri plnení dnešnej misie ešte niekoľko týždňov. Veríme, že ty partiu trhať nebudeš!

Z operačného strediska zdraví Manager.

Ciele

  1. Naučiť sa vytvárať vlastné triedy, ich konštruktory a metódy.
  2. Naučiť sa preťažiť implementáciu metódy pre rôzne typy parametrov.
  3. Naučiť sa reprezentovať stav objektu členskými premennými (zapuzdrenie údajov).
  4. Naučiť sa riadiť viditeľnosť členských premenných a metód.
  5. Naučiť sa vytvárať inštancie objektov z tried.

Postup

Krok 1: Reactor Class

Tvojou úlohou je vytvoriť triedu, ktorá bude modelovať reaktor v prostredí hry Alien Breed. Stav takého reaktora bude reprezentovaný aktuálnou teplotou jeho jadra, celkovým poškodením reaktora a animáciou, ktorá aktuálny stav graficky vizualizuje. Postupne budeš do tejto triedy pridávať aj správanie reaktora pri jeho činnosti.

Diagram tried, ktorý vyjadruje vzťah triedy Reactor s triedou AbstractActor.
Obr. 1: Diagram tried, ktorý vyjadruje vzťah triedy Reactor s triedou AbstractActor.

Úloha 1.1

Vytvorte v projekte Java balík sk.tuke.kpi.oop.game.

Keďže projekt používa nástroj Gradle, je potrebné dodržať jeho konvenciu na umiestňovanie Java zdrojových súborov, a preto je potrebné vytvoriť tento balík v adresári src/main/java.

Úloha 1.2

V balíku sk.tuke.kpi.oop.game vytvorte triedu Reactor, ktorá bude potomkom triedy AbstractActor.

Zápis, ktorý v jazyku Java hovorí o tom, že trieda A je potomkom triedy B, je nasledovný:

class A extends B {
    // telo triedy
}

Trieda AbstractActor, ktorú použijete ako rodičovskú triedu novej triedy Reactor, predstavuje všeobecného aktéra hry. Je súčasťou knižnice GameLib, ktorá je definovaná medzi závislosťami nášho projektu. Nie je preto potrebné ju vytvárať. Stačí ju importovať do súboru vytvoreného pre triedu Reactor. V tomto vám ale dokáže pomôcť samotné vývojové prostredie po tom, čo napíšete názov triedy, ktorý kompilátor bez import príkazu nebude vedieť interpretovať.

Poznámka

Príkaz import, ktorý prostredie doplní, slúži len na skrátenie zápisu plného mena triedy, ktorým je sk.tuke.kpi.gamelib.framework.AbstractActor. Po použití importu stačí v súbore používať krátke meno AbstractActor.

Poznámka

Keď budete potrebovať importovať nejakú triedu z poskytnutej knižnice a prostredie vám ponúkne na výber z niekoľkých možných tried s rovnakým krátkym názvom, tú správnu možnosť identifikujete podľa prefixu balíka sk.tuke.kpi.gamelib.

Úloha 1.3

Do triedy Reactor pridajte členské premenné, ktoré budú reprezentovať stav reaktora: aktuálnu teplotu, poškodenie a animáciu reprezentujúcu reaktor.

Jedná sa o nasledovné členské premenné:

  • temperature - aktuálna teplota reaktora, reprezentovaná celým číslom, pričom minimálna hodnota je 0 stupňov.
  • damage - poškodenie reaktora, percentuálna hodnota, reprezentovaná celým číslom v rozsahu od 0 (nepoškodený) do 100 (zničený).
  • normalAnimation - objekt animácie, ktorá reprezentuje bezporuchový stav reaktora. Použijeme typ Animation reprezentovaný triedou Animation z balíka sk.tuke.kpi.gamelib.graphics.

Pri tvorbe členských premenných nezabúdajte na to, že nechceme, aby boli voľne viditeľné mimo triedu! Použite preto patričný modifikátor viditeľnosti.

Poznámka

V počítačovej grafike (hlavne v súvislosti s 2D hrami) je možné sa veľmi často stretnúť s termínom sprite. Sprite je malý dvojrozmerný obrázok (alebo animácia), ktorý je integrovaný do väčšej scény. Efekt animácie často vzniká postupným zobrazením niekoľkých obrázkov (snímkov) na rovnakej pozícii.

Snímky sprite-u hlavnej postavy hry Jet Set Willy idúcej vpravo
Obr. 2: Snímky sprite-u hlavnej postavy hry Jet Set Willy idúcej vpravo

Výsledná animácia vytvorená postupným prehratím jednotlivých obrázkov animácie kombinovaná s horizontálnym posúvaním
Obr. 3: Výsledná animácia vytvorená postupným prehratím jednotlivých obrázkov animácie kombinovaná s horizontálnym posúvaním

V hernej knižnici, ktorú používame, budeme pracovať s objektami typu Animation, ktoré je možné rovnako použiť na jednoduché statické obrázky, ako aj na obrázky obsahujúce snímky animácie sprite-u.

Úloha 1.4

Vytvorte bezparametrický konštruktor triedy Reactor, v ktorom novovytváranému objektu inicializujete premenné reprezentujúce jeho stav.

V rámci inicializácie objektu nastavte:

  • animáciu, ktorá bude znázorňovať reaktor, na nasledujúci obrázok

    Animácia reactor_on.png (rozmery sprite-u: 80x80, trvanie snímku: 0.1 sekundy)
    Obr. 4: Animácia reactor_on.png (rozmery sprite-u: 80x80, trvanie snímku: 0.1 sekundy)

  • počiatočnú teplotu jadra reaktora na hodnotu 0, a

  • počiatočnú percentuálnu hodnotu poškodenia na hodnotu 0.

Animácie sa nachádzajú v projekte v priečinku src/main/resources/sprites/. Pri nastavovaní cesty k animácii používajte relatívnu cestu, pričom za koreňový priečinok je považovaný src/main/resources/. Cesta k animácii reaktora preto bude vyzerať takto: sprites/reactor_on.png.

Poznámka

Pri písaní ciest k súborom animácie môžete tiež využiť skratku Ctrl+Space na dopĺňanie názvov súborov a adresárov.

Pre nastavenie animácie využite nasledovný fragment kódu umiestnený v konštruktore triedy Reactor:

// create animation object
normalAnimation = new Animation("sprites/reactor_on.png", 80, 80, 0.1f, Animation.PlayMode.LOOP_PINGPONG);
// set actor's animation to just created Animation object
setAnimation(normalAnimation);

Poznámka

Dokumentáciu k jednotlivým triedam, ich konštruktorom a metódam môžete získať priamo v rámci vývojového prostredia umiestnením textového kurzora na meno požadovaného elementu a použitím skratky Ctrl+Q. Táto skratka je dostupná aj pri prechádzaní zoznamom možností ponuky automatického dopĺňania kódu (Ctrl+Space). Dokumentácia je tiež dostupná na samostatnej stránke.

Úloha 1.5

Overte správnosť svojej implementácie vytvorením inštancie reaktora a jeho vložením do hernej scény.

Pokiaľ ste postupovali správne, po spustení projektu (Shift+F10) budete vedieť pomocou inšpektora vytvoriť inštanciu triedy Reactor a umiestniť ju do hry.

Plne funkčný reaktor
Obr. 5: Plne funkčný reaktor

Úloha 1.6

Vytvorte metódy getTemperature() a getDamage(), pomocou ktorých budete vedieť získať hodnotu aktuálnej teploty jadra a jeho poškodenia.

Tieto metódy poskytnú prístup na čítanie hodnôt teploty a poškodenia. Samozrejme, ich viditeľnosť je potrebné nastaviť na public. Následne viete opäť skontrolovať ich funkcionalitu pomocou inšpektora.

Poznámka

Všimnite si, že metóda na získanie hodnoty členskej premennej temperature sa volá getTemperature() a pre damage sa volá getDamage(). Takéto pomenovanie metód na získanie hodnôt premenných pridaním predpony get k názvu premennej je konvenčné a budeme ho často využívať. Výsledná metóda sa zvykne "hovorovo" nazývať aj getter.

Krok 2: Intermezzo - Method Overloading

Teraz, keď už vieš, ako vytvoriť triedu a jej metódy, ukážeme ti jednu z užitočných vlastností polymorfizmu (ku ktorému sa ešte viackrát vrátime). Tou vlastnosťou je preťaženie metód (anglicky method overloading).

Úloha 2.1

V balíku sk.tuke.kpi.oop.game vytvorte triedu Computer ako potomka triedy AbstractActor.

Ako animáciu použite sprite obrázok computer.

Animácia computer.png (rozmery sprite-u: 80x48, trvanie snímku: 0.2)
Obr. 6: Animácia computer.png (rozmery sprite-u: 80x48, trvanie snímku: 0.2)

Úloha 2.2

V triede Computer vytvorte metódy pre vykonanie základných aritmetických operácií add() a sub() pre číselné údajové typy int a float.

Každá z týchto metód bude mať 2 parametre rovnakého typu pre operandy danej aritmetickej operácie. Keďže raz to budú parametre typu int, raz float, implementáciou takýchto metód nastane ich tzv. preťaženie.

Poznámka

To, ktorá konkrétna metóda z dvoch možných pre meno add (a taktiež sub) bude zavolaná, je určené staticky počas kompilácie na základe typov argumentov dodaných vo volaní metódy.

Implementáciu si môžete overiť po spustení hry pomocou nástroja Inšpektor.

Krok 3: Reactor (Over)Heating

Späť k reaktoru! Získať jeho stav už vieš, teraz sa pozrieme na to, ako jeho stav meniť.

Úloha 3.1

Vytvorte metódu increaseTemperature(), pomocou ktorej bude možné zvýšiť aktuálnu teplotu jadra reaktora.

Táto metóda nebude vracať žiadnu hodnotu a bude mať jeden celočíselný parameter (pomenovaný napr. increment), ktorý bude reprezentovať hodnotu, o ktorú sa má aktuálna teplota jadra zvýšiť.

Pri implementovaní metódy však zohľadnite nasledovné skutočnosti:

  • S nárastom teploty lineárne zvyšujte poškodenie reaktora. Reaktor sa začne kaziť po prekročení teploty 2000 stupňov a prestane byť funkčný pri dosiahnutí 6000 stupňov. Následné ochladenie reaktora ale nezníži úroveň poškodenia, ktoré už vysoká teplota spôsobila (to znamená, že po volaní tejto metódy nemôže byť hodnota poškodenia reaktora nižšia ako pred jej volaním). Pri výpočte úrovne poškodenia zaokrúhľujte desatinnú časť výsledku nadol.

    Závislosť zvyšovania poškodenia reaktora pri raste jeho teploty
    Obr. 7: Závislosť zvyšovania poškodenia reaktora pri raste jeho teploty

    Poznámka

    Znižovanie poškodenia reaktora bude možné jedine jeho opravou, ktorú budete riešiť nabudúce.

  • Ak teplota po zvýšení prekročí hodnotu 4000 stupňov, vzhľad reaktora bude od tohto momentu reprezentovaný nasledujúcou animáciou:

    Animácia reactor_hot.png (rozmery sprite-u: 80x80, trvanie snímku: 0.05)
    Obr. 8: Animácia reactor_hot.png (rozmery sprite-u: 80x80, trvanie snímku: 0.05)

  • Ak je poškodenie reaktora v intervale <33%, 66%>, teplota rastie 1.5-násobne; ak prekročí hodnotu 66%, teplota porastie dvojnásobne. V oboch prípadoch je zmena uvedená oproti pôvodnej hodnote parametra increment. Po výpočte novej hodnoty teploty výsledok zaokrúhlite vždy nahor na najbližšie celé číslo.

  • Ak teplota po zvýšení dosiahne (alebo prekročí) hodnotu 6000 stupňov, dôjde ku zničeniu reaktora (poškodenie reaktora dosiahne hodnotu 100%). Zničený reaktor bude reprezentovaný nasledujúcou animáciou:

    Animácia reactor_broken.png (rozmery sprite-u: 80x80, trvanie snímku: 0.1)
    Obr. 9: Animácia reactor_broken.png (rozmery sprite-u: 80x80, trvanie snímku: 0.1)

  • Ak poškodenie reaktora dosiahlo hodnotu 100%, akékoľvek ďalšie zvyšovanie teploty už nemá žiadny efekt.

Poznámka

Je výhodné vytvoriť objekty animácií už pri inicializovaní objektu a ich hodnoty uložiť do ďalších členských premenných. Keďže reaktor sa môže nachádzať v niekoľkých stavoch (v našom prípade v troch - stav ok, prehriaty a zničený reaktor), animácie reprezentujúce tento stav sa budú meniť. Ak sa v priebehu hry zmení stav reaktora napr. 50x, znamená to, že pri každej zmene potrebujete načítavať inú animáciu zvlášť - spolu teda 50 načítaní súborov animácií. Ak si však všetky animácie načítate pri inicializácii objektu, načítate spolu iba 3 súbory animácií, ktoré sa už budú používať podľa potreby.

Úloha 3.2

Vytvorte metódu decreaseTemperature(), pomocou ktorej bude možné znížiť aktuálnu teplotu jadra reaktora.

Táto metóda nebude vracať žiadnu hodnotu a bude mať jeden celočíselný parameter (pomenovaný napr decrement), ktorý bude reprezentovať hodnotu, o ktorú sa má aktuálna teplota jadra znížiť. Pozor, zníženie teploty neznižuje poškodenie, ktoré vysoká teplota už spôsobila!

Pri implementovaní metódy zohľadnite nasledovné skutočnosti:

  • Ak je poškodenie jadra aspoň 50%, reálne znižovanie teploty bude len polovičné oproti hodnote parametra decrement.
  • Ak je poškodenie jadra na úrovni 100%, znižovanie teploty už nemá žiaden efekt.
  • Ak teplota jadra pri ochladzovaní klesne na 4000 stupňov alebo menej, zmeňte animáciu reprezentujúcu stav reaktora na reactor_on.

Úloha 3.3

Vytvorte metódu updateAnimation(), ktorá na základe aktuálnej teploty nastaví animáciu reaktora.

Pri pozornom pohľade na metódy increaseTemperature() a decreaseTemperature() si všimnete, že obe metódy riešia nastavenie správnej animácie reaktora v závislosti na jeho teplote. Takáto duplicita je však zbytočná a komplikuje prípadnú zmenu funkcionality. Preto refaktorujte svoj kód tak, že túto spoločnú funkcionalitu oddelíte do samostatnej metódy updateAnimation().

Úloha 3.4

Upravte implementáciu metód increaseTemperature() a decreaseTemperature() tak, aby ste pre zmenu aktuálnej animácie využili práve vytvorenú metódu updateAnimation().

Po refaktorizácii z predchádzajúcej úlohy nezabudnite zmeniť aj pôvodné metódy, kde sa extrahovaný kód nachádzal, aby vyžívali novú metódu.

Poznámka

Metóda updateAnimation() reprezentuje len implementačný detail a nie je potrebné (ba dokonca ani žiadúce) aby bola dostupná používateľom objektu reaktora. Skryte preto túto metódu vhodným modifikátorom viditeľnosti.

Úloha 3.5

Pomocou Inšpektora umiestnite objekt reaktora do mapy a otestujte správnosť svojej implementácie.

Pokazený (prehriaty) reaktor
Obr. 10: Pokazený (prehriaty) reaktor

Krok 4: Repository

Úloha 4.1

Nahrajte (cez commit a push) váš zdrojový kód do repozitára na GitLab-e. Spravte tak do nasledujúceho cvičenia. Zároveň si pripravte otázky, ktoré by ste na cvičení chceli vyriešiť.

Zdrojový kód nahrajte aj v prípade, ak ste nestihli dokončiť všetky úlohy. Rozpracované časti, ktoré by mohli spôsobiť kompilačné chyby, odporúčame zakomentovať.

Doplňujúce úlohy

Úloha A.1

Ošetrite prípad, kedy sa pri volaní metód increaseTemperature() a decreaseTemperature() použije záporný parameter.

Ak k takémuto volaniu dôjde, nevykonajte žiadnu zmenu teploty.

Úloha A.2

Upravte reaktor tak, aby rýchlosť pulzovania jeho animácie závisela od úrovne poškodenia.

Pri vyššom poškodení reaktora by trvanie jedného snímku animácie malo byť kratšie, pre dosiahnutie efektu rýchlejšieho pulzovania reaktora.

Doplňujúce zdroje