Codemas - 19. den

Ježíšek je smutný! Včera jsme do hry přidali zvuky, ale (ve většině prohlížečů) nám nehrají. Snad nejsou Vánoce znovu v ohrožení, když už to s dokončením naší hry vypadalo tak dobře?

Ale ne, nebojte. Jen musíme obejít mechanismus dnešních moderních prohlížečů, které většině stránek zakazují přehrávat zvuky, aby neobtěžovaly uživatele automaticky se spouštějící hudbou nebo videem.

Prohlížeče povolí zvuky ve chvíli, až když uživatel na stránce na něco klikne. To nám nevadí, protože naše hra zatím začínala ihned, jak se stránka načetla, a to není moc dobrá uživatelská zkušenost.

Lepší by bylo, kdybychom měli nějakou startovní obrazovku, kam bychom mohli napsat, o co ve hře jde, a zároveň by tam bylo tlačítko START, které by hru spustilo. Kliknutí na tlačítko spustí hru a zároveň je to pro prohlížeč indikátor, že si uživatel přeje se stránkou pracovat a povolí přehrávání zvuků.

Dnes budeme psát relativně málo nového kódu, ale budeme ten stávající trochu přesouvat. Čti pozorně, ať někde něco nevynecháš. Ježíšek už se opravdu těší na ty zvuky. :)

Startovní obrazovka

Když se podíváme, jak vypadá naše HTML, tak vidíme, že máme v kódu hlavičku hry uvnitř značky <header> a pak vlastní herní plochu tvořenou značkou <div id="hra">.

Nebudeme to komplikovat a hlavičku hry necháme zobrazenou neustále. Do HTML ale přidáme <div id="uvod">, což bude naše úvodní obrazovka, do které vložíme text a startovací tlačítko.

V naší hře pak budeme podle potřeby skrývat nebo zobrazovat buď <div> s úvodní obrazovkou, nebo ten druhý s herní plochou. A později přidáme třeba ještě další s koncovou obrazovkou, když hra skončí.

Do souboru index.html přidejme před <div id="hra"> následující kód:

<div id="uvod">
  <div class="obsah">
    <p>Ježíšek si na rozvoz dárků dětem najal pomocníky, ale stalo se neštěstí. Jednomu pomocníčkovi se rozbily jeho létající sáně. Zbláznily se a létají sem a tam a dárky z nich padají na zem. Doručení dárků je ohroženo.</p>

    <p>Pomocí šipek na klávesnici ovládáš robota, který pomáhá dárky chytat a ukládat do pytle.</p>
    
    <p>Kolik dárků se ti takto povede zachránit?</p>

    <button id="start">Start hry</button>
  </div>
</div>

Máme <div id="uvod">, který představuje celou naši úvodní obrazovku, kterou budeme skrývat nebo odkrývat. Uvnitř máme <div class="obsah">, který představuje blok textu s tlačítkem vycentrovaný uprostřed úvodní obrazovky.

Centrování obsahu uvnitř úvodní obrazovky zařídíme pomocí flexboxu. Flexbox primárně slouží pro tvorbu layoutů, kde jsou prvky rozmístěny vedle sebe, ale dá se použít i jako nejjednodušší způsob horizontálního i vertikálního centrování jednoho prvku uvnitř druhého. To, že náš <div id="uvod"> je flexbox, bude důležité později.

V HTML máme vše hotové, jen to pochopitelně nevypadá moc hezky, dokud nepřidáme i CSS styl.

Úvodní obrazovku nastylujeme podobně jako horní plochu - nastavíme stejnou šířku a výšku, stejný margin kolem i stejné pozadí. Rozdíl bude pouze v tom, že z úvodní obrazovky uděláme pomocí display: flex; ten zmíněný flexbox, aby mohl být obsah uvnitř vycentrovaný pomocí vlastností justify-content a align-items.

Přidejme na konec souboru style.css následující kód:

#uvod {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-left: auto;
  margin-right: auto;
  width: 900px;
  height: 600px;
  background-image: url('obrazky/pozadi.png');
  background-size: 100% 100%;
}

Přidejme ještě styl pro <div> s obsahem úvodní obrazovky. Nemusíme vymýšlet nic složitého, stačí nám pouze omezit šířku bloky a nastavit zarovnání textu na střed.

.obsah {
  width: 60%;
  text-align: center;
}

Tak teď už to vypadá o dost lépe, až na tlačítko Start hry, které má výchozí vzhled prohlížeče a vypadá… no, prostě ošklivě. Přidejme do CSS styl, který naše tlačítko „trochu“ vylepší. :) Změníme velikost písma i celého tlačítka, nastavíme pozadí, přidáme rámeček a stín a doplníme i stav tlačítka, když na něj najede myš (:hover), a stav tlačítka, když se na něj klikne (:active).

