Kosi hitac u Processing-u – fudbal igrica

Lekcija 8 od 14 · Processing

Da bi prethodno kreiranu animaciju kosog hica iskoristila i napravili kretanje lopte za "fudbal igricu", potrebno je da dodamo slike u scatch. Potrebno je preuzeti slike pozadine npr. sliku stadiona i slika koja će predstavljati loptu(fudbal). Slike možete preuzeti sa sajta:

pixabay.com/Posle ovih promena animacija fudbal igrice bi trebalo da izgleda kao što je prikazano u nastavku:

Interaktivna animacija: animacija sa starog sajta (processing.js) nije preneta na novi sajt. Kod i objašnjenje su u nastavku, a skeč možeš da pokreneš u Processing-u.

Dodavanje slika unutar scatch-a u processing-u

Slike je najbolje kreirati unutar foldera pod imenom "data", a koji treba da se nalazi unutar korenog foldera aplikacije. Možete kreirati ovaj folder samostalno unutar file explorer-a ili iz PDE okruženja:

Dodavanje slika u processing-u
Slika 1: Dodavanje slika u processing-u

Dodaćemo dve slike unutar "data" foldera i dati im prikladno ime:

Dodavanje slika u processing-u. Data folder
Slika 2: Dodavanje slika u processing-u. Data folder

Ove slike će unutar scatch-a biti kao objekti klase PImage.

