Konzolový Had 1 – Prípravy

V prvom diely zo série tutoriálov na konzolovú hru Had si pripravíme pomocné triedy a funkcie.

Úvod

Na začiatku by sme si mali rozobrať, čo všetko chceme, aby naša hra Had mala. V hre by sme (samozrejme) mali mať hada, ktorý bude rásť, zakaždým, keď zje modrý bod. Pokiaľ zje bod červený, tak sa zmenší a z celkového skóre bude odpočítaných 100 bodov. Okrem toho ešte v hre budeme mať zelené body, ktoré budu dodávať životy. Tých budeme mať na začiatku 5. Život môžeme stratiť tým, že do seba narazíme, ale že narazíme do steny. Hra by mala mať 10 preddefinovaných úrovní a ďalšie náhodne generovať.

Niekedy neskôr do hry môžeme zakomponovať možnosť hry dvoch hráčov na jednom počítači, ukladanie postupu a tabuľku najvyššieho skóre.

Game

Základné pomocné triedy – Vector2, Rectangle a Pixel

Vector2

Ako najzákladnejšie pomocné triedy budeme chápať triedy Vector2Rectangle. Tie nám pomôžu ľahšie určovať pozíciu hráča a riešiť kolízie.

Najprv si pripravíme triedu Vector2. Vytvorte si teda pre ňu nový C# súbor, a nezabudnite jej pridať modifikátor public.

Táto trieda bude obsahovať len 2 premenné, a to XY, pričom oboje budú typu 32 bit Integet. Rovnako pridáme aj konštruktor, v ktorom budú X,Y ako parametre.

public int X; //Pozícia X - vzdialenosť od ľavého okraja obrazovky.
public int Y; //Pozícia Y - vzdialenosť od pravého okraja obrazovky.

public Vector2(int X, int Y)
{
    this.X = X;
    this.Y = Y; //Premenným priradíme parametre konštruktora.
}

Okrem toho pre uľahčenie práce nahradíme operátori +, -, *, / a ==. Keď nahradzujeme ==, musíme nahradiť aj != a preťažiť funkcie EqualsGetHashCode.

public static Vector2 operator +(Vector2 v1, Vector2 v2)
{
    return new Vector2(v1.X + v2.X, v1.Y + v2.Y);
    //Pri spočítavaní spočítame zvlášť obidva členy.
}

public static Vector2 operator -(Vector2 v1, Vector2 v2)
{
    return new Vector2(v1.X - v2.X, v1.Y - v2.Y);
    //Odpočítanie, násobenie a delenie funguje podobne
}

public static Vector2 operator *(Vector2 v1, Vector2 v2)
{
    return new Vector2(v1.X * v2.X, v1.Y * v2.Y);
}

public static Vector2 operator /(Vector2 v1, Vector2 v2)
{
    return new Vector2(v1.X / v2.X, v1.Y / v2.Y);
}

public static bool operator ==(Vector2 v1, Vector2 v2)
{
    return ((v1.X == v2.X) && (v1.Y == v2.Y));
    //Pri porovnávaní opäť porovnáme obidva členy zvlášť, a vrátime true, ak sa všetko zhoduje.
}

public static bool operator !=(Vector2 v1, Vector2 v2)
{
    return !(v1 == v2);
    //Nerovná sa bude len obrátená hodnota porovnávanie
}

public override bool Equals(object obj)
{
    return (this==(Vector2)obj);
    //Podobne postupujeme aj pri metóde Equals.
}

public override int GetHashCode()
{
    int hash = 1001; //Nadefinujeme si nejaké číslo
    hash = (hash * 7) + X.GetHashCode();
    hash = (hash * 7) + Y.GetHashCode(); //A vypočítame hash podla premenných
    return hash;
}

Rectangle

Ako druhý je Rectangle, ktorý ako názov napovedá predstavuje obdĺžnik. Ten bude mať premenné až 4, pretože okrem jeho pozície ukladáme aj jeho šírku a výšku. Navyše bude mať až štyri konštruktory, ktoré nám opäť uľahčia prácu.

public int X;
public int Y;
public int Width; //Šírka
public int Height; //Výška

private void SetupHelper(int X, int Y, int Width, int Height)
{
    //Pomocná funkcia, na nastavenie premenných
    this.X = X;
    this.Y = Y;
    this.Width = Width;
    this.Height = Height;
}

