Codemas - 17. den

Včera jsme přidali kód, který nám zajistil, že dárky padají ze saní. Dnes dopíšeme tu nejdůležitější část hry - naučíme robota dárky chytat!

A jak to uděláme? Robot a dárek jsou v podstatě jen dva obdélníky. Pro jednoduchost prohlásíme, že robot chytí dárek, kdykoliv se tyto dva obdélníky protnou. Nebudeme zatím brát ohled na to, jestli dárek spadl na horní část robota nebo jestli robot najel do dárku zboku.

Codemas Prunik Obdelniku

Protnutí dvou obdélníků lze zjistit pomocí relativně jednoduchého principu, který však v kódu vypadá až strašidelně komplikovaně. Když se nad ním ale člověk zamyslí, tak už to tak složité není.

Celý princip vychází z jednoduché finty. Mnohem jednodušší je totiž zjistit, kdy se obdélníky neprotínají. Neprotínají se, když je jeden obdélník úplně mimo druhý obdélník, tedy když je:

  • levá hrana obdélníku A > pravá hrana obdélníku B (tj. A je vpravo od B), nebo …
  • pravá hrana obdélníku A < levá hrana obdélníku B (tj. A je vlevo od B), nebo …
  • dolní hrana obdélníku A < horní hrana obdélníku B (tj. A je nad B), nebo …
  • horní hrana obdélníku A > dolní hrana obdélníku B (tj. A je pod B)

Když platí alespoň jedna z těchto podmínek, obdélníky se neprotínají.

A když celý výraz obrátíme (provedeme tzv. negaci, tj. ptáme se, zda neplatí žádná z těchto podmínek), tak zjistíme, kdy se obdélníky ne-neprotínají, tj. kdy se protínají :)

Zní to velmi komplikovaně, ale opravdu není. Pokud tě to zajímá, zkus se nad tím zamyslet. Pokud ti to připadá příliš složité, nedělej si s tím starosti a ber podmínku, kterou použijeme níže, jakou černou schránku, která nějak funguje a ty ji jenom použiješ.

Tuto podmínku (nebo podobnou), najdeme téměř v každé hře, kde mohou kolidovat dva předměty (v naší hře je to robot a dárek).

Napišme si univerzální funkci, kterou nazveme třeba protnutiObdelniku a do které budeme předávat dva parametry - dva herní objekty, o kterých chceme zjistit, zda se jejich obdélníky protínají, tj. zda se obrázky obou objektů na obrazovce navzájem alespoň částečně překrývají. V herní terminologii se většinou používá termín, že objekty kolidují.

Funkce protnutiObdelniku vrátí zpět hodnotu true, když se oba obdélníky protínají (robot sbírá dárek), nebo hodnotu false, když se objekty neprotínají (dárek je mimo robota).

Na konec našeho programu v souboru script.js přidejme:

// funkce pro zjištění protnutí obdélníku
// jako parametr se předávají dva herní objekty
// funkce vrací true/false, podle toho, zda ke kolizi dochází nebo ne
function protnutiObdelniku(a, b) {
  if ( a.x + a.sirka < b.x
       || b.x + b.sirka < a.x
       || a.y + a.vyska < b.y
       || b.y + b.vyska < a.y) {
    // obdelniky se neprotinaji
    return false;
  } else {
    // obdélníky se protinaji
    return true;
  }
}

Kryptický zápis na řádcích 5 až 8 přesně odpovídá testu na neprotínající se obdélníky, který jsme si popsali o kousek výše. Dvě svislé čárky || znamenají logické nebo.

Teď už umíme zjistit, zda se obdélník dárku protíná s obdélníkem robota, tj. zda robot dárek chytil. Ale kde v našem kódu se na to budeme ptát?

My už v programu máme funkci otestujDarky, ve které zjišťujeme, zda dárek nespadl na zem. Náš kód pro zjištění kolize dárku s robotem můžeme přidat do této funkce. Když si princip fungování funkce popíšeme slovně, bude vypadat následovně:

  • Procházej postupně všechny dárky v seznamu od posledního k prvnímu.
    • Zjisti, zda aktuální dárek nekoliduje s robotem.
      • Pokud ano, tak:
        • Zvětši skóre.
        • Odstraň dárek.
      • Pokud ne, tak:
        • Zjisti, zda dárek nedopadl na zem.
          • Pokud ano, tak odstraň dárek.

