9. týždeň

Mission Impossible

Motivácia

Máme problém! Na 347. poschodí došlo k úniku vysoko toxického materiálu do ovzdušia! Bezpečnostný systém automaticky uzamkol kontaminované miestnosti a pracovníci, ktorí neboli kontaminovaní, boli evakuovaní do bezpečia. Zlyhal však odsávací systém, ktorý mal zabezpečiť automatické odsatie kontaminovaného vzduchu. Keďže však máte bohaté skúsenosti s opravovaním predmetov kladivom, boli ste vybratí, aby ste so svojou Ripleyovou pokazený ventilátor opäť uviedli do chodu.

Taktický tím pre vás pripravil plán misie, ktorého striktné dodržanie musí bezpodmienečne viesť k jej úspešnému splneniu.

Z operačného strediska zdraví Manager.

Ciele

  1. Porozumieť herným mapám vytváraným v editore Tiled .
  2. Naučiť sa využívať návrhový vzor Abstract Factory .
  3. Využiť návrhový vzor Observer (publish/subscribe).
  4. Oboznámiť sa s runtime reprezentáciou tried.
  5. Využiť statické členy triedy.

Postup

Krok 1: Welcome to the Real World!

Primárnym cieľom tejto misie je naučiť Ripleyovú pohybovať sa v novom prostredí. Na dosiahnutie tohto cieľa potrebujete pri vytváraní hernej scény načítať mapu reprezentujúcu prostredie Ripleyovej, spolu s okolitými, viac či menej živými, aktérmi.

Aby to bolo možné, potrebujete najskôr vytvoriť factory triedu, pomocou ktorej bude vedieť vaša simulácia nakonfigurovať patričnú situáciu a rozmiestniť aktérov do ich počiatočných pozícií podľa údajov zaznačených v mape. A keďže mapa môže obsahovať aj steny, bude potrebné naučiť Ripleyovú ich rozoznávať a neprechádzať skrze ne.

Úloha 1.1

Stiahnite si balíček s mapou pre dnešnú misiu a rozbaľte ho do adresára src/main/resources/maps/ v projekte.

Výsledná štruktúra súborov v adresári src/main/resources/maps by mala byť nasledovná:

src/main/resources/maps/
├── tilesets/
│  └── tileset01.png
└── mission-impossible.tmx

Úloha 1.2

V main() metóde projektu upravte volanie konštruktora triedy World tak, že k názvu scény pridáte druhý argument v podobe cesty k .tmx súboru s mapou, ktorá sa má do scény načítať.

Cestu k mape zapíšete ako maps/mission-impossible.tmx.

Úloha 1.3

V balíku pre scenáre vytvorte novú triedu pre scenár pomenovanú podľa tejto misie: MissionImpossible.

Trieda musí, samozrejme, implementovať rozhranie SceneListener a scenár budete neskôr písať do metódy sceneInitialized().

Úloha 1.4

V triede scenára MissionImpossible vytvorte vnorenú verejnú statickú triedu s názvom Factory, ktorá implementuje rozhranie ActorFactory.

Táto trieda bude reprezentovať návrhový vzor továrenská metóda (Factory Method) a trieda samotná bude obsahovať len jednu metódu create(String type, String name).

Úloha 1.5

Implementujte metódu create() v triede MissionImpossible.Factory tak, aby vytvárala inštancie aktérov potrebných pre dnešnú misiu.

Herná mapa dnešnej misie obsahuje objekty s menami access card, door, ellen, energy, locker, ventilator. Zatiaľ viete poskytnúť inštancie pre Ripleyovú (ellen) a lekárničku (energy). Pre iné mená z metódy create() vráťte zatiaľ hodnotu null.

Gamelib

Metóda create() je volaná hernou knižnicou pri spracovávaní mapy. Mapy sa vytvárajú pomocou editora Tiled. Sú to v podstate XML súbory, ktoré vo viacerých vrstvách definujú vzhľad herného sveta. Každý aktér, ktorého mapa definuje, má typ a meno (odtiaľ parametre metódy create() v rozhraní ActorFactory), ako aj svoju počiatočnú polohu. Úlohou vytváranej továrne na aktérov je teda vytvoriť inštancie, ktoré sa majú použiť na reprezentáciu daného aktéra. Umiestnenie aktérov na danú pozíciu definovanú v mape už automaticky zabezpečí scéna volaním metódy setPosition() nad získaným aktérom.

