Cvičenie 4
Cieľom dnešného cvičenia bude precvičiť si dedenie, abstraktné triedy a ukážeme si ďalší návrhový vzor - Command a tiež sa pohráme s užívateľským vstupom.
4.1 Observer - part 2
Na minulom cvičení sme si ukázali jednoduchú implementáciu observera. Malo to však zásadnú chybu - nedokázal zistiť, v akom stave aktuálne IObservable je.
Toto vieme vyriešiť niekoľkými spôsobmi. Jedno riešenie (bez použitia typových parametrov) si uvedieme:
Pridajte do konštruktora pre Crystal referenciu na PowerSource ku ktorému bude pripojený. Následne z tohoto konštruktora viete priamo dať subscribe a keďže máte referenciu, viete sa aj spýtať na stav.
Ďalší krok (aj keď teraz nie úplne nutný, keďže PowerSource nadobúda iba 2 stavy) je získať aktuálny stav - upravte teda IObserver:
void Notify(IObservable)V Crystal upravte zodpovedajúcu metódu aby ste získali potrebné hodnoty.
4.2 Abstract thinking
Naposledy sme si vytvorili 2 triedy, ktoré mali časť funkcionality rovnakú, dnes sa pozrieme ako by sme mohli túto časť dať dokopy.
Vytvorte si triedu AbstractSwitchable nech táto rozširuje AbstractActor a implementuje ISwitchable.
Metódy Toggle(), IsOn(), TurnOn(), TurnOff() v Crystal majú na prvý pohľad veľmi podobnú implementáciu ako aj v PowerSource. Rozdiel je len v UpdateAnimation() na ktorý sa odkazujú.
Z Crystal presuňte Toggle, IsOn, TurnOn, TurnOff do AbstractSwitchable (samozrejme to vymažte z PowerSource). Chýbajúcu UpdateAnimation() iba deklarujte ako abstract a ponechajte implementácie, ktoré už máte v Crystal a PowerSource - toto zabezpečí, že budú použité zodpovedajúce implementácie, ale nemusíte zbytočne opakovať kód, ktorý je spoločný. Nastala chyba... C# neumožňuje prekrývať ľubovoľné metódy, iba tie, ktoré sú na to označené - keyword virtual. Doplňte ho tam.
private virtual void UpdateAnimation()_UpdateAnimation() nepotrebujete volať z iných tried, teda by mala byť private - keďže ale ju chceme prekrývať, tak musí byť minimálne dostupná v triedach potomkov, ale nechceme aby bola verejná. Tu sa nám hodí použiť tretí modifikátor prístupu - protected (dostupné iba vrámci triedy a jej potomkov).
4.3 base class
Vytvorte si triedu CrackedCrystal - bude sa jednať o kryštál, ktorý je poškodený a dá sa rozsvietiť len x-krát. Ako predka(rodiča) zoberte Crystal.
Prekryte TurnOn() (nezabudnite v AbstractSwitchable upraviť deklaráciu aby obsahovala virtual)
Teraz už je možné túto metódu prekryť, tak v CrackedCrystal upravte implementáciu tak, aby sa len x-krát zapol, potom sa už nič nestane.
Prekrytím metódy je schovaný pôvodný kód, ale nie je úplne stratený - pomocou base sa k nemu vieme dostať:
base.TurnOn();Pridajte aj nový konštruktor s ďalším parametrom - počtom zapnutí (int). Využite kód, ktorý ste už implementovali v Crystal. Konštruktory je možné vzájomne volať pomocou this (vrámci jednej triedy) a base pre konštruktor rodičovskej triedy:
public class DerivedClass : BaseClass
{
    public DerivedClass() : base()
    {
        //magic happens here
    }
    public DerivedClass(string s) : base()
    {
        //some more magic happens here
    }
    public DerivedClass(int x) : this()
    {
        //and magic again
    }
}4.4 Cometh forth mine own apprentice
Nadišiel čas, vševediaci Merlin volá svojho učňa. Predstúp!
Vytvorte si triedu Player (rozširuje AbstractActor), ako animáciu použite player.png - v prípade, že sa hráč otočí, použite Animation.FlipAnimation() a keď sa nehýbe Animation.Stop()
4.5 Didst thee forget how to moveth?
Ale, ako sa hýbať?
Asi najjednoduchšie by bolo priamo do update implementovať čítanie klávesnice a upravovať takto pozíciu. Ale prečo si to trošku neskomplikovať?
Vytvorte si interface IMovable (nech sa nachádza v namespace Merlin.Actors) - tento interface v sebe nebude mať žiadnu funkcionalitu, jedná sa o tzv. marker interface - slúži len na odlíšenie tried.
Nech Player implementuje IMovable
V engine máte definovaný ďalší interface:
public interface Command
{
    void Execute();
}Jedná sa o základ rovnomenného návrhového vzoru. Teraz si ho implementujeme.
Vytvorte si priečinok Commands a pridajte si do neho triedu Move (implementuje Command).
Nech je konštruktor deklarovaný nasledovne: public Move(IMovable movable, int step, int dx, int dy) kde step udáva rýchlosť a dx a dy udávajú smer pohybu.
Svet používa klasické indexovanie súradníc ako v poli, kde ľavý horný roh má [0,0], X-ová súradnica je vodorovná a rastie doprava, Y-ová je zvislá a rastie smerom dole.
Nezabudnite vykonať typovú kontrolu, či ste dostali objekt, ktorý je aj Actor. Ak by to neplatilo, program nesmie pokračovať - vyvolajte chybu (s vhodnou chybovou hláškou):
throw new ArgumentException("error message goes here");Ak chybu nikde v kóde neošetríme, toto spôsobí, že program spadne - čo je dnes náš cieľ.
V Execute() zabezpečte zmenu pozície IMovable actora - využite metódy GetX, GetY a SetPosition
V triede Player si na vhodnom mieste (asi konštruktor) inicializujte veci potrebné pre pohyb zabezpečte, aby sa v metóde Update vykonalo čítanie z klávesnice a zodpovedajúci pohyb vykonajte pomocou návrhového vzoru command.
Čítanie z klávesnice je dostupné pomocou triedy Input. Táto implementuje vzor Singleton, ktorý umožňuje mať vždy len jednu dostupnú inštanciu - dostanete sa ku nej pomocou GetInstance()
Príklad použitia ste mali na minulom cvičení. Okrem IsKeyPressed je dostupná aj metóda IsKeyDown, vyskúšajte si ich, aký je v nich rozdiel?
Klávesy sú vymenované v Input.Key, zatiaľ sú pre nás zaujímavé Input.Key.UP / DOWN /LEFT / RIGHT - tieto mapujú šípky.
Nezabudnite si hráča pridať do sveta a svoju implementáciu vyskúšať.
S využitím command dokážeme zabezpečiť aj viaceré nezávislé pohyby - napríklad vstup od užívateľa a gravitáciu, prípadne odkopnutie atď.