# Cvičenie 2 ## 1.0 Setting up the project Vytvorte si nový projekt: .NET Core console application ------------- Samozrejme, každý správny programátor používa pri práci nejaký nástroj na správu verzií. My budeme používať git. (Ako sa s gitom pracuje už máte vedieť) Naša katedra má vlastný server [gitlab.cit.fei.tuke.sk](https://gitlab.cit.fei.tuke.sk/) ktorý budeme používať na tomto predmete (prípadne možete používať github). Na internete si nájdite `.gitignore` pre C# a pridajte si ho do priečinka s projektom. Pre tento predmet bol pridaný vlastný balíčkovací server, kde bude dostupná aktuálna verzia herného enginu. Aby ste si ho vedeli nahrať, postupujte nasledovne: 1. Tools -> NuGet Package Manager -> Package Manager Settings 2. Do package source si pridajte (zelené plus) `http://hades.cit.fei.tuke.sk:8080/v3/index.json` 3. Kliknite pravým tlačidlom na projekt a zvoľte Manage NuGet Packages 4. V pravom hornom rohu si nastavte package source na `Hades` alebo `all` (odporúčam all pre prípad zabudnutia) 5. Nájdite si balíček Merlin2d a nainštalujte ## 1.1 Setting up the project, part 2 Obdobne ako v C, každý program v C# musí mať definovaný vsupný bod (main funkcia). Štandardne je to funkcia `static void Main(string[])` v triede `Program` (dá sa to zmeniť v nastaveniach projektu). Na časti tohoto predmetu budeme programovať hru. Samotné vykresľovanie a hernú slučku sme už pre vás pripravili. Objektové programovanie nám umožňuje, za predpokladu dodržania už definovaných rozhraní (interfaces) tvoriť veci modulárne. Tento princíp umožňuje jednotlivým častiam komunikovať medzi sebou bez toho, aby ste museli na všetko myslieť vopred (narozdiel od C). Vytvorte si okno v ktorom pobeží hra: ```csharp GameContainer container = new GameContainer(name, width, height); //constructor, creates new instance of the game //put initialization code here container.Run(); //start the game, infinite loop, no change is accepted after this until the game ends ``` `name - string` názov okna (string sa, podobne ako v C, píše v úvodzovkách: `"aaa"`) `width, height - int` rozmery okna v pixeloch _Podobne ako v C, aj tu je potrebné deklarovať importované knižnice. C# na to využíva tzv. namespace. Po jeho importovaní sú dostupné všetky triedy a ich verejné metódy ktoré obsahuje._ `GameContainer` sa vám vyznačil na červeno - prostredie ho ešte nepozná. Keď ukážete myšou na červenou vyznačený kód, objaví sa vedľa neho žiarovka s červeným `X`. Kliknite na ňu a zvoľte prvú možnosť - `using Merlin2d.Game;` _Ak niekto pracuje v Linuxe (alebo na Macu), tento import musíte pravdepodobne spraviť ručne:_ ```csharp using System; using Merlin2d.Game; namespace Merlin { class Program { static void Main(string[] args) { //code ``` Ak ste postupovali správne, zobrazilo sa vám po spustení čierne okno. ## 1.2 A brave new world Stiahnite si balíček s mapou a textúrami [resources.zip](https://drive.google.com/file/d/1Pa87h0DEtAXdkILPhEos0EOYEOtmrlnS/view?usp=sharing) Rozbaľte si ho do priečinka s projektom, nech výsledná cesta vyzerá nasledovne: `projectFolder/resources` Teraz si vo VS v okienku `Solution Explorer` vyznačte všetky súbory, ktoré ste práve pridali a v záložke `properties` nastavte `copy to output directory` na `always`. Je to trošku nepraktické a vhodnejšie by bolo, keby sa dal označiť celý priečinok, ale akosi na to tvorcovia VS zabudli. Teraz už budeme môcť načítať mapu -> pridajte si do `Main` nasledujúci kód (na miesto, kde je v komentári napísaná inicializácia): ```csharp container.SetMap("resources/level01.tmx"); ``` ## 1.3 Actors, actors everywhere S herným prostredím dokážete komunikovať pomocou rôznych rozhraní (`interface`), jedným z nich je `Actor`. V tejto, prvej časti sme pre vás pripravili abstraktnú triedu `AbstractActor` ktorá už časti tohoto rozhrania implementuje. Vytvorte si novú triedu ktorá bude predstavovať koltík `Kettle`, ktorá bude rozširovať `AbstractActor` ```csharp public class Kettle : AbstractActor { } ``` V tomto momente vám VS (Visual Studio) podčiarklo `Kettle` - ukážte naň myšou a uvidíte tam niekoľko možností, zvoľte Implement abstract class. Toto za vás vygeneruje chýbajúce funkcie definované rozhraním actor (ktoré abstraktná trieda AbstractActor neimplementuje) Objavilo sa nasledovné: ```csharp public override void Update() { throw new NotImplementedException(); } ``` O výnimkách sa dozviete neskôr, zatiaľ stačí vedieť, že ak sa počas behu dostane program na takéto miesto, bez vhodného ošetrenia spadne. Vymažte riadok vyvolávajúci výnimku (kde je `throw`). V Main funkcií si vytvorte nový objekt triedy Kettle. ```csharp Kettle kettle = new Kettle(); ``` Skúste spustiť program, stalo sa niečo? Ak ste postupovali správne, tak sa zobrazila akurát mapa, ale žiadny kotlík. Prečo? ## 1.4 Animations V 2D grafike sa často používajú tzv. sprites [(wiki link)](https://en.wikipedia.org/wiki/Sprite_(computer_graphics)). V našom prípade sú animácie vytvorené sekvenciou obrázkov uložených vedľa seba v jednom .png súbore. Ako vstupné parametre v konštruktore budú cesta k spritesheet a výška a šírka jedného obrázku (viď. schému). ```csharp Animation animation = new Animation("resources/kettle.png", 64, 64); kettle.SetAnimation(animation); //set animation for kettle kettle.SetPosition(100, 100); //set position of the kettle in the game world ``` Ak teraz spustíte program, stále sa nič nestane. Je potrebné ešte svetu povedať, že nejaký kotlík existuje: ```csharp container.GetWorld().AddActor(kettle); ``` Keď teraz spustíte hru, mal by sa na obrazovke zjaviť kotlík, ktorý bude lietať niekde v prostredí - ešte sme nevymysleli gravitáciu (k tomu sa dostaneme až o niekoľko cvičení) Ešte by bolo spustiť animáciu: ```csharp animation.Start(); ``` V objektovo orientovanom programovaní je jedným z dôležitých princípov zapúzdrenie (encapsulation) - členské premenné v jednotlivých triedach nemajú byť prístupné zmene z vonku. Na manipuláciu majú byť použité tzv. mutátory a accessory (setter, getter) - v praxi to ale vo veľa prípadoch vyzerá nasledovne: ```csharp private int x; public int GetX() { return x; } public void SetX(int value) { x = value; } ``` (dodržanie princípu si vyžaduje napísanie veľa "zbytočného" kódu - z funkčného hľadiska je to to isté, ako keby tam bolo priamo `public int x`) C# má na tento problém jednu syntaktickú perličku, ktorej sa hovorí properties: ```csharp public int X { get; set; } //version 1 public int X {get; private set;} //version 2 - can be changed only within the object itself private int x; public int X { get { return x; } set { x = value; } } //version 3 - custom accessor and mutator, equivalent to usual getter and setter from the previous example; these are normal functions and may be coded as such (conditions, loops, et cetera) ``` Project.merlin, ktorého zdrojový kód bol použitý pri tvorbe knižnice, ktorú používate, bol napísaný v Jave. Narozdiel od C#, Java nepozná properties, len klasický prístup ku premenným, ako bolo ukázané v prvom príklade. Mimo toho je syntax v Jave veľmi podobná C#, takže väčšinu kódu je možné medzi jazykmi prekopírovať len s malými zmenami (samozrejme to neplatí pri použití frameworkov). Tento engine je skoro priamou kópiou z Javy, boli zmenené iba časti, kde sa syntax líši a kód zodpovedajúci za vykresľovanie na obrazovku, keďže je použitý iný wrapper pre OpenGL (C# Raylib_cs vs Java org.newdawn.slick2d). Posledná zmena sa týka štábnej kultúry, kde C# používa CapitalFont miesto Javovského camelFont. V praxi to pre vás znamená, že engine nepoužíva properties ale ku všetkému sa pristupuje cez klasický getter a setter. Vo vlastnom kóde však môžete používať obidva spôsoby (ak si to nejaký interface špecificky nevyžaduje). ## 1.5 Magic potion Kotlík by nám bol sám o sebe dosť nanič, ale môžeme ho použiť na varenie kúzelného elixíru. Engine funguje jednoducho - každú sekundu sa 60x vykoná nasledovné: - zavolá funkcia `Update()` u každého actora pridaného do sveta - vykreslí sa scéna (samozrejme, pokiaľ nemáte počítač značky Casio) Nasledujúce cvičenia budú stále predpokladať 60 aktualizácií za sekundu (tzn. 5sec napočítate počítadlom, ktoré pôjde do 300) Pridajte do kotlíka nasledujúcu funkcionalitu: 1. úvodná animácia je pre každý kotlík rovnaká - presuňte kód ktorý ju rieši z Main funkcie do konštruktora kotlíka. 2. pri varení je potrebné udržať teplotu, pridajte teda funkcionalitu, ktorá bude riešiť tento problém: - nech je teplota reprezentovaná celým číslom - pridajte funkcie `public int GetTemperature(), public void IncreaseTemperature(int delta)` - vonku ešte nie je zima, teda nech kotlík nemôže vychladnúť pod 20°C - nech kotlík každé 2 sekundy schladne o 1°C - ak je teplota kotlíka vyššia ako 60°C zmeňte animáciu na `kettle_hot.png` - ak teplota prekročí 100°C zmeňte animáciu `kettle_spilled.png` nastavte teplotu na 20°C a nech kotlík ignoruje ďalšiu zmenu teploty - vyskúšajte meniť teplotu tak, aby sa prejavila zmena animácií (zatiaľ z `Main`) _Poznámka: Animácie si môžete dopredu pripraviť a už ich len následne podľa potreby meniť - nepnačítavajte stále animáciu nanovo._ _Poznámka(2): ak si chcete skontrolovať, čo sa deje, viete si do konzoly vypísať správu nasledovne:_ ```csharp Console.WriteLine("string"); ``` Rôzne spôsoby prevodu čísel na string vám veľmi ochotne ukáže Google. (napr. `String.Format(), object.ToString()`) ## 1.6 Is it hot in here or is it just me Pridajte si triedu `Stove` ktorá implementuje AbstractActor s animáciou `resources/stove.png`. Táto bude predstavovať pec na ktorej sa bude kotlík hriať. Ďalej do tejto triedy pridajte metódu `public void AddKettle(Kettle kettle)` ktorá nastaví referenciu na kotlík, ktorý bude pec zohrievať. - Nech pec každú sekundu zohreje kotlík o 1°C Či bol kotlík nastavený viete overiť porovnaním referencie s `null`: ```csharp if (someObject != null) { //someObject exists } ``` Ak toto neskontrolujete a kotlík nastavený nebol, program spadne. Vyskúšajte, či všetko funguje - v `Main` pridajte pec, nastavte jej kotlík a sledujte ako sa zohrieva. Ak ste postupovali správne, mal by po nejakom čase vykypieť. Pridajte do `Stove` dve funkcie: `public void AddWood()` a `public void RemoveWood()` a pridajte k nim zodpovedajúcu funkcionalitu: - nech každné polienko zohrieva kotlík o 1°C za sekundu - nech v peci môže byť 0-3 polená - ak v peci nie je žiadne poleno, zmeňte animáciu na `stove_cold.png`, ak sa pridá poleno, zmeňte animáciu späť - predpokladajme, že pec zatiaľ horí stále, keď má drevo (zmeníme to neskôr)