Naše funkce otestujDarky vypadá zatím takto:

// funkce pro testování padajících dárků
// - dopadl dárek na zem?
// - chytil dárek robot?
function otestujDarky() {
  // projdeme pozpátku všechny dárky v poli
  for (let i = darky.length - 1; i >=0; i--) {
    // dopadl dárek na zem?
    if (darky[i].y + darky[i].vyska > hra.vyska) {
      // odstraníme obrázek dárku z herní plochy
      darky[i].element.remove();

      // smažeme herní objekt z pole dárků
      darky.splice(i, 1);
    }
  }
}

Když se podíváme na náš slovní postup, popsaný výše, tak vidíme, že na dvou místech kódu musíme odstranit dárek. Bylo by tedy vhodné, kdybychom pro odstranění dárku udělali krátkou funkci odstranDarek, do které jako parametr předáme index dárku, který chceme smazat z pole dárků.

Někam za funkci otestujDarky přidáme funkci odstranDarek, do které přesuneme dva řádky kódu z funkce otestujDarky, které zajišťují smazání dárku:

// odstraní obrázek dárku z herní plochy s smaže dárek v poli dárků
function odstranDarek(index) {
  // odstraníme obrázek dárku z herní plochy
  darky[index].element.remove();
  
  // smažeme herní objekt z pole dárků
  darky.splice(index, 1);
}

Dva zmíněné řádky kódu nahradíme voláním funkce odstranDarek. Funkce otestujDarky bude po změně vypadat takto:

// funkce pro testování padajících dárků
// - dopadl dárek na zem?
// - chytil dárek robot?
function otestujDarky() {
  // projdeme pozpátku všechny dárky v poli
  for (let i = darky.length - 1; i >=0; i--) {
    // dopadl dárek na zem?
    if (darky[i].y + darky[i].vyska > hra.vyska) {
      // odstraníme dárek
      odstranDarek(i);
    }
  }
}

Pojďme teď dovnitř cyklu for přidat podmínku, kde budeme zjišťovat kolizi robota a dárku.

Využijeme řetězení podmínek if, které v principu vypadá takto:

if (podminka1) {
  // když platí podmínka 1
} else if (podminka2) {
  // podmínka 2 se testuje, pouze když neplatí podmínka 1
} else {
  // když neplatí žádná z výše uvedených podmínek, provede se větev else
}

Podmínku uvnitř cyklu for ve funkci otestujDarky upravíme takto:

if (protnutiObdelniku(robot, darky[i])) {
  // obrázek dárku se protnul s obrázkem robota
  // robot sebere dárek

  // odstraníme sebraný dárek ze hry
  odstranDarek(i);

} else if (darky[i].y + darky[i].vyska > hra.vyska) {
  // dopadl dárek na zem?

  // odstraníme dárek
  odstranDarek(i);

}

Zatím děláme stejnou věc (odstraňujeme dárek ze hry), ať už se dárek srazí s robotem nebo dopadne na zem. Zítra hru doplníme o zvětšování skóre za každý sebraný dárek a naše hra už bude skoro kompletní.

Na závěr

To nejtěžší máme za sebou. Posledních pár dní bylo docela složitých a psali jsme pro začátečníky poměrně komplikovaný kód. Ale nakonec jsme to zvládli a to je dobře, protože všichni víme, co je v sázce!

Nyní budeme do hry doplňovat víceméně drobnosti, díky kterým ale bude hra mnohem zajímavější a pro uživatele příjemnější. V následujících dnech postupně přidáme počítání skóre, zvuky, úvodní a závěrečnou obrazovku. A když nám zbude čas, dodáme i malé překvapení.

Pro případ, že se ti dnes něco nepovedlo, kompletní kód hry po dnešních úpravách si můžeš prohlédnout zde.

Máš-li dotazy, diskutuj u příspěvku pro dnešní den ve facebookové události. Těšíme se na tebe opět zítra.