public Rectangle(int X, int Y, int Width, int Height)
{
    //Základný konštruktor
    SetupHelper(X, Y, Width, Height);
}

public Rectangle(Vector2 Position, int Width, int Height)
{
    //Konštruktor z vektorovanou pozíciou
    SetupHelper(Position.X, Position.Y, Width, Height);
}

public Rectangle(int X, int Y, Vector2 Size)
{
    //Konštruktor z vektorovanou veľkosťou
    SetupHelper(X, Y, Size.X, Size.Y); /
}

public Rectangle(Vector2 Position, Vector2 Size)
{
    //Konšturktor z obomi parametrami typu Vector2
    SetupHelper(Position.X, Position.Y, Size.X, Size.Y);
}

Následne opäť preťažíme operátori. Keďže postup je skoro totožný ako pri triede Vector2, tak vám kód zrolujem aby zbytočne nezaberal miesto, no ak si ho chcete pozrieť, môžete ho rozbaliť.

Pre rozbalenie kliknite

public static Rectangle operator +(Rectangle v1, Rectangle v2)
{
    return new Rectangle(v1.X + v2.X, v1.Y + v2.Y, v1.Width + v2.Width, v1.Height+v2.Height);
}
public static Rectangle operator -(Rectangle v1, Rectangle v2)
{
    return new Rectangle(v1.X - v2.X, v1.Y - v2.Y, v1.Width - v1.Width, v1.Height - v2.Height);
}
public static Rectangle operator *(Rectangle v1, Rectangle v2)
{
    return new Rectangle(v1.X * v2.X, v1.Y * v2.Y, v1.Width * v2.Width, v1.Height * v2.Height);
}
public static Rectangle operator /(Rectangle v1, Rectangle v2)
{
    return new Rectangle(v1.X / v2.X, v1.Y / v2.Y, v1.Width / v2.Width, v1.Height / v2.Height);
}
public static bool operator ==(Rectangle v1, Rectangle v2)
{
    return ((v1.X == v2.X) && (v1.Y == v2.Y) && (v1.Width == v2.Width) && (v1.Height == v2.Height));
}
public static bool operator !=(Rectangle v1, Rectangle v2)
{
    return !(v1 == v2);
}
public override bool Equals(object obj)
{
    return (this == (Rectangle)obj);
}
public override int GetHashCode()
{
    int hash = 1001;
    hash = (hash * 7) + X.GetHashCode();
    hash = (hash * 7) + Y.GetHashCode();
    hash = (hash * 7) + Width.GetHashCode();
    hash = (hash * 7) + Height.GetHashCode();
    return hash;
}

Rectangle je však o niečo inteligentnejšia trieda, a tak v nej sú ešte ďalšie dve metódy. Jedná sa o implicitnú konverziu triedy Vector2 na Rectangle a kontrolu kolízie dvoch obdĺžnikov.

public static implicit operator Rectangle(Vector2 value)
{
     return new Rectangle(value, 1, 1);
}

Pri konverzií len vytvoríme nový Rectangle na pozícií ktorú sme zadali a dáme mu velkosť 1×1.

Kontrola kolízie je trošku zložitejšia, ale stále je to jedna z jednoduchších metód. Pre každú osu porovnáme, či sa nenachádzame úplne mimo obdĺžnik a podľa toho vrátime výsledok.

public bool Intersects(Rectangle Rectangle)
{
    //Kontrola osi X - vzdialenosť od ľavého okraja obrazovky
    if (X >= Rectangle.X) //ak sa nachádzame ďalej ako začiatok druhého rectanglu
    {
        if (X >= Rectangle.X + Rectangle.Width) return false; //a zároveň aj ďalej ako jeho koniec (začiatok + dĺžka), tak vrátime hodnotu false.
    }
    else if (X <= Rectangle.X) //ak sa nachádzame pred začiatkom druhého rectangl
    {
        if (X + Width <= Rectangle.X) return false; //a pred jeho koncom, vrátim opäť vratíme false
    }

    //Podobné kontroly na osi Y
    if (Y >= Rectangle.Y)
    {
        if (Y >= Rectangle.Y + Rectangle.Height) return false;
    }
    else if (Y <= Rectangle.Y)
    {
        if (Y + Height <= Rectangle.Y) return false;
    }

    return true; //Ak sme ani raz nevrátili false, znamená to, že z druhým obdĺžnikom kolidujeme a vrátime true
}

A na záver triedy Rectangle, aby sme si prácu uľahčili, pridáme statickú verziu metódy Intersects