Úloha 1.6

Nastavte inštanciu triedy MissionImpossible.Factory ako továreň na aktérov pre scénu.

Vytvorenú inštanciu triedy MissionImpossible.Factory prepojte s inštanciou scény tak, že ju v metóde main() odovzdajte ako tretí argument konštruktora triedy World:

Scene missionImpossible = new World("mission-impossible", "maps/mission-impossible.tmx", new MissionImpossible.Factory());

Úloha 1.7

V scenári zabezpečte nastavenie ovládania Ripleyovej pomocou ovládačov MovableController a KeeperController, zobrazenie jej batohu a stavu energie.

Keďže je teraz Ripleyová vytváraná cez továreň na základe jej umiestnenia v mape scény, inštanciu Ripleyovej už v scenári nevytvárajte, ale získajte už existujúcu inštanciu zo scény.

Poznámka

Aby ste nemuseli duplikovať kód potrebný na zobrazenie stavu (energie) Ripleyovej do každého scenára, vytvorte v triede Ripley metódu showRipleyState() v ktorej zabezpečíte vykreslenie potrebného textu. Túto metódu potom zavolajte v metóde sceneUpdating() v scenári.

Gamelib

Ak chcete, aby scéna sledovala pohyb Ripleyovej, využite na to metódu scene.follow(Actor actor).

Poznámka

Nezabudnite pridať novú triedu pre dnešný scenár ako listener na scénu v metóde main().

Úloha 1.8

Overte správnosť implementácie.

Ak ste postupovali správne, po spustení uvidíte scénu s mapou, na ktorej sa už bude nachádzať lekárnička a Ripleyová. Ripleyovú by ste mali vedieť ovládať pomocou klávesnice, a zatiaľ by mala zvládnuť prechádzať aj cez steny.

Ripleyová nachádzajúca sa v reálnom svete zatiaľ prechádza aj cez steny.
Obr. 1: Ripleyová nachádzajúca sa v reálnom svete zatiaľ prechádza aj cez steny.

Úloha 1.9

Zabezpečte, aby Ripleyová neprechádzala cez steny.

Nášmu taktickému tímu sa ako najvhodnejšie javí implementovať túto funkcionalitu do triedy akcie, ktorá sa stará o pohyb aktérov.

Gamelib

Zistiť, či je objekt typu Actor v kolízii so stenou, môžete pomocou volania metódy intersectsWithWall(Actor actor) nad objektom mapy. Objekt mapy získate zo scény pomocou metódy getMap().

V mape sú steny definované v rámci vrstvy s názvom walls. Táto vrstva nie je v rámci scény graficky vykreslená, avšak informácie z tejto vrstvy (vyplnené, respektíve prázdne dlaždice) sú použité na vytvorenie stien v miestach vyplnených dlaždíc.

Mapa scény so zobrazenou vrstvou walls v editore Tiled.
Obr. 2: Mapa scény so zobrazenou vrstvou walls v editore Tiled.

Krok 2: Alohomora!

Pri implementácii triedy MissionImpossible.Factory ste si určite všimli, že ste nemali k dispozícii všetkých potrebných aktérov definovaných v mape. V rámci plnenia tohto kroku misie niekoľko chýbajúcich predmetov vytvoríte. Konkrétne budete vytvárať dvere a prístupovú kartu, pomocou ktorej bude možné zamknuté dvere odomknúť.

Pri plnení tohto kroku misie bude potrebné aj rozšíriť možnosti použiteľných predmetov, aby ste ich mohli používať interaktívne - pomocou klávesnice.

Dvere, zamknuté dvere a prístupová karta na odomykanie.
Obr. 3: Dvere, zamknuté dvere a prístupová karta na odomykanie.

Úloha 2.1

V novom balíku sk.tuke.kpi.oop.game.openables vytvorte rozhranie Openable, ktoré bude reprezentovať otvárateľné objekty.

