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 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:
- Tools -> NuGet Package Manager -> Package Manager Settings
- Do package source si pridajte (zelené plus)
http://hades.cit.fei.tuke.sk:8080/v3/index.json
- Kliknite pravým tlačidlom na projekt a zvoľte Manage NuGet Packages
- V pravom hornom rohu si nastavte package source na
Hades
aleboall
(odporúčam all pre prípad zabudnutia) - 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:
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:
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
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):
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
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é:
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.
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). 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).
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:
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:
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:
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:
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:
- ú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.
- 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:
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
:
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)