Animácia, interakcia a iné

V tomto opakovacom tutoriáli vyrobíme jednoduchú hru, v ktorej budeme klávesami ovládať hladný krúžok na ceste za zeleným jedlom a v ohrození nepriateľom.

Hráč je fialový krúžok, pohybuje sa horizontálne alebo vertikálne a pri vyjdení z okraja obrazovky sa vráti na opačnom konci. Nepriateľ je červený krúžok, na začiatku má náhodný smer a odráža sa od okrajov obrazovky. Jedlo je vygenerované na náhodnej pozícii a je to pulzujúci zelený štvorček.

1. Základný skeč

Keď chceme, aby skeč reagoval na klávesy či myš, alebo ak má byť animovaný, tak budeme využívať podprogramy setup() a draw(). Zároveň si už pripravíme aj pozadie a nejaké základné premenné:

float hx, hy;   // pozicia hraca
float shx, shy; // smer hraca
float nx, ny;   // pozicia nepriatela
float snx, sny; // smer nepriatela
float jx, jy;   // pozicia jedla

void setup()
{
  size(600,600);
}

void draw()
{
  background(0);
}

2. Programujeme s komentármi

Jeden z šikovných spôsobov, ako napísať program, je najprv si ho napísať ľudskou rečou pomocou komentárov. Tým získame prehľad o tom, čo všetko treba urobiť, a následne už len dopĺňame ku každému komentáru jeho kód. Aha takto:

float hx, hy;   // pozicia hraca
float shx, shy; // smer hraca
float nx, ny;   // pozicia nepriatela
float snx, sny; // smer nepriatela
float jx, jy;   // pozicia jedla

void setup()
{
  size(600,600);
  
  // Nastavenie pozicie a smeru hraca
  
  // Nastavenie pozicie a smeru nepriatela
  
  // Nastavenie pozicie jedla
}

void draw()
{
  background(0);
  
  // Pohyb hracom
  
  // Pohyb nepriatelom
  
  // Vykreslenie hraca, jedla, nepriatela
  
  // Zisti, ci som sa zrazil s jedlom
  
  // Zisti, ci som sa zrazil s nepriatelom
  
}

3. Počiatočné nastavenia

Pozície hráča, jedla aj nepriateľa nastavíme náhodne na obrazovke. Smer hráča budeme určovať klávesami, ale pre začiatok mu dajme smer doprava (shx = 1, shy = 0). Smer nepriateľa dajme ako náhodné súradnice s rozsahu -3, 3.

  // Nastavenie pozicie a smeru hraca
  hx = random(width); 
  hy = random(height);
  shx = 1;
  shy = 0;
  
  // Nastavenie pozicie a smeru nepriatela
  nx = random(width); 
  ny = random(height);
  snx = random(-3,3);
  sny = random(-3,3);
  
  // Nastavenie pozicie jedla
  jx = random(width);
  jy = random(height);

4. Vykreslenie

Keď už máme pozície všetkých 3 objektov, môžeme ich dať vykresliť:

  // Vykreslenie hraca, jedla, nepriatela
  fill(255,0,255);
  circle(hx, hy, 40);
  
  fill(0,255,0);
  square(jx-10, jy-10, 20);
  
  fill(255,0,0);
  circle(nx, ny, 40);

5. Animovaný pohyb

Pohyb docielime tak, že v draw() budeme zvyšovať pozície hráča aj nepriateľa o ich príslušné smery. Takto:

  // Pohyb hracom
  hx = hx + shx;
  hy = hy + shy;
  
  // Pohyb nepriatelom
  nx = nx + snx;
  ny = ny + sny;

Pohyb hráča je zacyklený na obrazovke. Ak je jeho pozícia príliš vpravo, nastavíme o na ľavý okraj obrazovky. A podobne pre všetky ostatné smery:

  // Pohyb hracom
  hx = hx + shx;
  hy = hy + shy;
  if (hx > width)
  {
    hx = 0;
  }
  if (hx < 0)
  {
    hx = width;
  }
  if (hy > height)
  {
    hy = 0;
  }
  if (hy < 0)
  {
    hy = height;
  }

Alebo môžeme tento kód zapísať aj skrátene. Ak je v podmienke if len jeden jediný príkaz a žiadne else, dá sa každá podmienka zapísať aj bez zátvoriek {,} a do jedného riadku:

  // Pohyb hracom
  hx = hx + shx;
  hy = hy + shy;
  if (hx > width) hx = 0;
  if (hx < 0) hx = width;
  if (hy > height) hy = 0;
  if (hy < 0) hy = height;