Rozhranie bude rozširovať rozhranie Actor a bude mať nasledovné metódy:

  • void open() - otvorenie predmetu (aktéra)
  • void close() - zatvorenie predmetu (aktéra)
  • boolean isOpen() - vráti true, ak je predmet (aktér) otvorený, v opačnom prípade vráti false

Úloha 2.2

V balíku pre otvárateľných aktérov vytvorte podľa vyššie uvedeného diagramu triedu Door reprezentujúcu bežné dvere v hre a implementujte v nej metódy z rozhraní Openable a Usable.

Dvere budú použiteľné s akýmkoľvek aktérom, preto ako typový argument typu Usable použite typ Actor. Pre aktuálnu misiu budeme potrebovať vertikálne otočené dvere, preto ako animáciu použite obrázok vdoor.

Animácia vertikálnych dverí vdoor.png (rozmery spritu: 16x32, trvanie snímku: 0.1)
Obr. 4: Animácia vertikálnych dverí vdoor.png (rozmery spritu: 16x32, trvanie snímku: 0.1)

Vo východzom stave budú dvere zavreté. Pri použití dverí niektorým z aktérov dvere otvorte, ak sú zavreté, a zatvorte ich, ak už sú otvorené.

Gamelib

Pri animovaní otvárania a zatvárania dverí viete využiť režimy prehrávania animácie ONCE a ONCE_REVERSED, ktoré viete nastavovať metódou setPlayMode() na existujúcej animácii, spolu s jej metódami play() a stop().

Úloha 2.3

Doplňte implementáciu dverí tak, aby cez ne nebolo možné prejsť, keď sú zavreté.

Nášmu taktickému a strategickému tímu sa ako najlepší spôsob znepriechodnenia zatvorených dverí javí vytvoriť na ich mieste v mape stenu. Pri ich otvorení je zase potrebné stenu zrušiť.

Gamelib

Všetky steny sú v rámci hernej mapy definované vo vrstve walls, ktorú ste videli už na obrázku vyššie. Pri práci s editorom Tiled sú mapy vytvárané pomocou dlaždíc, ktoré sú v našom prípade veľké 16x16 pixelov. Každá vyplnená dlaždica vo vrstve walls reprezentuje stenu.

Aj v rámci objektovej reprezentácie mapy máme k dispozícii dlaždice. Každá dlaždica pokrývajúca mapu má svoju x-ovú a y-ovú súradnicu, ktorá reprezentuje poradie dlaždice v smere osi x a y, pričom počiatok súradnicového systému je v ľavom dolnom rohu mapy. Dlaždica na tomto umiestnení má súradnice [0, 0].

Dlaždice mapy s vyznačenou dlaždicou so súradnicami mriežky [3, 5]
Obr. 5: Dlaždice mapy s vyznačenou dlaždicou so súradnicami mriežky [3, 5]

Konkrétnu dlaždicu viete získať pomocou metódy getTile(int tileX, int tileY) nad objektom mapy. Táto dlaždica je typu MapTile a okrem jej pozície a rozmerov z nej viete metódou isWall() zistiť, či daná pozícia v mape je považovaná za stenu. Konečne, metódou setType() viete typ dlaždice zmeniť na jednu z dvoch hodnôt enumerácie MapTile.Type:

  • CLEAR - miesto, kde sa dlaždica tohto typu nachádza, je priechodzie,
  • WALL - miesto, kde sa dlaždica tohto typu nachádza, je považované za stenu.

Poznámka

Uvedomte si, že jednotky súradníc dlaždíc sa nezhodujú s jednotkami súradníc napr. aktérov. Preto budete potrebovať robiť prepočet z pozícií definovaných v pixeloch do pozícií definovaných v indexe dlaždice v mriežke. Pri výpočte vychádzajte z veľkosti dlaždice.

Poznámka

Nezabudnite na to, že dvere svojou plochou pokrývajú dve dlaždice. Obom je potrebné meniť stav pri otváraní a zatváraní dverí.

Úloha 2.4

Upravte create() metódu v MissionImpossible.Factory tak, aby pre meno aktéra "door" vrátila inštanciu triedy Door a overte svoju implementáciu.

V scéne by mali pribudnúť dvere a Ripleyová by sa nemala cez ne vedieť dostať do menšej miestnosti.