Dodajmo prvo sliku pozadine. Unutar glavnog scatch-a treba prvo deklarisati objekat koji će predstavljati pozadinsku sliku, a klase PImage (pozadina). Zatim ćemo dovršiti kreiranje ovog objekta unutar setup metode, a pomoću processing-ove metode loadImage("stadion.jpg) kojoj treba proslediti naziv fajla pozadinske slike sa odgovarajućom ekstenzijom i na kraju postaviti sliku unutar draw metode pomoću processingove metode background(pozadina), kao što se može videti na sledećoj slici (podvučeno crvenom linijom):

Dodavanje pozadinske slike u processing-u
Slika 3: Dodavanje pozadinske slike u processing-u

Veličina slike treba da bude ili ista kao i veličina okvira definisanog metodom size, ili ako nije ista, onda metodom resize podesiti da veličina slike bude ista kao veličina okvira. U ovom primeru su obe veličine, slike i okvira, podešene na 1200 * 654 pixela.

Dodavanje slike lopte(fudbala)

Slično kao i za sliku pozadine, potrebno je deklarisati PImage objekat, kreirati objekat unutar setup metode korišćenjem processingove loadImage() metode i na kraju, unutar draw metode, podesiti veličinu slike, a zatim i iscrtati sliku.

Kreiranje slike lopte najbolje je uraditi unutar klase Lopta, tako da se kod za iscrtavanje slike lopte u ovom slučaju piše u render metodi klase Lopta, a poziva iz glavnog scatch-a, iz draw metode. Nov kod, u klasi Lopta je podvučen zelenim linijama, što se može videti na slici ispod:

Kod sa slike 4: Dodavanje slika u processingu - slika lopte

Isečak koda – prikazan je samo deo programa koji se menja.

class Lopta {
  float r;                    // geometrijska veličina – poluprečnik
  PVector polozaj;
  PVector v;
  PVector g;
  PVector pocetniPolozaj;
  PVector v0;
  PImage slikaLopte;          // ← novo: deklaracija objekta slike lopte

  public Lopta(float r, float x, float y) {
    this.r = r;
    polozaj = new PVector(x, y);
    g = new PVector(0, G);
    pocetniPolozaj = polozaj.get();
    slikaLopte = loadImage("fudbal.png");   // ← novo: kreiranje objekta slike lopte
  }

  public void kretanje() {
    PVector a = PVector.div(g, FrameRate);
    v.add(a);
    PVector dv = PVector.mult(v, RAZMERA);
    polozaj.add(dv);
    // println("v.y=" + v.y);
  }

  public void render() {
    fill(0, 0, 255);
    imageMode(CENTER);                                    // ← novo: mod za iscrtavanje slike – centar
    image(slikaLopte, polozaj.x, polozaj.y, r * 2, r * 2); // ← novo: crta loptu sa centrom u vektoru položaja
    // ellipseMode(CENTER);
    // ellipse(polozaj.x, polozaj.y, 2 * r, 2 * r);
  }
  // ...

Nože se uočiti da je mod za crtanje slike, postavljen na CENTER, , a u pozivu image() metode, prosleđena prva dva parametra odgovaraju x i y koordinati položaja, što znači da će to u ovom slučaju biti centar slike,. Širina i visina okvira slike će biti jednaki prečniku kruga, što je definisano 3. i 4. parametrom koji se prosleđuje metodi image().

Pomeranje linije tla

Vidimo da je kao pozadinska slika stavljena slika stadiona koja predstavlja stadion u 3D prostoru, dok mi kreiramo igricu u 2D, pa lopta koja opisuje kosi hitac to izvršava u ravni normalnoj na tlo terena. Lopta se odbija o donju ivicu okvira , a ne u liniji koja bi prolazila na onoj visini na kojoj se nalaze golovi. Zbog toga, da bi delovalo da lopta leti ka golu, pomerićemo liniju tla da ide po sredini gola. Uvešćemo konstantu VISINA_TLA i ovu vrednost postaviti na 180px. Ova vrednost se po potrebi može menjati. Promenićemo graničnu vrednost za odbijanje tako da se lopta odbija kad dođe na visinu linije tla. Slika animacije bi izgledala kao na slici ispod:

Fudbal igrica u processing-u. Pomeranje linije tla
Slika 5: Fudbal igrica u processing-u. Pomeranje linije tla

Kod koji treba promeniti obojen je crvenom bojom i prikazan ispod. Unutar glavnog scatch-a:

Kod sa slike 6: Fudbal igrica u processing-u. Dodavanje visine tla

Isečak koda – prikazan je samo deo programa koji se menja.

int FrameRate = 20;
PVector brzina;                       // m/s
static final float RAZMERA = 4;       // 10 px = 1 m
Lopta lopta1;
static final float G = 9.81;
PImage pozadina;                      // deklaracija objekta pozadine
static final int VISINA_TLA = 180;    // ← novo: pomeranje linije tla u odnosu na donju granicu okvira

void setup() {
  size(1200, 654);
  brzina = new PVector(3, -8);
  lopta1 = new Lopta(8, 30, 3 * height / 4);
  lopta1.v = brzina;
  lopta1.v0 = brzina.get();
  frameRate(FrameRate);
  pozadina = loadImage("stadion.jpg");  // kreiranje objekta slike pozadine
}

Promena unutar klase Lopta:

Kod sa slike 7: Fudbal igrica u processing-u. Promena graničnih uslova

Isečak koda – prikazan je samo deo programa koji se menja.

  // ... (klasa Lopta)
  public void render() {
    fill(0, 0, 255);
    imageMode(CENTER);
    image(slikaLopte, polozaj.x, polozaj.y, r * 2, r * 2);
  }

  void granicniUslovi() {
    if (v.x > 0 && width - polozaj.x < r || v.x < 0 && polozaj.x < 0) {
      v.x *= -0.8;            // promena pravca i gubitak energije
    }
    if (v.y > 0 && height - polozaj.y < r + VISINA_TLA) {  // ← novo: granica odbijanja je pomerena za visinu tla
      v.y *= -0.8;            // gubitak energije
    }
    // Uslov da se zaustavi
    if (abs(v.y) < 0.3 && height - polozaj.y - r < -5) {
      reset();
    }
  }

Simulacija šuta lopte. Definisanje slučajne vrednosti intenziteta i pravca vektora početne brzine lopte

Da bi simulirali šut lopte, definisaćemo vrednost intenziteta vektora početne brzine, kao i pravac pomoću random funkcije.

Pravac možemo da definišemo preko nagiba vektora početne brzine u odnosu na pravac x ose.

Random funkcija je objašnjena na web strani: processing.org/reference/random_.htmlX komponenta brzine i vrednost ugla se dobijaju kao slučajne vrednosti, a y komponenta brzine se može odrediti iz trougla koji zaklapa vektor brzine sa svojim komponentama, a preko tangensa ugla.(videti u kodu, na slici ispod). Ove vrednosti se računaju dva puta: prvi put unutar setup metode, a drugi put unutar reset metode u klasi Lopta. Na sledećim slikama su prikazane ove izmene i podvučene su crvenim linijama:

Kod sa slike 8: Fudbal igrica u processing-u. Promena vektora početne brzine. Upotreba random metode.

Isečak koda – prikazan je samo deo programa koji se menja.

int FrameRate = 20;
PVector brzina;                       // m/s
static final float RAZMERA = 4;       // 10 px = 1 m
Lopta lopta1;
static final float G = 9.81;
PImage pozadina;                      // deklaracija objekta pozadine
static final int VISINA_TLA = 180;    // pomeranje linije tla u odnosu na donju granicu okvira

void setup() {
  size(1200, 654);

  float x = random(3, 10);                // ← novo: slučajan broj između 3 i 10
  float alfa = radians(random(20, 75));   // ← novo: ugao između 20° i 75°, pretvoren u radijane
  float y = -x * tan(alfa);               // ← novo: y komponenta brzine

  // brzina = new PVector(3, -8);
  brzina = new PVector(x, y);             // ← novo
  lopta1 = new Lopta(8, 30, 3 * height / 4);
  lopta1.v = brzina;
  lopta1.v0 = brzina.get();
  frameRate(FrameRate);
  pozadina = loadImage("stadion.jpg");    // kreiranje objekta slike pozadine
}

Unutar klase Lopta se menjaju reset metoda i metoda granicniUslovi, što se može videti na sledećoj slici:

Kod sa slike 9: Fudbal igrica u processing-u. Promene u klasi Lopta

Isečak koda – prikazan je samo deo programa koji se menja.

  // ... (klasa Lopta)
  void granicniUslovi() {
    if (v.x > 0 && width - polozaj.x + 100 < r || v.x < 0 && polozaj.x < 0 - 100) {  // ← novo: lopta izađe 100 px van ekrana
      // v.x *= -0.8;         // promena pravca i gubitak energije
      reset();                // ← novo: novi šut
    }
    if (v.y > 0 && height - polozaj.y < r + VISINA_TLA) {  // granica odbijanja je pomerena za visinu tla
      v.y *= -0.8;            // gubitak energije
    }
    // Uslov da se resetuje (isključen)
    /* if (abs(v.y) < 0.2 && height - polozaj.y - r - VISINA_TLA < -5) {
      reset();
    } */
  }

  void reset() {
    polozaj = pocetniPolozaj.get();
    g = new PVector(0, G);

    float x = random(3, 10);                // ← novo
    float alfa = radians(random(20, 75));   // ← novo
    float y = -x * tan(alfa);               // ← novo

    v = new PVector(x, y);                  // ← novo
    // v = v0.get();
  }
}

Sada, kada lopta dođe do granice okvira levo ili desno, umesto da se odbije, simulira se novi šut, pozivom reset() metode. Ove granice su pomerene za 100px izvan vidljivog dela aplikacije.

Translacija i rotacija koordinatnog sistema u processing-u

U primeru kosog hica lopte, slika lopte je iscrtana na rastojanju definisanom vektorom položaja u odnosu na koordinatni početak O, koji se nalazi u gornjem levom uglu ekrana, kao što se može videti na slici ispod(slika 10). Pomeranje objekata koji se iscrtavaju se mogu izvršiti i tako što se koordinatni sistem translatorno pomeri za određene vrednosti x i y u ravni za iscrtavanje(x1-O1-y1), a da se onda položaj objekta definiše u odnosu na novi koordinatni sistem. To se takođe može videti na sledećoj slici(slika 10).

Translacija koordinatnog sistema u Processing-u
Slika 10: Translacija koordinatnog sistema u Processing-u

Translacija koordinatnog sistema u Processing-u se izvršava pozivom translate metode processinga, gde se kao parametri prosleđuju x i y koordinate položaja centra lopte. U našem primeru koordinate položaja se čuvaju u promenljivoj "polozaj" i ovu translaciju treba izvršiti u metodi render() u našem slučaju, kao što je prikazano u kodu na slici 11.

Kod sa slike 11: Fudbal igrica -Translacija i rotacija u Processingu-kod.

Isečak koda – prikazan je samo deo programa koji se menja.

  // ... (klasa Lopta)
    polozaj.add(dv);
    fi = fi + w;
  }

  public void render() {
    fill(0, 0, 255);

    /* ellipseMode(CENTER);
    pushMatrix();
    translate(polozaj.x, polozaj.y);
    rotate(fi);

    ellipse(0, 0, 2 * r, 2 * r);
    popMatrix(); */

    imageMode(CORNER);        // mod za iscrtavanje slike – gornji levi ugao
    if (slikaLopte != null) {
      pushMatrix();           // pamti postojeće stanje koordinatnog sistema

      translate(polozaj.x, polozaj.y);
      rotate(fi);

      image(slikaLopte, -r, -r, r * 2, r * 2);  // crta loptu sa centrom u novom koordinatnom početku
      popMatrix();            // vraća prethodno stanje
    }
  }

