|
|
|
# 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ý.
|
|
|
|
|
|
|
|
## 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()` a... máme chybu. C# neumožňuje prekrývať ľubovoľné metódy, iba tie, ktoré sú označené - keyword `virtual`. Upravte teda v `Crystal` signatúru aby ho obsahovala:
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
public virtual void TurnOn()
|
|
|
|
```
|
|
|
|
|
|
|
|
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` a `player_move.png` - v prípade, že sa hráč otočí, použite `Animation.FlipAnimation()`
|
|
|
|
|
|
|
|
## 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 `Movable` (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 `Movable`
|
|
|
|
|
|
|
|
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(Movable movable, int step, int dx, int dy)` kde step udáva rýchlosť a dx a dy udávajú smer pohybu. 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 `Movable` 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ď. |