Ripleyová stojí pred dverami, ktoré ešte nedokáže otvoriť.
Obr. 6: Ripleyová stojí pred dverami, ktoré ešte nedokáže otvoriť.

Úloha 2.5

Do rozhrania Usable a do každého konkrétneho aktéra implementujúceho toto rozhranie pridajte metódu getUsingActorClass().

V nasledujúcej úlohe budete pridávať podporu pre plánovanie akcie Use pomocou klávesnice. Scenár použitia je napríklad takýto:

  1. Ripleyová príde k dverám, ktoré implementujú rozhranie Usable.
  2. Stlačením priradenej klávesy hráč signalizuje zámer otvoriť dvere.
  3. Akcia Use je naplánovaná tak, aby sa dvere použili s Ripleyovou.

Iný scenár použitia, ktorý budete implementovať neskôr, bude nasledovný:

  1. Ripleyová s prístupovou kartou v batohu príde k uzamknutým dverám.
  2. Stlačením priradenej klávesy hráč signalizuje zámer použiť predmet na vrchu batoha.
  3. Akcia Use je naplánovaná tak, aby sa prístupová karta (predmet z batoha) použila so zamknutými dverami (predmet v scéne v kolízii s Ripleyovou).

Poznámka

Ako je viditeľné predovšetkým v druhom prezentovanom scenári použitia, Ripleyová tu účinkuje ako sprostredkovateľ použitia predmetu, ktorý sa nachádza v jej blízkosti.

Vzhľadom na obmedzenie JVM platformy nie je možné počas behu programu pracovať priamo s typmi reprezentovanými typovými parametrami (tie existujú len počas kompilácie). Preto pridávanou metódou getUsingActorClass() budeme pre každý použiteľný predmet explicitne vracať runtime reprezentáciu triedy aktéra, s ktorým daný použiteľný predmet dokáže pracovať. Táto reprezentácia triedy sa využije v ďalšej úlohe pri hľadaní kompatibilného aktéra (aktéra, ktorý je inštanciou danej triedy), s ktorým môže byť akcia Use naplánovaná.

Nová metóda v rozhraní Usable nech má nasledujúcu signatúru:

Class<T> getUsingActorClass();  // T reprezentuje typovy parameter v triede Usable

V každom použiteľnom aktérovi, ktorý dosadzuje konkrétny typ kompatibilného aktéra do typového argumentu pre Usable, vráťte z tejto metódy referenciu na runtime reprezentáciu triedy kompatibilného aktéra. V prípade kladiva (triedy Hammer), ktoré je použiteľné s reaktorom, bude implementácia tejto metódy teda vyzerať nasledovne:

public Class<Reactor> getUsingActorClass() {
    return Reactor.class;
}

Úloha 2.6

V akcii Use pridajte metódu scheduleForIntersectingWith(Actor mediatingActor), pomocou ktorej bude možné naplánovať Use akciu na aktérovi, ktorý je kompatibilný s Usable predmetom odovzdaným konštruktoru akcie a nachádza sa v kolízii so sprostredkujúcim aktérom reprezentovaným parametrom mediatingActor.

Táto metóda je potrebná v triede akcie Use kvôli tomu, lebo potrebujeme v kontexte, v ktorom typový systém jazyka má k dispozícii typový argument Usable predmetu, nájsť prvého kompatibilného aktéra na scéne v kolízií so sprostredkujúcim aktérom (predovšetkým Ripleyovou). To nám v ďalšej úlohe umožní interaktívne plánovať akciu Use ako reakciu na stlačenie klávesy.

Za predpokladu, že máte funkčnú implementáciu akcie Use, kde členská premenná usable reprezentuje použiteľného aktéra odovzdaného konštruktoru akcie a T reprezentuje typový parameter akcie, je možné túto metódu implementovať nasledovne:

public Disposable scheduleForIntersectingWith(Actor mediatingActor) {
    Scene scene = mediatingActor.getScene();
    if (scene == null) return null;
    Class<T> usingActorClass = usable.getUsingActorClass();  // `usable` je spominana clenska premenna

    for (Actor actor : scene) {
        if (mediatingActor.intersects(actor) && usingActorClass.isInstance(actor)) {
            return this.scheduleFor(usingActorClass.cast(actor));  // naplanovanie akcie v pripade, ze sa nasiel vhodny akter
        }
    }
    return null;
}

