Objavljeno: 28.1.2014 | Avtor: Ciril Bohak, Matevž Pesek | Monitor Februar 2014

Kako si poenostaviti delo

Razvijalci izdelujejo programske knjižnice predvsem zato, da si olajšajo izvajanje nekaterih opravil, ki sicer od programerja terjajo veliko dodatnega dela. Programske knjižnice združujejo funkcionalnosti, ki jih uporabimo večkrat in si tako poenostavimo implementacijo določenih funkcionalnosti. Kot za vsak programski jezik je tudi za JavaScript na voljo precej knjižnic, ki so se skozi čas uveljavile in nekatere razvile celo tako zelo, da si včasih razvoj spletnih aplikacij brez njih le še težko predstavljamo. Med najbolj priljubljene javascriptne knjižnice sodi knjižnica jQuery, seveda pa je še kar nekaj drugih, ki jih spletni programerji le stežka pogrešamo pri razvoju svojih aplikacij. Nekaj takih knjižnic bomo predstavili v današnjem članku, z njihovo pomočjo pa bomo izdelali tudi preprosto igro.

Kaj so programske knjižnice

Programske knjižnice (angl. library) so zbirke programskih funkcij, ki združujejo sorodne funkcionalnosti, ki nam pomagajo pri hitrejši implementaciji novih funkcionalnosti v naše spletne aplikacije. Za večino programskih jezikov je na voljo kup knjižnic, namenjenih najrazličnejšim stvarem. Najpogosteje uporabljene so knjižnice za delo z vhodom in izhodom, ki nam poenostavijo uporabo miške, tipkovnice ali kakšne tretje naprave, pa tudi matematične knjižnice, ki nam poenostavijo izračun zahtevnejših formul.

Tudi v spletnem programiranju so se dokaj kmalu uveljavile prve knjižnice za pomoč pri razvoju spletnih aplikacij. Najbolj priljubljene nam poenostavijo manipulacijo z dokumentom HTML, dostop do posameznih delov dokumenta, spreminjanje posameznih lastnosti in podobno. Takšna knjižnica je na primer v nadaljevanju predstavljena knjižnica jQuery, ki nam olajša kup opravil, povezanih s spreminjanjem tako vsebine kot videza spletnih strani. Pri razvoju vmesnikov spletnih aplikacij nam priskoči na pomoč knjižnica jQueryUI, s katero lahko hitro zgradimo uporabniški vmesnik spletne aplikacije in ga prikrojimo svojim potrebam.

Pri razvoju igric nam dostikrat pridejo prav knjižnice, ki nam olajšajo implementacijo posameznih konceptov igre. Zelo pogosto uporabljena knjižnica pri razvoju iger, namenjena implementaciji fizike, je Box2D. Knjižnica je omejena na dvodimenzionalne svetove, omogoča pa nam implementacijo različnih fizikalnih konceptov, kot so: gravitacija, sile, trenje, trki ... Ne nazadnje je tudi dosti knjižnic, ki implementirajo celotne igralne pogone, za hitro implementacijo preprostih spletnih iger. Zgled take knjižnice, ki jo bomo danes zgolj predstavili, v naslednjih člankih pa bomo z njeno pomočjo začeli izdelovati preprosto ploščadno igro, je Quintus.

V okviru tokratnega članka bomo ob pomoči knjižnic jQuery in jQueryUI razvili preprosto igrico, kjer lovimo padajoče predmete s preprostim loparjem.

jQuery in jQueryUI

Knjižnica jQuery je sorazmerno majhna, a močna in zelo olajša delo z dokumenti HTML. Omogoča zelo preprosto naslavljanje izbranih delov dokumenta in spreminjanje njihovih lastnosti. Knjižnica omogoča tudi zelo enostavno implementacijo dela z dogodki, kot so kliki elementov, vlečenje elementov, odziv na vnos s tipkovnico.

Funkcionalnosti knjižnice bomo uporabili pri izdelavi naše igre. Delo nam bo olajšala pri prilagajanju velikosti posameznih komponent v igri, pri dodajanju zvoka v aplikacijo in pri nastavljanju lastnosti, kot je skrivanje mišjega kazalca.

Knjižnica jQueryUI predstavlja razširitev oz. nadgradnjo knjižnice jQuery in je namenjena hitri izdelavi preprostih uporabniških vmesnikov za spletne aplikacije. Knjižnica omogoča izdelavo menujev, prikaznih oken, gumbov in drugih komponent, poleg tega omogoča enostavno implementacijo odzivanja na dogodke, ki jih komponente vmesnika prožijo, kot so akcije ob kliku gumba, ob zaprtju dialoga in podobno.