imageMode je postavljeno na CORNER, što znači da će vrednosti koje se šalju kao prva dva parametra metodi image(), predstavljati koordinate ugla slike. S obzirom da je koordinatni početak premešten u centar lopte (polozaj.x, polozaj.y), onda je ugao slike pomeren za r i po x i po y osi. Širina i visina okvira slike su jednake prečniku lopte(2*r), što je predstavljeno 3. i 4. parametrom u pozivu image() metode processing-a. Ove promene položaja koordinatnog sistema su privremene, tj. važile bi samo za iscrtavanje slike lopte, a ne i za eventualno dalja iscrtavanja unutar render metode. To je obezbeđeno pozivima pushMatrix(), na početku promena, a popMatrix() na kraju, tako da se koordinatni sistem posle poziva popMatrix() vraća u početno stanje. Više objašnjenja o upotrbi pushMatrix() i popMatrix() pročitajte na web lokaciji: learningprocessing.com/examples/chp14/example-14-17-nested-push-popDa bi kretanje lopte što vernije bilo prikazano, potrebno je uvesti rotaciju lopte tokom vremena za neki ugao(fi). Kao što se koordinatni sistem može translirati, privremeno(upotreba pushMatrix() i popMatrix()) ili do kraja izvršenja draw metode, može se takođe, koordinatni sistem u kojem se lopta iscrtava, rotirati za neki ugao pre nego što dođe do samog iscrtavanja. To radi metoda rotate() kojoj se kao parametar prosleđuje ugao rotacije(fi) u radijanima. Lopta će se onda rotirati oko ose normale na 2D ravan u kojoj se iscrtava animacija, a koja prolazi kroz trenutni koordinatni početak. Dodatak koda u render() metodi koji vrši rotaciju se vidi na slici 11, a položaj koordinatnog sistema koji je zarotiran, je prikazan u nastavku, na slici br. 12.