Poznámka

Ak ste sa už oboznámili so stream API pre prácu s kolekciami a chceli by ste ho využiť v tomto prípade, ekvivalentná implementácia tejto metódy môže byť nasledovná:

public Disposable scheduleForIntersectingWith(Actor mediatingActor) {
    Scene scene = mediatingActor.getScene();
    if (scene == null) return null;
    Class<T> usingActorClass = usable.getUsingActorClass();  // `usable` je spominana clenska premenna
    return scene.getActors().stream()  // ziskame stream aktérov na scene
        .filter(mediatingActor::intersects)  // vyfiltrujeme akterov, ktori su v kolizii so sprostredkovatelom
        .filter(usingActorClass::isInstance) // vyfiltrujeme akterov kompatibilneho typu
        .map(usingActorClass::cast)  // vykoname pretypovanie streamu akterov
        .findFirst()  // vyberieme prveho (ak taky existuje) aktera zo streamu
        .map(this::scheduleFor)  // zavolame metodu `scheduleFor` s najdenym akterom a vratime `Disposable` objekt
        .orElse(null);  // v pripade, ze ziaden vyhovujuci akter nebol najdeny, vratime `null`
}

Úloha 2.7

Do ovládača KeeperController pridajte plánovanie akcie Use pre predmety na scéne.

Pri stlačení klávesy U naplánujte použitie prvého nájdeného použiteľného predmetu v kolízii s ovládaným aktérom. Ovládaný aktér bude následne vystupovať v úlohe sprostredkovateľa pre nájdenie kompatibilného predmetu pomocou pridanej metódy scheduleForIntersectingWith().

Poznámka

Pri riešení úlohy budete v ovládači potrebovať vykonať pretypovanie všeobecného aktéra na Usable aktéra, no nebudete mať informáciu o tom, ktorý konkrétny podtyp aktéra je typovým argumentom nájdeného použiteľného aktéra. Použite teda zástupný symbol ? namiesto konkrétneho typového argumentu. Pretypovanie môže vyzerať takto:

(Usable<?>) actor

Úloha 2.8

Overte správnosť svojej implementácie.

Ak postupujete správne, Ripleyová by mala vedieť otvoriť dvere, ku ktorým sa priblíži, stlačením klávesy U.

Úloha 2.9

Podľa diagramu tried na začiatku kroku vytvorte v balíku pre otvárateľných aktérov triedu LockedDoor, ktorá bude dediť od triedy Door a bude reprezentovať uzamknuté dvere.

Odstrániť bezpečnostný zámok však bude možné len použitím správnej prístupovej karty, čo budete implementovať v ďalšej úlohe. Po odomknutí dverí ich už bude možné kedykoľvek otvoriť a zatvoriť, tak ako bežné dvere.

V triede implementujte metódy:

  • void lock() - zamknutie (a zároveň zatvorenie) dverí,
  • void unlock() - odomknutie (a zároveň otvorenie) dverí,
  • boolean isLocked() - vráti aktuálny stav dverí - či sú zamknuté alebo nie.

Úloha 2.10

Podľa diagramu tried na začiatku kroku vytvorte v balíku sk.tuke.kpi.oop.game.items triedu reprezentujúcu prístupovú kartu AccessCard.

Táto karta bude implementovať rozhrania Collectible a Usable a bude použiteľná práve s uzamknutými dverami LockedDoor. Použitím karty sa uzamknuté dvere odomknú.

Animácia reprezentujúca prístupovú kartu je uložená v súbore key.

Animácia key.png (rozmery spritu: 16x16)
Obr. 7: Animácia key.png (rozmery spritu: 16x16)

Úloha 2.11

Pridajte do ovládača KeeperController naplánovanie akcie Use pre predmety na vrchu batoha.

Pri stlačení klávesy B naplánujte akciu Use na predmete na vrchu batoha v prípade, že je tento predmet použiteľný. Opäť využite ovládaného aktéra ako sprostredkovateľa na nájdenie kompatibilného aktéra v jeho blízkosti na scéne.

Úloha 2.12

