|
|
# 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`:
|
|
|
|
|
|
```csharp
|
|
|
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.
|
|
|
|
|
|
```csharp
|
|
|
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ť:
|
|
|
|
|
|
```csharp
|
|
|
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:
|
|
|
|
|
|
```csharp
|
|
|
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:
|
|
|
|
|
|
```csharp
|
|
|
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):
|
|
|
|
|
|
```csharp
|
|
|
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ď. |
|
|
\ No newline at end of file |