Box2D

Knjižnica Box2D združuje funkcionalnosti za implementacijo dvodimenzionalne simulacije fizike v naše aplikacije. 2D simulacijo fizike si lahko predstavljamo tako s stranskega pogleda kot iz ptičje perspektive in knjižnica omogoča oboje. Box2D je knjižnica, ki je bila v osnovi razvita za programski jezik C++. Od leta 2006, ko je bila razvita osnovna knjižnica, je bila uspešno prenesena na številne razvojne platforme (ActionScript, Java, JavaScript) in je postala ena najbolj priljubljenih knjižnic za implementacijo 2D fizike. Največkrat je seveda uporabljena pri izdelavi iger, kjer je dobro izvedena in optimizirana fizika ključnega pomena za dobro igralnost in realističen občutek.

Knjižnica omogoča izdelavo fizikalnega sveta. Omogočimo lahko gravitacijo, ki ji določimo smer (s tem lahko ustvarimo tudi svetove, kjer deluje gravitacija povsem drugače – na primer postrani), določiti pa moramo tudi velikost sveta, znotraj katerega bomo računali fizikalne spremembe. Uporaba knjižnice je precej preprosta, omogoča pa tudi zaznavanje trkov in ustrezno odzivanje nanje.

Več podrobnosti o uporabi knjižnice najdete na domači strani http://box2d-js.sourceforge.net/, kjer so predstavljeni tudi zgledi rabe. Sami se knjižnici ne bomo pretirano posvečali, jo bomo pa v nadaljevanju uporabljali kot del igralnega pogona Quintus, ob pomoči katerega bomo skozi prihajajoče članke razvili našo končno igro.

Slika 1: Demonstracijska aplikacija uporabe knjižnice Box2D

Slika 1: Demonstracijska aplikacija uporabe knjižnice Box2D

Quintus

Namen igralnih pogonov je olajšati izdelavo iger določene oblike. Igralni pogon Quintus predstavlja igralni pogon za izdelavo 2D iger, razvit v obliki programske knjižnice v jeziku JavaScript. Igralni pogon je bil razvit z namenom, da omogoči zelo preprosto implementacijo iger za splet in mobilne naprave. Na voljo je pod odprto licenco in ga lahko za svoj razvoj uporablja vsakdo. Če ga želimo uporabljati, pa moramo kljub vsemu spoznati osnovne zasnove, ki jih bomo predstavili v prihajajočih člankih. Knjižnica Quintus za svoje delovanje uporablja tudi knjižnico Box2D za simulacijo fizike. Dotlej najdete več o pogonu na spletnem naslovu http://html5quintus.com/.

Slika 2: Demonstracijska aplikacija uporabe knjižnice Quintus za izdelavo preproste 2D ploščadne igre.

Slika 2: Demonstracijska aplikacija uporabe knjižnice Quintus za izdelavo preproste 2D ploščadne igre.

JavaScript in razredi

Prejšnji mesec smo na hitro predstavili osnove programskega jezika JavaScript. Tokrat bomo spoznali še nekaj lastnosti jezika, ki nam omogoča tudi uporabo nekoliko naprednejših programerskih konceptov, kot je predmetno usmerjeno programiranje. Ker razvoj programskega jezika ni bil načrtovan vnaprej, so se določeni koncepti s časom dodajali v jezik in predstavljajo nekoliko nenavaden pristop k izvedbi. Razred v javascriptu definiramo kot funkcijo, tako kot prikazuje spodnji zgled:

// deklaracija razreda

var ImeRazreda = function() {

   var imePrivatneSpremenljivke = “abc”;

   var imePrivatneFunkcije = function() {

      ...

   };

   this.imeJavneSpremenljivke = 123;

   this.imeJavneFunkcije = function() {

      ...

   };

};

// nov primerek razreda

var primerekRazreda = new ImeRazreda();

V zgornjem zgledu smo prikazali najpomembnejše zasnove predmetov. To so zasebne spremenljivke, zasebne funkcije, javne spremenljivke in javne funkcije. Vseh teh konceptov smo vajeni že iz programskega jezika Java, ki smo ga spoznali pri programiranju za Android. Omenjene lastnosti programskega jezika nam bodo olajšale izvedbo same igre.

Igra – Lovljenje kock