Upravte create() metódu v MissionImpossible.Factory tak, aby pre meno "door" vytvárala inštanciu LockedDoor namiesto Door, pre meno "access card" zas inštanciu AccessCard a overte svoju implementáciu.

Overte, že použitím klávesy U už nie je možné otvoriť zamknuté dvere a že tieto sa otvoria, ak si Ripleyová vezme do batohu prístupovú kartu a použijete klávesu B po priblížení sa k dverám.

Ripleyová s prístupovou kartou v batohu prechádza odomknutými dverami.
Obr. 8: Ripleyová s prístupovou kartou v batohu prechádza odomknutými dverami.

Krok 3: Missing Items

Pre úplnosť aktérov definovaných v mape ešte chýba skrinka a ventilátor. Tie vytvoríte v tomto kroku. Takisto budeme potrebovať rozšíriť opravovacie schopnosti kladiva (schovávajúceho sa v skrinke) na akékoľvek opraviteľné predmety.

Diagram tried znázorňujúci postavenie triedy Locker a Ventilator.
Obr. 9: Diagram tried znázorňujúci postavenie triedy Locker a Ventilator.

Úloha 3.1

Upravte kladivo tak, aby vedelo opravovať ľubovoľný Repairable predmet a nie iba Reactor.

Nezabudnite zmeniť aj triedu získavanú metódou getUsingActorClass(). Rovnako ako triedu Reactor.class môžete vrátiť aj runtime reprezentáciou rozhrania Repairable ako Repairable.class.

Poznámka

Keďže typovým argumentom rozhrania Usable môže byť len podtyp Actor-a, zmena je potrebná aj v rozhraní Repairable: toto rozhranie upravte tak, aby rozširovalo rozhranie Actor.

Úloha 3.2

Podľa uvedeného diagramu tried vytvorte v balíku sk.tuke.kpi.oop.game triedu Locker, ktorá bude reprezentovať skrinku, po otvorení (použití) ktorej hráč nájde kladivo.

Nájdenie kladiva realizujte tým, že kladivo zo skrinky vypadne, akonáhle Ripleyová skrinku preskúma. Skrinka nech je použiteľná len raz, takže Ripleyová preskúmaním jednej skrinky získa len jedno kladivo.

Animácia reprezentujúca skrinku je uložená v súbore locker.

Animácia locker.png (rozmery spritu: 16x16)
Obr. 10: Animácia locker.png (rozmery spritu: 16x16)

Úloha 3.3

Podľa uvedeného diagramu vytvorte v balíku sk.tuke.kpi.oop.game aj triedu Ventilator reprezentujúcu ventilátor, ktorý bude musieť Ripleyová opraviť kladivom.

Animácia reprezentujúca ventilátor je uložená v súbore ventilator. Keďže ventilátor začne v scéne vystupovať ako pokazený, zastavte spočiatku prehrávanie jeho animácie. V metóde repair() zas reprezentujte opravenie ventilátora spustením prehrávania animácie.

Animácia ventilator.png (rozmery spritu: 32x32, trvanie snímku: 0.1)
Obr. 11: Animácia ventilator.png (rozmery spritu: 32x32, trvanie snímku: 0.1)

Úloha 3.4

Dokončite implementáciu MissionImpossible.Factory pre vytváranie objektov na základe mien "ventilator" a "locker".

Krok 4: Entering Contaminated Zone

Vstupujeme do poslednej fázy dnešnej misie. Cieľom je opraviť pokazený vetrák v zamorenej oblasti, čím nebezpečenstvo zamorenia pominie. Pri plnení tohto kroku si ukážeme ako pomocou správ vieme reagovať na vzniknuté udalosti, pričom využijeme návrhový vzor Observer.

Úloha 4.1

Pridajte do triedy Door témy správ o ich otvorení, respektíve zatvorení.

Rôzne časti hry (iní aktéri, váš kód v rámci scenára, ...) môžu chcieť reagovať na to, ak sa nejaké dvere otvoria alebo zatvoria. To umožníme vytvorením témy správ s definovaným typom obsahu (typom objektu, ktorý sa bude v danej téme posielať) a publikovaním správy (konkrétneho objektu daného typu) do zbernice správ na scéne. V prípade dverí bude typom správy samozrejme typ Door.