Nepriateľ sa má pri svojom pohybe odrážať od stien. To znamená, že ak jeho x-ová súradnica je mimo obrazovky, tak jeho x-ové smerovanie sa obráti na opačné. A takisto v prípade y:

  // Pohyb nepriatelom
  nx = nx + snx;
  ny = ny + sny;
  if (nx > width) snx = -snx;
  if (nx < 0) snx = -snx;
  if (ny > height) sny = -sny;
  if (ny < 0) sny = -sny;

6. Rýchlosť pohybu

Ak sa objekty hýbu príliš pomaly, môžeme pridať parameter rýchlosti pre hráča aj pre nepriateľa

float hx, hy;   // pozicia hraca
float shx, shy; // smer hraca
float nx, ny;   // pozicia nepriatela
float snx, sny; // smer nepriatela
float jx, jy;   // pozicia jedla
float rh = 3;   // rychlost hraca
float rn = 5;   // rychlost nepriatela

A pri pohybe jednotlivých objektov týmto parametrom vynásobíme smer:

  // Pohyb hracom
  hx = hx + rh * shx;
  hy = hy + rh * shy;
  // Pohyb nepriatelom
  nx = nx + rn * snx;
  ny = ny + rn * sny;

7. Ovládanie klávesnicou

Ak meniť smer pohybu hráča klávesnicou, budeme pridáme do skeču podprogram, ktorý reaguje na stlačenie klávesy:

void keyPressed()
{
  
}

Pohyb budeme ovládať šípkami. Ak bola stlačená šípka nahor, zmení sa smer na shx = 0 a shy = -1. Ak bola stlačená šípka nadol, bude shx = 0, shy = 1. A tak podobne pre ostatné dva smery. Položíme podmienku na základe hodnoty systémovej premennej keyCode, ktorá obsahuje kód stlačenej klávesy:

void keyPressed()
{
  if (keyCode == UP)
  {
    shx = 0; 
    shy = -1;
  }
  if (keyCode == DOWN)
  {
    shx = 0;
    shy = +1;
  }
  if (keyCode == LEFT)
  {
    shx = -1;
    shy = 0;
  }
  if (keyCode == RIGHT)
  {
    shx = 1;
    shy = 0;
  }
}

Ak chcete fintu na skrátenie kódu, rozbaľte si nasledujúcu sekciu. Ale nie je to povinné:

Skrátenie if-ov podmocou switch

Ak máme takto veľa if-ov, ktoré sa všetky pýtajú, čomu sa rovná jedna premenná (v našom prípade keyCode), tak môžeme namiesto 4 if-ov napísať jeden switch a 4 prípady case:

void keyPressed()
{
  switch(keyCode)
  {
    case UP: shx = 0; shy = -1; break;
    case DOWN: shx = 0; shy = 1; break;
    case LEFT: shx = -1; shy = 0; break;
    case RIGHT: shx = 1; shy = 0; break;
  }
}

8. Kolízia s jedlom

Toto je jediná vec, ktorá je nová v tomto opakovaní. Aby sme vedeli zistiť, či sa dve veci dotýkajú, budeme potrebovať jednoduchú matematiku. Dve veci sa dotýkajú, ak vzdialenosť ich stredov je menšia ako súčet ich polomerov.

Aby sme mali program urobený všeobecne, tak vyrobme premenné na veľkosť hráča, nepriateľa aj jedla. Doteraz sme mali v kóde čísla natvrdo (v príkazoch square a circle), ale vytiahneme si ich do premenných:

float hx, hy;   // pozicia hraca
float shx, shy; // smer hraca
float nx, ny;   // pozicia nepriatela
float snx, sny; // smer nepriatela
float jx, jy;   // pozicia jedla
float rh = 3;   // rychlost hraca
float rn = 5;   // rychlost nepriatela
float vh = 40;  // velkost hraca
float vn = 40;  // velkost nepriatela
float vj = 20;  // velkost jedla

A upravíme aj vykresľovanie:

  // Vykreslenie hraca, jedla, nepriatela
  fill(255,0,255);
  circle(hx, hy, vh);
  
  fill(0,255,0);
  square(jx - vj/2, jy - vj/2, vj);
  
  fill(255,0,0);
  circle(nx, ny, vn);

Teraz už môžeme napísať podmienku na zistenie, či sme sa zrazili s jedlom. Pomôžeme si príkazom dist, ktorý počíta vzdialenosť medzi dvojicou súradníc. Ak je vzdialenosť menšia ako súčet polomerov, tak došlo ku kolízii a my vygenerujeme nové jedlo na nových súradniciach:

  // Zisti, ci som sa zrazil s jedlom
  if (dist(hx, hy, jx, jy) < vh/2 + vj/2)
  {
    // Zapocitame skore
    // ...
    
    // Presunieme jedlo na nove suradnice
    jx = random(width);
    jy = random(height); 
  }

