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