Témy správ vytvoríte pomocou factory metódy Topic.create(), ktorej prvý argument je názov témy a druhý typ objektu posielaný v správach.

Keďže k témam budeme chcieť pristupovať bez potreby mať referenciu na konkrétne dvere, implementujte ich ako statické členské premenné triedy Door. Téma pre otvorenie dverí môže vyzerať takto:

public static final Topic<Door> DOOR_OPENED = Topic.create("door opened", Door.class);

Analogicky vytvoríte aj tému pre zatvorenie dverí.

Úloha 4.2

Publikujte správy o otvorení, respektíve zatvorení dverí.

V metódach open() a close() dverí publikujte správu o tom, ktoré dvere boli práve otvorené, respektíve zatvorené. Využite metódu publish() na objekte zbernice správ scény MessageBus, ktorý zo scény získate metódou getMessageBus().

Úloha 4.3

V scenári naplánujte akciu, v rámci ktorej bude dochádzať k postupnému znižovaniu energie Ripleyovej v dôsledku rozširovania kontaminácie po otvorení dverí.

Zbernica správ MessageBus má metódu subscribe(Topic<M> topic, Consumer<M> listener), ktorou sa vieme prihlásiť k "odberu" správ publikovaných v rámci určitej témy. Prvým argumentom je objekt témy správ a druhým je lambda výraz (resp. objekt typu funkcionálneho rozhrania Consumer) s jedným parametrom - objektom odoslaným v rámci konkrétnej správy.

Prihláste sa k téme správ Door.DOOR_OPENED a zareagujte na otvorenie dverí naplánovaním patričnej akcie na znižovanie energie Ripleyovej.

Poznámka

Odoberanie energie riešte vo vhodnom intervale, aby neprebiehalo príliš rýchlo a Ripleyová mala ešte čas na dokončenie misie.

Úloha 4.4

Overte svoju implementáciu.

Po otvorení dverí by mala začať Ripleyovej klesať energia.

Ripleyová v zamorenom priestore.
Obr. 12: Ripleyová v zamorenom priestore.

Úloha 4.5

Upravte triedu Ripley tak, aby sa po dosiahnutí energie 0 jej animácia zmenila na player_die a publikujte správu o jej smrti.

Pre animáciu použite režim prehrávania Animation.PlayMode.ONCE. Vytváranú tému správ pomenujte RIPLEY_DIED.

Animácia umierajúcej Ripleyovej 'player_die.png' (rozmery spritu: 32x32, trvanie snímku: 0.1)
Obr. 13: Animácia umierajúcej Ripleyovej 'player_die.png' (rozmery spritu: 32x32, trvanie snímku: 0.1)

Úloha 4.6

Zabezpečte, aby Ripleyová nebola po smrti ovládateľná ovládačmi MovableController a KeeperController.

Gamelib

Volanie metódy registerListener() nad objektom Input vracia referenciu na Disposable objekt, ktorým môžete zrušiť registrovaný listener (obdoba Disposable objektov vrátených z metódy scheduleFor na rušenie akcií).

Upravte implementáciu scenára tak, aby ste po prijatí správy o smrti Ripleyovej zrušili listenery pre spomínané ovládače a znemožnili tak ďalšie ovládanie Ripleyovej.

Úloha 4.7

Zabezpečte, aby sa po oprave ventilátora (pri čom dôjde k "odvetraniu" kontaminovaného priestoru) zastavilo znižovanie energie Ripleyovej.

Túto úlohu tiež riešte vytvorením témy správ pre opravenie ventilátora (pomenovanej VENTILATOR_REPAIRED). V reakcii na túto správu v scenári zastavte naplánované znižovanie energie Ripleyovej.

Poznámka

Pri riešení myslite na to, že premenné definované v rámci lambdy nie sú viditeľné mimo lambdy a do lokálnych premenných definovaných mimo lambdy nemôžete priraďovať v rámci lambdy iné objekty. Lokálne premenné zachytené v lambde sú totiž efektívne finálne.

Úloha 4.8

Overte implementáciu celej misie.

Po oprave ventilátora kladivom by Ripleyovej mala prestať klesať energia. Ak opravu nestihne do času, keď jej energia klesne na 0, nemali by ste vedieť Ripleyovú už ovládať.

Doplňujúce zdroje