public static bool Intersects(Rectangle R1, Rectangle R2)
{
    return R1.Intersects(R2);
}

Týmto sme si pripravili dve základne triedy – Vector2 a Rectangle. Zhrňme si všetko čo o nich vieme :

Trieda Vector2 má dve premenné, X a Y, označujúce jej pozíciu. Tie sú inicializované pri konštrukcií objektu ako parametre konštruktora. Táto trieda má nahradené operátori na spočítavanie, odpočítavanie, delenie, násobenie a porovnávanie, vďaka tomu budeme môcť vždy spočítavať a porovnávať dva body.

Trieda Rectangle má premenné štyri – X,Y,Width a Height – tie označujú pozíciu a veľkosť obdĺžniku. Rovnaku sú inicializované v konštruktore, pričom ich môžeme zadať aj formou vektorov. Operátori pre spočítavanie, odpočítavanie, delenie, násobenie a porovnávanie sú rovnako nahradené. Ďalej sme pridali implicitnú konverziu z triedy Vektor, vďaka čomu môžeme vytvoriť malí Rectangle 1×1 z bodu. Trieda Rectangle tiež vie zisťovať kolízie s iným obdĺžnikom.

Trieda Pixel

Ako určite viete, Pixel je jeden bod na obrazovke. V tomto prípade budeme pixel chápať ako najmenší bod na konzole. Keďže v C# konzole nieje možné natívne vykreslovať tvary, budeme na vykresľovanie používať klasické hlavne klasické písmená a medzery zo zafarbeným pozadím.

Aby sme mali opäť o niečo jednoduchšiu prácu, vytvoríme si triedu Pixel, ktorá bude obsahovať všetky informácie o jednom bode obrazu v konzole, ktoré potrebujeme.

Bude to znak, ktorý bude v konzole (defaultne medzera), Farba boduFarba písma a boolova hodnota označujúca, či sa pixel od posledného vykresľovanie zmenil, čo zrýchli beh programu.

public char Character = ' ';
public ConsoleColor Color = ConsoleColor.Black;
public ConsoleColor CharColor = ConsoleColor.Gray;
public bool Changed = true;

Okrem toho bude mať táto trieda len preťaženie operátorov porovnávania, keďže spočítavať farby nebudeme. Pri porovnávaní spravíme menšiu zmenu, a totiž nebudeme porovnávať premennú Changed.

public static bool operator ==(Pixel P1, Pixel P2)
{
    return P1.Equals(P2);
}
public static bool operator !=(Pixel P1, Pixel P2)
{
    return !P1.Equals(P2);
}
public override bool Equals(object obj)
{
    Pixel p2 = (Pixel)obj;
    return ((p2.Character == Character) && (p2.CharColor == CharColor) && (p2.Color == Color));
    //premennú Changed nebudeme porovnávať
}
public override int GetHashCode()
{
    int hash = 1001;
    hash = (hash * 7) + Character.GetHashCode();
    hash = (hash * 7) + Color.GetHashCode();
    hash = (hash * 7) + CharColor.GetHashCode();
    hash = (hash * 7) + Changed.GetHashCode();
    return hash;
}

Hlavná trieda a herná slučka

Na koniec prvého dielu si ešte pripravíme hlavnú triedu hry, čiže môžete vytvoriť nový súbor Game.cs. Táto trieda nemusí byť verejná.

V tejto triede teraz vytvorte tri prázdne bezparametrové metódy, InitUpdateDraw. Prvá z nich bude slúžiť na inicializáciu hry po spustení, v triede Update bude vaša herná logika (ovládanie, atď) a nakoniec v triede Draw sa všetko bude vykreslovať.

class Game
{
    public void Init()
    {
    }

    public void Update()
    {
    }

    public void Draw()
    {
    }
}

Teraz sa presunieme do hlavnej triedy Program a jej metódy Main.

V nej si vytvoríte nový objekt triedy Game a hneď aj použijete metódu Init.

Game game = new Game();
game.Init();

Po tomto už nasleduje len hlavná slučka while (true).V ktorej zavoláte metódy UpdateDraw

Game game = new Game();
game.Init();
while (true)
{
    game.Update();
    game.Draw();
}

Tento kód vytvoril slučku, ktorá bude neustále poháňať a vykreslovať hru.

Týmto končia prípravné práce a v ďalšom diely sa pustíme do vykreslovania.

Pridajte Komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *

Scroll to Top