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.

Upozornenie

Skôr než začnete prácu na tomto cvičení, aktualizujte si hernú knižnicu, ktorú v projekte používate, na verziu 2.4.1 podľa postupu na tejto stránke.

Ciele

  • Naučiť sa využívať návrhový vzor Abstract Factory
  • Porozumieť herným mapám vytváraným v editore Tiled
  • Využiť návrhový vzor Observer (publish/subscribe)
  • 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, actormi.

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ť actorov 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 actorov 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 hodnotu null.

Poznámka

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ý actor, 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 actorov je teda vytvoriť inštancie, ktoré sa majú použiť na reprezentáciu daného actora. Umiestnenie actorov na danú pozíciu definovanú v mape už automaticky zabezpečí scéna volaním metódy setPosition() nad získaným actorom.

Úloha 1.6

Nastavte inštanciu triedy MissionImpossible.Factory ako továreň na actorov 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.

Úloha 1.7

V scenári zabezpečte nastavenie ovládania Ripleyovej pomocou ovládačov MovableController a CollectorController, 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

Pre redukciu duplikovania kódu potrebného na zobrazenie stavu (energie) Ripleyovej vytvorte v triede Ripley metódu showRipleyState() v ktorej vykreslenie potrebného textu zabezpečíte. Túto metódu potom zavolajte v metóde sceneUpdating() v scenári, alebo naplánujte na opakované volanie pomocou akcií.

Poznámka

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

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.

Zistiť, či je objekt typu Actor v kolízii so stenou, môžete pomocou volania metódy intersectsWithWall() nad objektom mapy. Nášmu taktickému tímu sa ako najvhodnejšie javí implementovať túto funkcionalitu do triedy akcie, ktorá sa stará o pohyb actor-ov.

Poznámka

Objekt mapy získate zo scény pomocou metódy getMap().

Poznámka

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.

Ak je vaša implementácia správna, od tohto momentu už Ripleyová nebude cez steny prechádzať.

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 actor-ov 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 (actor-a)
  • void close() - zatvorenie predmetu (actor-a)
  • boolean isOpen() - vráti true, ak je predmet (actor) otvorený, v opačnom prípade vráti false

Úloha 2.2

V balíku pre otvárateľných actorov 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.

Trieda Door bude implementovať rozhranie Openable a takisto Usable. Dvere budú použiteľné s akýmkoľvek actorom, 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.png.

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 actorov dvere otvorte, ak sú zavreté a zatvorte ich, ak už sú otvorené.

Poznámka

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ť.

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() 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. actorov. 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 actora "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 actora 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, ktoré sme spomínali už pri akcii Take, 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 actora, s ktorým daný použiteľný predmet dokáže pracovať. Táto reprezentácia triedy sa využije pri hľadaní kompatibilného actora, 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 actorovi, ktorý dosadzuje konkrétny typ kompatibilného actora do typového argumentu pre Usable, vráťte z tejto metódy referenciu na runtime reprezentáciu triedy kompatibilného actora. 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 scheduleOnIntersectingWith(Actor mediatingActor), pomocou ktorej bude možné naplánovať Use akciu na actorovi, ktorý je kompatibilný s Usable predmetom odovzdaným konštruktoru akcie a nachádza sa v kolízii so sprostredkujúcim actorom 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 actora na scéne v kolízií so sprostredkujúcim actorom (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 actora odovzdaného konštruktoru akcie a T reprezentuje typový parameter akcie, je možné túto metódu implementovať s využitím stream API nasledovne:

public Disposable scheduleOnIntersectingWith(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 actorov na scene
        .filter(mediatingActor::intersects)  // vyfiltrujeme actorov, ktori su v kolizii so sprostredkovatelom
        .filter(usingActorClass::isInstance) // vyfiltrujeme actorov kompatibilneho typu
        .map(usingActorClass::cast)  // vykoname pretypovanie streamu actorov
        .findFirst()  // vyberieme prveho (ak taky existuje) actora zo streamu
        .map(this::scheduleOn)  // zavolame metodu `scheduleOn` s najdenym actorom a vratime `Disposable` objekt
        .orElse(null);  // v pripade, ze ziaden vyhovujuci actor nebol najdeny, vratime `null`
}

Ekvivalentná implementácia "klasickým" spôsobom by vyzerala nasledovne:

public Disposable scheduleOnIntersectingWith(Actor mediatingActor) {
    Scene scene = mediatingActor.getScene();
    if (scene == null) return null;
    Class<T> usingActorClass = usable.getUsingActorClass();

    for (Actor actor : scene) {
        if (mediatingActor.intersects(actor) && usingActorClass.isInstance(actor)) {
            return this.scheduleOn(usingActorClass.cast(actor));
        }
    }
    return null;
}

Úloha 2.7

Do ovládača CollectorController 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 actorom. Ovládaný actor bude následne vystupovať v úlohe sprostredkovateľa pre nájdenie kompatibilného predmetu pomocou pridanej metódy scheduleOnIntersectingWith().

Poznámka

Pri riešení úlohy budete v ovládači potrebovať vykonať pretypovanie všeobecného actora na Usable actora, no nebudete mať informáciu o tom, ktorý konkrétny podtyp actora je typovým argumentom nájdeného použiteľného actora. 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 actorov 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.png.

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

Úloha 2.11

Pridajte do ovládača CollectorController 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 actora ako sprostredkovateľa na nájdenie kompatibilného actora 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ť actorov 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, tak, aby toto rozhranie 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.png.

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.png. 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í actori, 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 má metódu subscribe(), 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 lambda (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.

Ú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.png 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 CollectorController.

Volanie metód registerListener() nad objektmi Input vracia referenciu na Disposable objekt, ktorým môžete zrušiť registrovaný listener (obdoba Disposable objektov vrátených z metódy scheduleOn na rušenie akcií). Upravte teda 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 po oprave ventilátora došlo k odvetraniu kontaminovaného priestoru a znižovanie energie Ripleyovej sa zastavilo.

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

Pr 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