Codemas - 24. den

Naše hra je hotová. Ale nebyly by to ty pravé Vánoce, kdyby pod stromečkem nebylo alespoň jedno malé překvapení. A nejsou to ty ani ty pravé bílé Vánoce, když venku nesněží.

Počasí poroučet ještě neumíme, ale do hry bychom si sněžení přidat mohli :) Protože je Štědrý den, nebudeme to dnes s kódováním přehánět. Program pro padající vločky není jako takový obzvláště složitý, ale je v něm spousta nových konceptů, které bychom během jednoho dne stejně nestihli vysvětlit.

Chcete-li, berte sněžení jako malý dárek od nás, který do hry prostě přidáte, aniž bychom si ho podrobně popisovali. Nechcete-li si hru už komplikovat, klidně přeskočte na konec článku a sníh do hry nepřidávejte. Princip a fungování celé hry to nijak neovlivní.

Padající sníh

Padající sněhové vločky se budou na naší herní ploše vykreslovat do samostatné vrstvy na prvek <canvas>. Canvas je doslova plátno, na které se dá JavaScriptem malovat. My toto plátno mnohokrát za vteřinu vždy smažeme a nakreslíme na něj malé tečky, představující sněhové vločky.

Abych na plátno mohli malovat, musíme ho přidat na herní plochu. Otevřeme soubor index.html a dovnitř <div id="hra"></div> přidáme:

<canvas id="snezeni"></canvas>

V CSS nastavíme vlastnosti, které naše plátno roztáhnou přes celou herní plochu a umístí ho nad všechny objekty na ploše.

Na konec souboru style.css přidáme:

#snezeni {
  position: absolute;
  top: 0;
  left: 0;
  width: 900px;
  height: 600px;
  z-index: 9999;
}

Plátno máme připravené, nyní musíme přidat program, který zařídí, že na plátně bude sněžit. Na konec souboru script.js zkopíruj celý tento kód:

/* -------------------------------------------------- */
/* - PADAJÍCÍ SNĚHOVÉ VLOČKY ------------------------ */
/* -------------------------------------------------- */

// inicializujeme kreslící plátno
let snih = {
  canvas: document.getElementById('snezeni'),
  ctx: null,
  sirka: 900,
  vyska: 600,
  pocet: 120,
  uhel: 0,
  particles: [],
  animovat: false
}
snih.canvas.width = snih.sirka;
snih.canvas.height = snih.vyska;
snih.ctx = snih.canvas.getContext('2d');

// spustí sněžení
function startSnezeni() {
  // vygenerujeme náhodné vločky
  snih.particles = [];
  for(let i = 0; i < snih.pocet; i++) {
    snih.particles.push({
      x: Math.random() * snih.sirka,  // souřadnice X
      y: Math.random() * snih.vyska,  // souřadnice Y
      r: Math.random() * 3 + 1,       // velikost vločky
      d: Math.random() * snih.pocet   // náhodny faktor pro pohyb vločky
    });
  }

  // spustíme snežení
  snih.animovat = true;
  requestAnimationFrame(nakresliSnih);
}


// ukončí sněžení s smaže plátno
function konecSnezeni() {
  // ukončíme animaci vloček
  snih.animovat = false;
  // smažeme plátno
  snih.ctx.clearRect(0, 0, snih.sirka, snih.vyska);
}

// vykreslení vloček na plátno
function nakresliSnih() {
  if (snih.animovat) {
    // smažeme canvas
    snih.ctx.clearRect(0, 0, snih.sirka, snih.vyska);

    // nakreslíme všechny vločky
    snih.ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
    snih.ctx.beginPath();
    for(let i = 0; i < snih.pocet; i++) {
      let p = snih.particles[i];
      snih.ctx.moveTo(p.x, p.y);
      snih.ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2, true);
    }
    snih.ctx.fill();

    // aktualizujeme polohu vloček
    aktualizujSnih();

    // vyžádáme si spuštení funkce znovu při další obnově obrazovky
    requestAnimationFrame(nakresliSnih);
  }
}


// funkce pro aktualizaci polohy vloček
function aktualizujSnih() {
  snih.uhel += 0.01;

  for(let i = 0; i < snih.pocet; i++) {
    // aktualni vlocka
    let p = snih.particles[i];

    // aktualizace X a Y souřadnic vločky
    // abychom pohyb udělali náhodnější, přidáváme na různých místech
    // k úhlu poloměr vločky a/nebo její náhodný faktor
    let dy = Math.cos(snih.uhel + p.d) + p.r / 2;
    if (dy < 0) { 
      // nechceme, aby se vločky pohybovaly nahoru
      dy = -dy; 
    }
    p.y += dy;
    p.x += Math.sin(snih.uhel + p.d / 20 );

    // je-li vločka mimo hranice plátna,
    // tak ji přesuneme zpět nahoru na náhodnou pozici X
    if (p.x > snih.sirka + 5 || p.x < -5 || p.y > snih.vyska) {
      snih.particles[i] = {
        x: Math.random() * snih.sirka,
        y: -10,
        r: p.r,
        d: p.d
      };
    }
  }
}

Uff. To je pěkný kus kódu. Když budeš mít náladu, můžeš si ho ve volném čase prozkoumat a zkusit si s ním pohrát (můžeš třeba upravit počet vloček, když změníš vlastnost pocet v objektu snih).

Zbývají nám poslední dva kroky.

Sněžení musíme spustit při startu hry. Do funkce startHry před řádek, který přepíná obrazovku na herní plochu, doplň:

// spustíme sněžení
startSnezeni();

A ihned po sobě i uklidíme. Do funkce konecHry přidej hned na začátek:

// ukončíme sněžení
konecSnezeni();

A je to. Když hru spustíš, měly by na herní ploše padat sníh. Vánoce jsou vždy hezčí, když jsou bílé.

Codemas Snih

Naše hra by se určitě dala dále vylepšovat. Možná je příliš jednoduchá a měly bychom upravit náhodné parametry tak, aby nešla hrát skoro do nekonečna. Možná bychom měli vylepšit pojmenování některých funkcí, aby byl kód čitelnější a přehlednější a aby bylo jasné, co která funkce dělá.

Občas asi děláme některé věci trochu krkolomně, protože jsme se v zájmu jednoduchosti vysvětlení pro začátečníky záměrně vyhýbali některým složitějším konceptům. Zkušení programátoři by nám určitě spoustu věcí opravili a nad některými možná i kroutili hlavou.

My jsme ale chtěli, aby vás programování především bavilo a abyste si náš adventní kalendář řádně užili. A to se nám, doufejme, povedlo.

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

Pochlub se

Zkus si hru zahrát a pošli nám do facebookové události svoje nejlepší skóre. Sdílej tam také odkaz na svůj kód, pokud sis hru upravil/a podle sebe, ať už změnou některých parametrů nebo třeba výměnou obrázků nebo zvuků za svoje vlastní. Moc rádi se podíváme.

Budeme také rádi, když nám do facebookové události napíšeš, jak se ti adventní kalendář líbil. Věříme, že to pro tebe byla stejná zábava, jako pro nás.

Šťastné a veselé Vánoce.