Rotacija koordinatnog sistema u Processing-u
Slika 12: Rotacija koordinatnog sistema u Processing-u

Ukoliko se pre rotacije, ne translira koordinatni sistem, onda bi se rotacija izvršila oko ose koja prolazi kroz tačku u gornjem levom uglu ekrana, jer bi onda to bio koordinatni početak, ako u međuvremenu ne bi bio premešten nekom drugom translacijom.

Promena, ugla fi se vrši u metodi kretanje, a početna vrednost zadaje u konstruktoru, što se može viditi na sledećoj slici:

Kod sa slike 13: Processing -Dodavanje ugaone brzine, ugla rotacije i njihove promene

Isečak koda – prikazan je samo deo programa koji se menja.

  // ... (klasa Lopta)
  PVector g;
  PVector pocetniPolozaj;
  PVector v0;
  PImage slikaLopte;          // deklaracija objekta slike lopte
  float w;                    // ← novo: ugaona brzina
  float fi;                   // ← novo: ugao

  public Lopta(float r, float x, float y) {
    this.r = r;
    polozaj = new PVector(x, y);
    g = new PVector(0, G);
    pocetniPolozaj = polozaj.get();
    // slikaLopte = loadImage("files/theme/files/images/fudbal.png");  // lokalno
    slikaLopte = loadImage("files/theme/images/fudbal.png");          // kreiranje objekta slike lopte
    w = 0.1;
    fi = 0;                   // ← novo
  }

  public void kretanje() {
    PVector a = PVector.div(g, FrameRate);
    v.add(a);

    PVector dv = PVector.mult(v, RAZMERA);

    polozaj.add(dv);
    fi = fi + w;              // ← novo: ugao se povećava za ugaonu brzinu
  }

Promena ugla je za vrednost ugaone brzine u rad/s, definisane kao promenljiva w. Izračunavanje ugaone brzine w će se izvršiti kad se lopta odbije o tlo, jer je potrebna vrednost vektora v u tom trenutku. Kod je prikazan na slici 14.

Kod sa slike 14: Processing -Fudbal igrica, Izračunavanje ugaone brzine.

Isečak koda – prikazan je samo deo programa koji se menja.

  // ... (klasa Lopta)
  void granicniUslovi() {
    if (v.x > 0 && width - polozaj.x + 50 < r || v.x < 0 && polozaj.x < 0 - 50) {
      // v.x *= -0.8;         // promena pravca i gubitak energije
      reset();
    }
    if (v.y > 0 && height - polozaj.y < r + VISINA_TLA) {  // granica odbijanja je pomerena za visinu tla
      v.y *= -0.8;            // gubitak energije
      w = v.x * RAZMERA / r;  // ← novo: ugaona brzina posle udarca u tlo (kotrljanje: w = v / r)
    }
    // Uslov da se resetuje (isključen)
    /* if (abs(v.y) < 0.2 && height - polozaj.y - r - VISINA_TLA < -5) {
      reset();
    } */
  }

  void reset() {
    polozaj = pocetniPolozaj.get();
    g = new PVector(0, G);
    // ...

Ugaona brzina se izračunava tako što se posmatra da sve tačke na lopti rotiraju oko jedne nepokretne tačke(trenutni pol rotacije), a to je tačka dodira lopte sa tlom(vidi ravno kretanje krutog tela). Animacija fudbal igrice bi sada trebalo da izgleda kao što je prikazano u nastavku:

Proveri znanje

Otvori kviz

Kviz: Fudbal igrica

1. U koji folder skeča se stavljaju slike?




2. Čemu služi random() u igrici?




3. Šta radi translate(x, y)?




4. Zašto se lopta crta posle translate i rotate?