V tokratnem članku bomo predstavili izdelavo igre, v kateri z loparčkom lovimo padajoče predmete. Igra je predelava stare igre Catch ‘Em za operacijski sistem DOS, kjer smo lovili padajoče predmete in je prikazana na sliki 3. V našem primeru bodo to preproste kocke, kaj hitro pa je mogoče igro nadgraditi tudi tako, da kocke nadomestimo z drugimi predmeti. Igro bomo izvedli modularno, da omogočimo enostavno nadgradnjo, to bomo omogočili z razredno implementacijo. Za morebitne razširitve moramo tako zgolj prilagoditi ustrezne razrede in celotna igra bo še vedno delovala v skladu s pričakovanji. Končni videz igre je prikazan na sliki 4, kjer vidimo loparček, s katerim lovimo padajoče kocke. Cilj igre je ujeti vse kocke, ki padajo navzdol po zaslonu. V celotni igri imamo tri življenja, kar se kaže v treh loparčkih, če kdaj med igro pomotoma izpustimo kakšno kocko, s tem izgubimo življenje. Ko izgubimo vsa tri življenja, smo izgubili igro. Igro uspešno končamo, če poberemo vse padajoče kocke, ne da bi izgubili vsa življenja.

Slika 3: Igra Catch ‘Em za operacijski sistem DOS

Slika 3: Igra Catch ‘Em za operacijski sistem DOS

Igro v osnovi razdelimo na več delov. Del, ki se bo izvedel, ko se naloži spletna stran, na dele, ki se bodo izvajali ob določenih dogodkih (npr. ob premiku miške ali pritisku na tipko), in na dele, ki definirajo izris in funkcionalnost igre. V nadaljevanju bomo predstavili vsak del ločeno in predstavili njegovo delovanje.

Podobno kot v prejšnjem članku bomo tudi tokrat vse izrisovali na platno znotraj dokumenta HTML. Poskrbeli bomo tudi, da se velikost platna avtomatsko prilagaja velikosti okna brskalnika. Za to niso dovolj zgolj nastavitve širine in višine platna na 100 %, temveč je treba ob spremembi velikosti okna v JavaScriptu tudi popraviti velikost platna.

  height="100%">

V JavaScriptu je treba ob koncu nalaganja strani in ob spremembi velikosti okna klicati spodaj definirano metodo resizeCanvas. Tako poskrbimo, da se platno ne le raztegne na ustrezno širino in višino, temveč ima ob spremembi velikosti tudi ustrezno ločljivost (ustrezno število slikovnih pik).

function resizeCanvas(){

   $("#surface").attr('width', $(document).

      width() );

   $("#surface").attr('height',

      ($(document).height() ));

};

window.onresize = function() {

   resizeCanvas();

};

window.onload = function() {

   resizeCanvas();

};