Do CSS přidej:

button {
  margin-top: 30px;
  padding: 20px 30px;
  font-family: 'Bevan', cursive;
  font-size: 36px;
  line-height: 1;
  border-radius: 60px;
  border: 5px solid #ffc04b;
  color: white;
  background: linear-gradient(to bottom, #275786, #0e234d);
  text-shadow: 3px 3px 5px rgba(0, 0, 0, 0.4);
  box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.4);
  outline: 0;
  transition: all 0.2s;
}

button:hover {
  background: linear-gradient(to bottom, #3677af, #234f79);
}

button:active {
  transform: translate(3px, 3px);
  box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.4);
}

Výsledek by měl vypadat asi takto:

Codemas Uvod

Tak tohle už je super, to už se nám líbí. Jupííí! Zbývá pouze drobný problém - na stránce teď máme úvodní obrazovku a pod ní druhou obrazovku, kde probíhá naše hra.

Zobrazování a skrývání objektů JavaScriptem

My chceme naše obrazovky zobrazovat nebo skrývat JavaScriptem. To můžeme udělat pomocí CSS vlastnosti display. Když libovolnému prvku nastavíme CSS vlastnost display na hodnotu none, prvek bude skrytý. Doslova říkáme zobrazení: žádné.

Každý prvek v HTML je nějakého typu - máme blokové prvky, řádkové, řádkově blokové, tabulkové, flexbox apod. Vlastnost display nastavuje právě tento typ prvku, resp. jakým způsobem se prvek zobrazuje.

  • display: block; - prvek se chová a je zobrazený jako blokový prvek
  • display: flex; - prvek se chová a je zobrazený jako flexbox
  • display: none; - prvek není zobrazený vůbec

Pro nás jsou důležité tyto tři typy, protože naše úvodní obrazovka je typu flexbox (tedy vlastnost display má hodnotu flex) a naše obrazovka s herní plochou je typu blokový prvek (tedy vlastnost display má hodnotu block).

Když budeme chtít skrýt úvodní obrazovku nebo herní plochu, nastavíme jejich display na hodnotu none. Když je budeme chtít znovu zobrazit, nastavíme jejich display na příslušnou hodnotu - tedy flex pro úvodní obrazovku a block pro herní plochu.

Vlastnost display nastavujeme v JavaScriptu úplně stejně jako třeba vlastnosti top a left, které používáme pro posun objektů na obrazovce. Použijeme odkazNaElement.style.display = 'none';

Upravme naše CSS tak, aby jak úvodní obrazovka, tak herní plocha byly na začátku skryté.

Najdeme v CSS část, kde máme nastavený styl pro #hra a přidáme display: none. Styl pro #hra bude vypadat takto:

#hra {
  display: none;
  position: relative;
  margin-left: auto;
  margin-right: auto;
  width: 900px;
  height: 600px;
  background-image: url('obrazky/pozadi.png');
  background-size: 100% 100%;
}

Obdobně to uděláme i s naší dnes přidanou úvodní obrazovkou. Najdeme v CSS definici pro #uvod a přepíšeme display: flex; na display: none. Celá definice pro #uvod bude vypadat takto:

#uvod {
  display: none;
  justify-content: center;
  align-items: center;
  margin-left: auto;
  margin-right: auto;
  width: 900px;
  height: 600px;
  background-image: url('obrazky/pozadi.png');
  background-size: 100% 100%;
}

Když teď hru spustíme, uvidíme (zcela správně) pouze hlavičku hry. Obě obrazovky jsou schované.

Abychom mohli JavaScriptem nastavovat vlastnosti úvodní obrazovce, musíme si na ní vytvořit odkaz, jako jsme to dělali pro všechny ostatní prvky na stránce.

Na začátku souboru script.js najdeme objekt hra a přidáme do něj vlastnost uvod, kam si uložíme odkaz na HTML prvek naší úvodní obrazovky:

// objekt pro hru
let hra = {
  element: document.getElementById('hra'),
  sirka: 900,
  vyska: 600,
  dalsiDarek: 150,
  skore: 0,
  skoreElement: document.getElementById('pocet'),
  hudba: document.getElementById('hudba'),
  zvukNaraz: document.getElementById('zvuk-naraz'),
  zvukSebrano: document.getElementById('zvuk-sebrano'),
  uvod: document.getElementById('uvod')
}

Pro pohodlné přepínání obrazovek v našem programu si napíšeme funkci, do které budeme jako parametr předávat název obrazovky, kterou chceme zobrazit. Někam na konec souboru script.js dopiš:

// přepínání obrazovky
function prepniObrazovku(obrazovka) {
  // nejprve všechny obrazovky skryjeme
  hra.uvod.style = 'none';      // úvod
  hra.element.style = 'none';   //herní plocha

  // podle parametru zobrazíme příslušnou obrazovku
  if (obrazovka === 'uvod') {
    
    // úvod je flexbox, nastavíme na flex
    hra.uvod.style.display = 'flex';

  } else if (obrazovka = 'hra') {
    
    // herní plocha je blokový prvek, nastavíme na block
    hra.element.style.display = 'block';
  
  }
}

Funkci budeme volat buď jako prepniObrazovku('uvod'), nebo prepriObrazovku('hra'). Někdy později možná přidáme ještě obrazovku 'konec'.

Upravíme spouštění hry

Pokud si ještě vzpomínáš, v našem kódu máme řádek window.addEventListener('load', startHry);, kterým programu říkáme, že když jsou na stránce kompletně načtené všechny prvky (text, obrázky fonty apod.), tak má zavolat funkci startHry. V této funkci pak umístíme robota a sáně na výchozí pozice, nastartujeme časovače, prostě spustíme hlavní kód naší hry.

To se nám teď už nehodí. My v naší hře chceme nejprve zobrazit úvodní obrazovku a když se na této obrazovce klikne na tlačítko start, tak teprve chceme spustit funkci startHry.

Upravme výše zmíněný řádek, který čeká na načtení stránky (událost load), aby volal funkci uvodHry.

window.addEventListener('load', uvodHry);

Funkci uvodHry my samozřejmě v kódu nemáme, ale hned si ji tam přidáme. Uvnitř funkce budeme potřebovat odkaz na HTML prvek tlačítka Start hry, takže si ho nejprve přidáme do objektu hra k odkazům na ostatní prvky v naší hře. Do objektu doplníme vlastnost tlacitkoStart:

// objekt pro hru
let hra = {
  element: document.getElementById('hra'),
  sirka: 900,
  vyska: 600,
  dalsiDarek: 150,
  skore: 0,
  skoreElement: document.getElementById('pocet'),
  hudba: document.getElementById('hudba'),
  zvukNaraz: document.getElementById('zvuk-naraz'),
  zvukSebrano: document.getElementById('zvuk-sebrano'),
  uvod: document.getElementById('uvod'),
  tlacitkoStart: document.getElementById('start')
}

Nyní můžeme přidat funkci uvodHry, ve které se přepneme na úvodní obrazovku a na tlačítko Start přidáme posluchač události, který bude čekat na kliknutí. Při kliknutí na tlačítko zavolá naši původní funkci startHry.

Do script.js přidej novou funkci uvodHry:

// funkce pro zobrazení úvodu hry
function uvodHry() {
  // přepneme na úvodní obrazovku
  prepniObrazovku('uvod');

  // na tlačítku budeme čekat na kliknutí
  // při kliknutí zavoláme startHry
  hra.tlacitkoStart.addEventListener('click', startHry);
}

A nyní už nám zbývá poslední změna a budeme hotovi. Ve funkci startHry se musíme přepnout z úvodní obrazovky na herní plochu. Na konec funkce start hry stačí dopsat prepniObrazovku('hra');

Celá funkce startHry tedy bude vypadat takto:

// funkce pro spuštění hry
// volá se po stisku tlačítka Start na úvodní obrazovce
function startHry() {
  // nastavíme objekty do výchozí polohy
  robot.x = Math.floor(hra.sirka / 2 - robot.sirka / 2);
  umistiObjekt(robot);
  umistiObjekt(sane);

  // když na stránce dojde ke stisku klávesy (jakékoliv),
  // zavolá se funkce priStiskuKlavesy
  document.addEventListener('keydown', priStiskuKlavesy);

  // nastartujeme časovač, který bude 50× za vteřinu posouvat sáně, dárky, apod.
  setInterval(aktualizujHru, 20);

  // spustíme hudbu
  hra.hudba.play();

  // přepneme obrazovku na herní plochu
  prepniObrazovku('hra');
}

A jsme hotovi. Tedy alespoň pro dnešek. :) Když naši hru spustíme, měla by se ukázat úvodní obrazovka s tlačítkem Start hry. Když klikneme na tlačítko, měla by se objevit herní plocha s létajícími sáněmi a padajícími dárky. Měla by hrát hudba a při sebrání nebo spadnutí dárku by se měl ozvat drobný zvuk.

Na závěr

Ježíšek je nadšený a skáče radostí. Konečně se dočkal. Hraje hudba, hra má úvodní obrazovku, dárky padají (i když zrovna z toho tak nadšený není) a robot je může sbírat. V levého horním rohu se nám počítá skóre.

Naší hře už teď chybí asi jediná věc - nejde nijak dohrát. Nemůžeme zvítězit ani prohrát. Na to se zaměříme v následujících dnech.

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.