Ostáva pridať započítavanie skóre a jeho výpis na obrazovku. Vyrobíme si premennú pre skóre:

float hx, hy;   // pozicia hraca
float shx, shy; // smer hraca
float nx, ny;   // pozicia nepriatela
float snx, sny; // smer nepriatela
float jx, jy;   // pozicia jedla
float rh = 3;   // rychlost hraca
float rn = 5;   // rychlost nepriatela
float vh = 40;  // velkost hraca
float vn = 40;  // velkost nepriatela
float vj = 20;  // velkost jedla
int skore = 0;

Dopíšeme kód pre vypisovanie skóre na obrazovku:

  // Vykreslenie hraca, jedla, nepriatela
  fill(255,0,255);
  circle(hx, hy, vh);
  
  fill(0,255,0);
  square(jx - vj/2, jy - vj/2, vj);
  
  fill(255,0,0);
  circle(nx, ny, vn);
  
  // Vypisanie skore
  textSize(40);
  fill(255);
  text(skore, width/2, 40); 

A do podmienky, keď nastala kolízia, doplníme zvýšenie skóre o 1:

  // Zisti, ci som sa zrazil s jedlom
  if (dist(hx, hy, jx, jy) < vh/2 + vj/2)
  {
    // Zapocitame skore
    skore = skore + 1;
    
    // Presunieme jedlo na nove suradnice
    jx = random(width);
    jy = random(height);     
  }

9. Kolízia s nepriateľom

Podmienku, či som sa zrazil s nepriateľom, doplníme podobne ako v prípade jedla:

  // Zisti, ci som sa zrazil s nepriatelom
  if (dist(hx, hy, nx, ny) < vh/2 + vn/2)
  {
    // Koniec hry
    // ...
  }

A na koniec hry vypíšeme na obrazovku veľký červený GAME OVER a zastavíme hru.

Ako zastavíme hru? Urobiť to korektne by bolo trochu na dlhšie, ale my si pomôžeme jednoduchým hackom: Nastavíme frameRate aplikácie na 0, tým sa vypnú všetky animácie. Bohužiaľ sa vypne aj interakcia, preto je to nekorektné. Ale na teraz nám to stačí:

  // Zisti, ci som sa zrazil s nepriatelom
  if (dist(hx, hy, nx, ny) < vh/2 + vn/2)
  {
    // Koniec hry
    frameRate(0);
    textSize(100);
    fill(255, 0, 0);
    textAlign(CENTER, CENTER);
    text("GAME OVER", width/2, height/2);
  }

10. Pulzujúce jedlo

Ako bonus si dorobíme animáciu jedla, ktorého veľkosť bude pulzovať. Na tento recept budeme potrebovať:

  • nejakú premennú, ktorá rastie v čase

  • funkciu, ktorá z tej premennej bude vyrábať pulzujúce hodnoty

  • funkciu, ktorá z tých pulzujúcich hodnôt vyrobí veľkosť jedla.

Premenná, ktorá rastie v čase je napríklad systémová premenná frameCount, ktorú Processing automaticky zvyšuje o 1 po každom vykonaní draw().

Pomocou matematickej funkcie sínus vyrobíme z rastúcich hodnôt frameCount hodnoty, ktoré oscilujú medzi -1 až +1.

A pomocou funkcie map() zmeníme hodnoty z intervalu -1 až +1 na interval napríklad 0.8 * veľkosť jedla až 1.5 * veľkosť jedla.

Keď toto vyrátame, uložíme si to do novej premennej, a tú potom použijeme pri vykreslení štvorčeka v príkaze square.

  // Vykreslenie hraca, jedla, nepriatela
  fill(255,0,255);
  circle(hx, hy, vh);
  
  // Pulzujuca velkost jedla
  float pvj = map(sin(frameCount), -1, 1, 0.8*vj, 1.5*vj); 
  fill(0,255,0);  
  square(jx - pvj/2, jy - pvj/2, pvj);
  
  fill(255,0,0);
  circle(nx, ny, vn);

Zelený štvorček začne pulzovať. Ak sa vám nepáči, že pulzuje veľmi rýchlo, tak spomalíme rýchlosť tak, že frameCount vynásobíme nejakým malým číslom, napríklad 0.1:

  float pvj = map(sin(0.1*frameCount), -1, 1, 0.8*vj, 1.5*vj); 

Last updated