Na kratko naj še razložimo delček kode v obliki $(...). Koda, ki se začne z znakom $, predstavlja klice knjižnice jQuery. S tem znotraj dokumenta poiščemo elemente, ki ustrezajo pogojem, podanim znotraj oklepajev. V zgornjem primeru $(“#surface”) iščemo element, ki ima pod atributom id, vrednost »surface«. V našem primeru je to platno, ki ga uporabljamo za izris. Temu elementu želimo nastaviti atributa višina in širina. V drugem primeru, $(document), pa se nanašamo na dokument, od katerega želimo pridobiti širino in višino s klicema ustreznih metod nad vrnjenim predmetom.

Za sledenje miške na zaslonu si pripravimo globalno tabelo, ki hrani položaj miške. Ob vsakem premiku miške znotraj dokumenta pa nato položaj miške osvežimo, kot je prikazano v spodnji kodi. Tudi tokrat za lažjo implementacijo uporabimo funkcionalnost knjižnice jQuery za pripenjanje odziva na določeno akcijo.

var currentMousePos = { x: -1, y: -1 };

$(document).mousemove(function(event) {

   currentMousePos.x = event.pageX;

   currentMousePos.y = event.pageY;

});

Ker želimo med igranjem igre skriti tudi mišji kazalec, si oglejmo, kako naredimo tudi to. Ko ga želimo v določenem trenutku zopet prikazati, enostavno spremenimo vrednost enega samega parametra.

var hideCursor = function() {

   $(body).css({'cursor' : 'none' });

};

Preostane nam še, da definiramo razrede, ki jih bomo uporabljali pri izdelavi same igre. Ti razredi so Igra - Game, Stopnja (nivo) - Level, Lopar - Racket, Škatla - Box. Razred Game vsebuje osnovne funkcionalnosti igre in osnovne parametre, ki se uporabijo ob zagonu nove igre. Parametre hranimo kot javne spremenljivke razreda, saj bomo morda želeli kdaj nekatere nastavljati ali brati tudi z drugih delov kode – npr. stanje igre za izračun zbranih točk v igri in podobno. Med te parametre sodijo število točk, število življenj, širina okolja, višina okolja, stopnja, ali se je igra začela, ali je igra končana, ali je igra na premoru, referenco na lopar, referenco na stopnjo, referenco na časovnik za poganjanje igre in tabelo z referencami na padajoče škatle. Dodali bi lahko seveda še množico drugih atributov, a to prepuščamo posameznemu bralcu, da izvede sam. Spodaj je kot zgled podanih zgolj nekaj definicij parametrov. Vhodni parameter cvs je referenca na platno – canvas, ki ga potrebujemo za izrisovanje.

var Game = function(cvs) {

   this.score = 0;

   this.width = $("#surface").width();

   this.racket = new Racket(this, this.

      context);

   ...

};

Poleg atributov vsebuje razred Game tudi metode, ki skrbijo za delovanje igre. Spodaj je predstavljeno ogrodje metod, ki jih vsebuje razred, vsaka pa ima lasten namen, predstavljen v nadaljevanju.

...

   this.gameLoop = function() { ... };

   this.update = function() { ... };

   this.draw = function() { ... };

...

Metoda gameLoop predstavlja glavno zanko igre, ki se izvede vsakih 20 ms. S tem poskrbimo, da v stalnih zaporednih časovnih korakih osvežimo stanje naše igre glede na predhodno stanje in uporabnikov vhod. Na vsakem časovnem koraku moramo osvežiti stanje igre – klic metode update –  in izrisati novo stanje igre – klic metode draw. Kaj točno se zgodi v omenjenih metodah, bomo predstavili nekoliko kasneje. V JavaScriptu lahko zaporedno izvajanje neke funkcije dosežemo tako, da uporabimo že vgrajeno funkcijo setInterval. Spodaj je prikazan zgled rabe v naši igri.

this.gameLoopInterval = setInterval(function() {

   ...

   game.update();

   game.draw();

   ...

}, 20);

V zgornjem zgledu v spremenljivko gameLoopInterval shranimo referenco na izvajanje glavne zanke, da lahko igro kasneje tudi prekinemo.

Znotraj metode update razreda game poskrbimo, da se osvežijo stanja vseh povezanih predmetov in poskrbimo za preverjanje, ali je katera od škatel preletela spodnji rob zaslona ali pa jo je igralec prestregel z loparjem. Prav tako poskrbimo, da v primernem trenutku skrijemo mišji kazalec in ga tudi prikažemo. V spodnjem izseku kode je prikazan del funkcije update, kjer osvežimo stanje vseh škatel in loparja in ob zaznanem trku med škatlo in loparjem sprožimo predvajanje zvočnega posnetka.

this.update = function() {

   this.racket.update();

   for (var j = 0; j < this.boxes.length;

      j ++) {

      this.boxes[j].update();

      ...

   }

   for (var a = 0; a < this.boxes.length;

      a ++) {

      if ( aabb( [this.boxes[a].x, this.

         boxes[a].y], ...  ) {

         ...

            $.playSound('audio/beep');

      }

   }

   ...

};

V zgornji kodi smo uporabili dve še neopisani funkciji. Prva je za zaznavanje trka med dvema škatlastima predmetoma v 2D prostoru, imenovana zaznava  trka AABB (angl. Axes Aligned Bounding Box), ki je predstavljena spodaj.

var aabb = function(min1, max1, min2, max2) {

   if (min1[0] > max2[0]) return false;

   if (min1[1] > max2[1]) return false;

   if (min2[0] > max1[0]) return false;

   if (min2[1] > max1[1]) return false;

   return true;

};

Druga je funkcija za predvajanje zvoka, ki jo vključimo v knjižnico jQuery. Avtor te funkcije je Alexander Manzyuk in je izdana pod odprtokodno licenco MIT. Podrobnejši opis te metode poiščite v spletu.

Veliko preprostejša je raba funkcije draw, kjer zgolj zbrišemo celotno platno in kličemo metode draw vseh odvisnih predmetov (škatel in loparja), zato je ne bomo posebej predstavljali. S tem smo končali predstavitev atributov in metod razreda Game in čas je, da se posvetimo še drugim razredom.

Razred Level ima zgolj nekaj zasebnih atributov in metodo start. S tem razredom nastavimo lastnosti posamezne stopnje igre. V naši igri imamo sicer samo eno stopnjo, a je s tem razredom pripravljena na razširitev na več stopenj, kjer z vhodnim parametrom nastavimo parametre zahtevnejše stopnje.

var Level = function(game, level) {

   ...

   this.start = function() {

      this.noBoxes = Number((this.level *

         Math.PI * 10).toFixed(0));

      this.speed = Number(Math.log(this.

         level * 100 * Math.PI).toFixed(0));

      this.size = 40 - 1/10 * this.level;

      this.color = "#" + (Math.ran

         dom()*1000000).toFixed(0);

   };

};

Z vhodnim parametrom, ki določa stopnjo stopnje (nivoja), lahko prilagodimo parametre, kot so število škatel, hitrost padanja, velikost in barva. Funkcije, s katerimi določamo te parametre, je dobro prilagoditi tako, da s časom ne postanejo prehitro prezahtevne, temveč se težavnost povečuje počasi. Takšna je npr. logaritemska funkcija, katere zahtevnost se s časom ne povečuje preveč, tako da igralca ne obremenimo prehitro. Tako igralec laže doseže zahtevnejše stopnje in več časa posveti igranju igrice. Ker razreda ne osvežujemo v vsakem časovnem koraku in ničesar ne izrisujemo, ne potrebujemo metod update in draw. Seveda je možno, da stopnje dopolnimo tudi z izrisom, na primer z izrisom različnih ozadij ali kakšne druge dekoracije, povezane s posamezno stopnjo.

Naslednji razred, ki ga bomo predstavili, je Racket (lopar). Ta hrani nekaj zasebnih atributov, ker pa lopar dejansko tudi izrisujemo, implementiramo tudi metodi update in draw. Metoda update skrbi, da se lopar izrisuje na ustrezni lokaciji glede na položaj mišjega kazalca, katerega položaj hranimo v globalni spremenljivki currentMousePos. Metoda draw pa poskrbi, da se lopar ustrezno izriše glede na število življenj, ki jih ima posameznik v nekem trenutku.

var Racket = function(game, ctx) {

   this.width = 100;

   this.height = 15;

   ...

   this.update = function() {

      this.x = currentMousePos.x - this.

         width/2;

      this.y = $("#surface").height() - 4 *

         this.height;

   };

   this.draw = function() {

      this.context.fillStyle = this.color;

      for (var i = 0; i < this.game.lives;

         i ++)

         this.context.fillRect(this.x,

            this.y - i * 20,

            this.width, this.height);

   };

};

Seveda bi lahko izris loparja še nadalje prilagodili tako, da bi se v primeru, da pobere določeno škatlo,  razširil ali skrčil, spremenil barvo ob trku in podobno. Pri implementaciji novih stvari smo omejeni zgolj z lastno domišljijo.

Zadnji razred, ki ga bomo predstavili, je Box (škatla). Predstavlja posamezne padajoče škatle, ki jih mora igralec prestreči, preden dosežejo spodnji rob zaslona. Razred podobno kot razred za lopar vsebuje nekaj zasebnih atributov in pa metodi update in draw. V metodi update poskrbimo, da se posamezna škatla pomakne navzdol za ustrezno razdaljo glede na to, kakšno hitrost ima pripisano. V metodi draw pa poskrbimo, da se škatla z ustrezno barvo izriše na ustrezno mestno na platnu.

var Box = function(ctx) {

   this.speed = 10;

   ...

   this.update = function() {

      this.y = this.y + this.speed;

   };

   this.draw = function() {

      this.context.fillStyle = this.color;

      this.context.fillRect(this.x, this.y,

         this.width, this.height);

   };

};

Kot vse druge izrisljive stvari lahko seveda prilagodimo tudi izris škatel. Na tem mestu lahko izris preprostega pravokotnika nadomestimo s poljubno slikico. Tako lahko igro predelamo v lovljenje padajočih živali, denarja ali kakšnih drugih predmetov in s tem še dodatno popestrimo igranje igre.

Slika 4: Prikaz končne podobe igre

Slika 4: Prikaz končne podobe igre

Tako smo prišli do konca implementacije igre ob pomoči programskih knjižnic. V članku smo predstavili, čemu so namenjene programske knjižnice, jih nekaj predstavili in prikazali tudi izdelavo nekoliko zahtevnejše igre kot v prejšnjem članku, a še vedno dokaj preproste za razumevanje. Vso programsko kodo najdete na spletnem naslovu http://html5.monitor.si. Tako smo oboroženi z novim znanjem in pripravljeni na zahtevnejše podvige, ki sledijo v prihajajočih člankih.

Naroči se na redna tedenska ali mesečna obvestila o novih prispevkih na naši spletni strani!

Komentirajo lahko le prijavljeni uporabniki

 
  • Polja označena z * je potrebno obvezno izpolniti
  • Pošlji