Drugo orodje, ista želja
Doslej smo se posvečali razvoju iger za okolje Android. V dveh razsežnostih smo izdelali igro Space Invaders (Monitor, marec–september 2012), nato smo se posvetili razvoju grafično bolj dovršene različice igre Asteroids (Monitor, februar–november 2013). Igri smo dodali tretjo dimenzijo in bolj dovršen prikaz. S tokratnim vodnikom ne bomo spremenili le teme igre, temveč tudi tehnologijo. Ta članek je uvod v razvoj programske opreme v okolju HTML5. Ker so nam igre še vedno blizu, se bomo tudi tokrat usmerili v spoznavanje okolja ob pomoči programiranja krajših iger. Tokrat bomo z osnovnimi gradniki sestavili igro križcev in krogcev, končni cilj tokratnega vodnika v svet programiranja pa bo arkadna igra v slogu dobro znanih iger Super Mario.
HTML je svojčas veljal za enega izmed po svoje bolj »nepriljubljenih jezikov«. Štejemo ga med označevalne jezike, saj z njim le opisujemo strukturo in ne algoritmov. Njegova nepriljubljenost delno po krivici izvira iz tega, da vizualna predstavitev zapisa HTML, ki se uporablja za strukturizacijo vsebine spletnih strani, ni enaka na vseh brskalnikih. Razvijalci spletnih strani zaradi primoranosti v rabo niso imeli dosti izbire poleg nadaljevanja rabe HTML. Čeprav delež krivde nosi tudi HTML s svojimi razmeroma ohlapnimi pravili, lahko z gotovostjo kažemo s prstom tudi na razvijalce brskalnikov. Slednji si pravila pogosto razlagajo po svoje, to pa razvijalcem povzroča preglavice. Svojčas smo za takšne težave krivili Microsoftov Internet Explorer, a se je v zadnjih različicah precej izboljšal. Še vedno nas pestijo težave obratne združljivosti (angl. backward compatibility), saj sorazmeren delež uporabnikov ne osvežuje programske opreme. Občasno lahko dandanes naletimo na podobne težave tudi pri brskalniku Firefox, ki je v Sloveniji med najbolj priljubljenimi.
HTML5 v korak s časom
S HTML5 smo si razvijalci obetali spremembe: poenotenje videza, razširitev nabora funkcionalnosti, uvedbo podpore večpredstavnim vsebinam (na primer gradniki za zvok in sliko). V praksi seveda ni tako: vizualno se prikaz med brskalniki še vedno razlikuje, a manj kot v pradavnini, ko smo uporabljali Internet Explorer 6.5. Težave z neenotnostjo pa so se na primer pri uvedbi zmožnosti vnosa avdio ali video vsebin razširile v pravno zagato licenčnin za standarde zapisa vsebine. Če želimo torej v obliki značke HTML dodati na svojo spletno stran zvočno datoteko, naloženo na strežnik, kjer gostuje naša spletna stran, bomo morali poskrbeti za vsaj dva različna zapisa avdio signala (recimo mp3 in ogg), če bomo želeli pokriti najbolj rabljene brskalnike. Saga o neenotnosti se torej nadaljuje. Navdušenci za odprtokodnost prigovarjajo k rešitvi z odprtimi standardi, razvijalci brskalnikov pa vztrajajo pri rabi licenčnih standardov, saj to potencialno prinaša nadzor nad razvojem in vpliv nanj ter denar pri licenčnini.
Kljub temu nam HTML5 daje več zmožnosti kot pretekle različice. Pravi standard še čakamo, saj sta se že pred časom dva izmed največjih akterjev – W3C (World Wide Web Consortium) in WHATWG (Web Hypertext Application Technology Working Group) – sporekla o načinu razvoja standarda. Težave so v nedorečenosti, kaj je standard HTML. V teoriji so povabljeni k sodelovanju večji igralci na področju razvoja spletnih brskalnikov, ki podajajo svoje predloge. V praksi nekateri razvijalci hitijo z izvedbo novih zmožnosti, drugi pa z implementacijami malce počakajo in jih izdajo v naslednji različici brskalnika.
Hitenje z uvedbo novih zmožnosti ima tudi negativne posledice. Primerljivi brskalniki s hitrimi razvojnimi cikli imajo pogosto več hroščev, ki nam povzročajo preglavice. Na primer, ena izmed zadnjih različic brskalnika Chrome ni več pravilno delovala ob uporabi certifikatov. Za povprečnega uporabnika to pomeni težave pri plačevanju položnic. Za državljana kakšne naprednejše države pa lahko to pomeni nezmožnost oddaje glasu na volitvah.
Slika 1: Diagram prikazuje razvoj HTML, CSS in JavaScript. Ker gre pri vseh za živ standard, so natančni mejniki stvar debate, saj so predlogi na voljo več let pred prvimi implementacijami.
Kaj lahko naredimo s HTML?
Jezik HTML je sestavljen iz značk (angl. »tag«), ki jih zapisujemo z znakoma »<« in »>« ter imenom v sredi. V osnovnem naboru imamo med značke, in, ki sestavljajo osnovno strukturo dokumenta HTML. Poleg slednjih je na voljo še kup drugih značk. Nekatere uporabljamo za samo »predalčkanje« videza: med bolj priljubljenimi v preteklosti so bili okvirji (), te so kasneje izpodrinile tabele (), slednje pa zdaj uporabljamo večinoma le še za tabelaričen prikaz vsebine, saj položaj elementov urejamo z lastnostmi samih blokov (na primer z). Oglejmo si še oblikovanje slogov in delo z jezikom javascript.
Kaskadne slogovne podloge (ali CSS)
Za oblikovanje sloga se je dodobra udomačila raba stilskih podlog (angl. Cascading style sheets - CSS), ki omogočajo redefinicijo videza osnovnih značk HTML, definicijo lastnih značk in izoblikovanje lastnih definicij, ki jih lahko uporabimo za en ali več prikazanih elementov. CSS omogoča zmožnost prenosa natančnejšega opisa videza elementov iz lastnosti značk HTML v ločeno datoteko ali odsek v datoteki HTML. V praksi je slednje uporabno predvsem z vidika preglednosti, saj je lahko struktura v datoteki HTML preglednejša brez opisa lastnosti – slednje lahko »prilepimo« k elementom prek dodajanja lastnosti razreda ali identifikacijske oznake (angl. class ali id). Tudi lastnosti elementov so laže berljive. Tako lahko v urejenem blokovnem zapisu pregledujemo lastnosti, ki jih želimo dodati elementu, pri neposrednem zapisu v HTML pa bi delno izgubili preglednost. Kot zgled podajmo kodo, ki smo jo pred prihodom HTML5 in CSS pogosto srečevali:
<div height="500px" width="100px" align="center"><font size="3" color="green">Jaz sem zelen</font></div>
Zgled bi bil z rabo CSS in HTML5 videti precej lepše. V HTML5 so tudi nekatere značke in lastnosti, ki smo jih uporabili, nedovoljene, saj jih nadomešča CSS.
//css datoteka
#blok {
height:500px;
width:100px;
text-align:center;
text-color:green;
font-size:12px;
}
<!--html datoteka -->
<div id="blok">Jaz sem zelen</div>
Seveda lahko CSS zapišemo tudi kot del značke HTML. Za ta namen se uporablja atribut style, ki omogoča vnos enakega zapisa kot v datoteki CSS. V tem primeru ne potrebujemo definicije identifikatorja »blok«. V takem primeru bo videz CSS definiral le en specifičen element. Če v datoteki CSS spremenimo zapis #blok v .blok, bomo s tem definirali lastnosti razredu. Razred lahko ima več elementov hkrati. Razred znački HTML dodamo z atributom class="blok". Posamezen element lahko deduje lastnosti več razredov. V tem primeru v atribut class s presledki določimo želene razrede.
CSS pa poleg tega ponuja še veliko več. Tako lahko definiramo sloge HTML značkam, na primer, celoten videz <body> ali <a>. V marsikaterem primeru nas bi to motilo, saj si lahko želimo hipertekstovne povezave v glavi strani, ki predstavljajo menu, odebeliti drugače kot v besedilu, prikazanem na strani. V tem primeru lahko določimo hierarhično postavitev značke, ki jo želimo oblikovati. V primeru hipertekstovnih povezav lahko definiramo razred header, ki bo rabil kot vsebovalnik glavnega menuja. Povezave v menuju bomo definirali posebej, preostale povezave pa bodo definirane z redefinicijo značke HTML za povezavo.
a {
font-size:22px
color:#F0F;
}
#header a {
font-size:20px
color:#FF0;
}
Poleg teh opisanih zmožnosti vsebuje CSS še precej naprednejše zmožnosti.
Slika 2: Pri pregledovanju izvorne kode spletne strani si lahko v brskalniku Chrome pomagamo z dodatkom Developer Tools (na voljo tudi za Firefox). Slednjega prikažete z bližnjico na tipki F12. Z dodatkom lahko pregledujemo HTML strukturo, CSS podloge in trenutno izvajano JavaScript kodo na spletni strani.
Javascript
Javascript je skriptni programski jezik, ki ga interpretirajo vsi novejši brskalniki. Na splošno se javascript v brskalniku uporablja za izvedbo procedur na strani uporabnika. To nam omogoča interakcijo z uporabnikom, pa tudi odlaganje računsko razmeroma zahtevnih opravil v uporabnikovo strojno opremo. Javascript poleg imena nima dosti skupnega z javo. Sintaksa je mešanica vplivov programskih jezikov java, C in perl. V zadnjih letih je javascript doživel velikanski razmah. Zaradi uporabne vrednosti animacij in drugih interakcij z uporabnikom so skripti postajali vedno daljši, to pa je upočasnjevalo delovanje spletnih strani. Kmalu ni bila več dovolj le podpora jeziku javascript, zato so brskalniki med seboj odprli novo fronto v merjenju hitrosti vgrajenega interpreterja.
Zaradi široke rabe nekaterih dandanes standardnih elementov se je razširila tudi uporaba knjižnic. Knjižnice pohitrijo implementacijo standardnih interakcij z uporabnikom, obenem pa delno odstranijo težavo nezdružljivosti med brskalniki, saj je za to večinoma poskrbljeno v samih knjižnicah. Ne nazadnje razvijalcu knjižnice tudi omogočajo osredotočanje na preostali del problema, saj so namensko razviti elementi knjižnice navadno dobro optimizirani. Med pogosteje rabljene knjižnice spadata jQuery in Prototype. Tokrat se bomo orientirali le na uporabo standardnih funkcionalnosti jezika javascript. V prihodnje pa si bomo ogledali uporabo knjižnic, ki nam bodo precej olajšale delo pri interakciji.
Slika 3: Zgled izpisa primera v spletnem brskalniku Chrome. Okno dialoga je bilo svojčas namenjeno opozorilu uporabnika med izvajanjem spletne strani, dandanes pa se le redko uporablja.
Javascript lahko uporabljamo kot vsak drug programski jezik, na primer za izračun in obdelavo podatkov. Oglejmo si zgled seštevanja števil v polju in izpisu.
var a = [1,2,3,4,5,6];
var sum = 0;
for(i = 0; i < 6; i++)
sum = sum + a[i];
alert("vsota stevil je: " + sum);
Metodo alert smo uporabili za prikaz dialoga z izpisom vsote števil. Pri izdelavi igre pa si bomo pomagali z metodo getElementByID, ki jo vsebuje dokument. Z metodo pridobimo referenco objekta, ki obstaja v strukturi dokumenta. S spreminjanjem lastnosti lahko dosežemo vizualne spremembe, ki jih bomo tokrat potrebovali za izdelavo igre. Delovanje kode primera je prikazano na sliki 3.
Risanje in animacija
Pri razvoju igre si bomo pomagali z elementom HTML, ki predstavlja risalno platno z imenom . Na platno bomo sprva risali like, nato pa izrisali osnovno postavitev za igro križci in krogci.
style="border:1px #000 solid">
Naše platno naj bo široko 900 in visoko 900 slikovnih pik. Platno je sprva prazno, zato smo kot del sloga dodali robove. Rob smo definirali z ukazom CSS, ki določa debelino ene slikovne pike, črno barvo in nepretrgano črto za rob. Ob pomoči javascripta na platno izrišimo kvadrat.
var platno = document.getElementById("platno");
var povrsina = platno.getContext("2d");
povrsina.fillRect(300, 200, 500, 400);
V prvi vrstici koda pridobimo element glede na ID. Ker na sam element ne moremo risati, zahtevamo dvodimenzionalni kontekst našega platna. Slednje je na prvi pogled neumestno, a dokumentacija HTML pravi, da bo v prihodnje na voljo tudi tridimenzionalni kontekst platna. Klic dvodimenzionalnega konteksta torej rabi združljivosti v prihodnosti, to pa posledično zagotavlja stabilnost našega izdelka. Na platno bomo izrisali kvadrat, ki je podan s koordinato zgornjega levega in spodnjega desnega roba. Kvadrat bo zapolnjen z barvo. Ker barve nismo eksplicitno nastavili, se na platnu prikaže črno obarvan kvadrat.
Križci in krogci
Tokrat bomo enostavno izdelali zgled igre križci in krogci. V želji po čimprejšnjem rezultatu se bomo izognili rabi javascriptnih knjižnic, te si bomo ogledali prihodnjič. Prav tako bomo izpustili večino zmožnosti igre, kot so točkovanje, najboljši rezultati in druge. Igra, ki jo bomo izdelali, pa bo primerna za igranje dveh igralcev na istem računalniku. Nadzorovali jo bomo ob pomoči klikov na želena polja, pri čemer bomo izmenično dodeljevali potezo igralcema. Z malo dodatne motivacije pa lahko bralec igro hitro razširi. Preostale funkcionalnosti si bomo skozi spoznavanje razvoja v HTML5 v prihodnje ogledali na drugih zgledih.
Platno za risanje elementov smo že definirali. Preostane nam še izris polja in igralnih elementov (križcev ali krogcev). Polje bomo izrisali ob pomoči odebeljenih črt, ki jih bomo izdelali s pravokotniki. Platno bomo razdelili na devet enako velikih polj.
var platno = document.getElementById("platno");
var povrsina = platno.getContext("2d");
povrsina.fillRect(300, 0, 2, 900);
povrsina.fillRect(600, 0, 2, 900);
povrsina.fillRect(0, 300, 900, 2);
povrsina.fillRect(0, 600, 900, 2);
Polje, ki smo ga izrisali, naj bo statično. Izrisani igralni elementi so trenutno prav tako statični. Dodati moramo funkcionalnost izrisa ob pritisku na gumb miške na želeni kvadrat znotraj razdeljenega polja. Zajeli bomo koordinate pritiska in izračunali koordinate za izris igralnega elementa.
Glede na to, kateri igralec je na vrsti, moramo vizualno ločiti igralne elemente. Izbrali bomo dve barvi – modro in rdečo – in ju izmenično uporabili za izris elementov. Preverjati moramo, ali je izbrani kvadrat že zaseden, saj je v tem primeru poteza neveljavna. Ker je igra enostavna, lahko prav tako preverjamo konec igre – bodisi so vsi kvadrati zasedeni bodisi je eden izmed igralcev zmagal. V tem primeru lahko preprečimo nadaljnji vnos potez in prikažemo dialog za nov zagon igre (izbris elementov v polju).
Najprej moramo zajeti dogodek klika na platno. Poljubnemu elementu lahko dodamo poslušalca in mu podamo funkcijo, ki jo bo poslušalec v primeru dogodka poklical. V funkciji lahko izvedemo poljubno akcijo. V našem primeru bomo sprva obvestili o kliku in izpisali koordinate klika. Koordinate želimo pridobiti v odvisnosti od platna in ne celotne strani. Te koordinate bomo izračunali z razliko med koordinato klika in položaja platna v dokumentu html.
var platnoLevo = platno.offsetLeft;
var platnoZgoraj = platno.offsetTop;
var povrsina = platno.getContext('2d');
platno.addEventListener('click', function(event) {
var x = event.pageX - platnoLevo;
var y = event.pageY - platnoZgoraj;
alert('kliknil si na platno - x: ' + x + " y: " + y);
}, false);
Ob pritisku na platno se pravilno izpišejo koordinate. Ugotoviti pa moramo, na katerem polju na platnu želimo izrisati element. Poizkusimo z izrisom kroga. Kasneje bomo dodali še izmeničen izris križcev in elemente obarvali. Prav tako moramo dodati še preverjanje zasedenosti položaja klika.
Element lahko določimo z delitvijo s širino posameznega polja. Rezultat lahko zaokrožimo navzdol in dobimo rezultat. Številka polja bo v tem primeru med 0 in 2. To nas pri pri izpisu morda moti, zavoljo preverjanja zasedenosti pa je uporabno, saj se polja začnejo z odmikom 0.
var xpos = Math.floor(x / 300);
var ypos = Math.floor(y / 300);
alert('kliknil si na platno - x: ' +
xpos + " y: " + ypos);
Narišimo krog na želeni položaj. Položaj smo že izračunali, vrednost pa hranimo v spremenljivkah xpos in ypos. Krog lahko izrišemo kot krivuljo, ki ji določimo barvo in debelino. Z metodo arc() lahko izrišemo poljuben lok. V našem primeru bomo izrisali lok s središčem v sredini polja, ki je 150 slikovnih pik odmaknjena od roba v polnem krogu, med 0 in 2*Pi. Z metodo stroke() izrišemo želeno krivuljo na platno.
povrsina.beginPath();
povrsina.arc(xpos * 300 + 150,ypos * 300 +
150,100,0,2*Math.PI);
povrsina.lineWidth = 10;
povrsina.strokeStyle = 'black';
povrsina.stroke();
Posvetimo se še izrisu križca. Križec bomo izrisali kot dve diagonalni črti znotraj polja.
povrsina.beginPath();
povrsina.lineWidth = 10;
povrsina.strokeStyle = 'black';
povrsina.moveTo(xpos * 300 + 40, ypos *
300 + 40);
povrsina.lineTo(xpos * 300 + 280, ypos *
300 + 280);
povrsina.moveTo(xpos * 300 + 40, ypos *
300 + 280);
povrsina.lineTo(xpos * 300 + 280, ypos *
300 + 40);
povrsina.stroke();
Ker želimo elemente izrisovati izmenično, lahko določimo spremenljivko, ki izmenično nakazuje na stanje igralca na vrsti. Spremenljivko bomo povečali pri izrisu krogcev in zmanjšali pri izrisu križcev. Če je spremenljivka enaka 0, bomo izrisali krogce, drugače križce.
var turn = 1; //prvi bodo na vrsti križci
//znotraj poslušalca
if(turn == 0)
turn++; //izriši križec
else
turn--; //izriši krogec
Tako, igra že skoraj deluje, a še vedno omogoča nedovoljene poteze izrisa elementov na zasedena polja. Za elemente vsake vrstice bomo zgradili novo polje in hranili zasedenost.
var row = [[0,0,0],[0,0,0],[0,0,0];
//znotraj poslušalca
if(row[xpos][ypos] == 1) //nedovoljena poteza
return;
else
row[xpos][ypos] = 1;
Igra postaja bolj podobna končnemu izdelku. Manjka nam še gumb za ponastavitev polja in dodatne zaslonske informacije o igralcu, ki je na vrsti. S spremenljivko turn že nadziramo vrstni red igralcev. V dodatni blok ob platnu bomo izpisali igralca v barvi igralnih elementov. Ob vsakem novem premiku lahko ob pomoči javascripta spremenimo vsebino dodatnega bloka in pravilno obarvamo besedilo. Poleg platna bomo dodali blok <div> in vanj vpisali želeno stanje.
<div style="float:left; display:
inline-block; width:609px; font-size:55px;
padding-top: 400px;" id="turndiv">
<p style="color: #8B0000">Na vrsti so
križci.</p>
</div>
V poslušalcu bomo v kodo dodali klic elementa in spremembo vsebovanega HTML za krogce takole:
document.getElementById("turndiv").
innerHTML = '
Na vrsti so križci.
';
in za križce enako, a z napisom »na vrsti so križci«. Prav tako lahko na koncu spremenimo zapis v »igra je končana«. Ker potez ne preštevamo, moramo bodisi dodati spremenljivko za štetje potez, bodisi ob vsaki novi potezi preverjati zasedenost vseh devetih izrisanih polj. Lažja različica vsebuje le preštevanje. Na začetku javascript kode definiramo spremenljivko turncount, ki jo nastavimo na 0. Nato ob vsaki pravilno izvedeni potezi spremenljivko povečamo. Če je število potez enako 9, igro končamo z izpisom »igra je končana« v zeleni barvi.
//v poslušalcu dodamo
if(turncount == 9) {
document.getElementById("turndiv").
innerHTML = '
#008B00">IGRA JE KONČANA.
';
}
Preverjanje zmage
Igra deluje v popolnosti, a program ne preverja morebitne zmage. V želji po resnično dobri igri bomo dodali še preverjanje zmage v igri in možnost ponastavitve. Ker si zasedena polja shranjujemo v spremenljivko row, lahko namesto zasedenosti zapisujemo zasedenost križcev in krogcev. V poslušalcu bomo namesto zasedenosti (označevali smo jo s številko 1) zapisovali 1 za krogce in 2 za križce. To lahko enostavno implementiramo v trenutni kodi s spremenljivko turn, ki ji prištejemo 1.
Zdaj lahko preverjamo možne rešitve, ki jih je v primeru te igre le osem: tri vodoravne, tri navpične in dve diagonalni. Ob morebitni zmagi bomo z metodo alert obvestili igralce o predhodnem koncu igre. To informacijo bomo izpisali tudi namesto izpisa poteze v dodatnem bloku.
if( //vse možnosti - najprej vertikalne,
nato horizontalne, na koncu diagonale
row[xpos][0] == turn+1 && row[xpos][1] ==
turn+1 && row[xpos][2] == turn+1 ||
row[0][ypos] == turn+1 && row[1][ypos] ==
turn+1 && row[2][ypos] == turn+1 ||
row[0][0] == turn+1 && row[1][1] == turn+1
&& row[2][2] == turn+1 ||
row[0][2] == turn+1 && row[1][1] == turn+1
&& row[2][0] == turn+1) {
document.getElementById("turndiv").
innerHTML = '
#008B00">' + (turn+1 == 1 ? "Krogci so
zmagali" : "Križci so zmagali") + '
';
alert('zmaga ' + (turn+1 == 1 ? " Krogci
so zmagali" : " Križci so zmagali"));
gameover = 1;
}
S spremenljivko gameover bomo označevali konec igre in na podlagi te onemogočali nadaljnje igranje. Končna podoba naše igre je predstavljena na sliki 4
Slika 4: To je končna podoba tokratne mini igre. Prostora za izboljšave je še veliko, igra pa nam je rabila za učenje dela z javascriptom in platnom HTML5.
Počasi po poti do končne igre
Tokrat smo enostavno implementirali preprosto igro križcev in krogcev ob pomoči javascripta in platna HTML5. Naslednjič bomo šli še malo globlje. Ogledali si bomo uporabo knjižnic, ki nam bodo olajšale delo. Tudi končni izdelek bo uporabljal tokrat predstavljene koncepte. Naslednjič si bomo ogledali uporabo knjižnic in izdelali bolj dovršeno javascript igro. Do takrat pa nas lahko presenetite z razširitvami tokrat implementirane igre križcev in krogcev. Z malo dodatnega znanja in ob pomoči knjižnic bomo vpeljali fizikalni pogon in izdelali pravo arkadno igro.