Korja kiiresti 10 seent, aga hoidu kärbseseentest eemale!
Liikumine: nooleklahvid või AWDS

Jätkame eelmistes loengutes/harjutustes seeneline, animeeritud (üks vaade/suund), animeeritud (4 suunda) alatud mängu arendamist.

Spriidilehed ja mitmekihiline kanvaa
Selles loengus esitatakse:

See on varem käsitletud seenekorjamismängu parandatud-täiendatud variant - ka siin mängus peab mängija võimalikult ruttu korjama mänguväljalt seeni, kuid seeni on erinevaid, mänguväljak on ümbritsetud (erinevate) puudega ja mängija on animeeritud - ta sammub.

Spriidilehed

Brauserimängu väga oluline aspekt on serverilt resursside (piltide, helide) allalaadimine. HTML-i kommunikatsiooniprotokoll TCP/IP (Transmission Control Protocol / Internet Protocol) nõuab, et iga resursi laadimiseks peab klient (brauseri arvuti) saatma serverile nõudmise (request), server vastab sellele resursi saatmisega. Resursid saadetakse üksteise järel ja laadimise kiirus sõltub arvutivõrgu seisust (pole ette ennustatav), seega võib palju pilte ja helisid (helifaile) sisaldava mängu käivitamine võtta üsna palju aega. Igal pildi ja helifailil on lisaks olulise info (pildifailis - pikselite värviinfo) ka faili otsik (header) ja need kõik surendavad ülekantava info mahtu, mis näiteks mobiilseadmete puhul võib muutuda väga oluliseks.

Laadimist kiirendab oluliselt piltide kokkupakkimine üheks failiks - spriidileheks. Koos spriidilehega saadetakse ka väike tekstifail, kus on näidatud iga spriidi (pildi) jaoks selle nimi ja asukoht spriidilehel - vasaku ülanurga koordinaadid ja spriidi laius/kõrgus; selle info põhajal saab mänguprogramm ise vajalikul hetkel näidata vajalikku pilti (lehte pole tarvis 'lahti lõigata'). Seda tehnoloogiat kasutatakse ka näiteks CSS-stiililehtedes - WWW-lehe nuppudel olevad pildikesed laaditakse kõik ühe spriidilehena ja CSS-stiilileht näitab igal nupul just selle nupu jaoks mõeldud spriidilehe ala.

Kokkupakkimiseks (spriidilehe loomiseks) on loodud palju programme, näiteks vaba Sprite Sheet Packer (http://spritesheetpacker.codeplex.com/). Spriidilehe loomiseks küsib programm lehele lisatavate piltifailide asukohta ja ühendab siis pildikesed üheks spriidileheks ja loob selle kirjelduse tekstifailina:


trees
seen0 = 0 0 32 32 seen1 = 33 0 32 32 seen2 = 0 33 32 32 seen3 = 33 33 32 32

Selle kirjelduse põhjal koostatakse objekti seen loomisel seente koordinaatide vektor [[0,0,32,32],[33, 0, 32, 32],[0,33,32,32],[33, 33, 32, 32]].

Puud on seentest suuremad (spriidid 64x64px), seega ka spriidilehe kirjeldus on erinev:

tree0_64 = 0 0 64 64
tree1_64 = 65 0 64 64
tree2_64 = 0 65 64 64
tree3_64 = 65 65 64 64

Mängu käivitamisel laaditavate resursside (piltide) loetelus tuleb endiste üht puud/seent kujutavate piltide asemel näidata spriidilehed:

var sources = {
            trees: "images/trees_64.png",
            hero: "images/tydruk_ani.png",
            seened: "images/seened.png",
		};
window.onload = function() {
        loadImages(sources, initGame);  // calls initGame after *all* images have finished loading
    };

Spriidilehe laeb serverilt varem vaadeldud funktsioon loadImages:

function loadImages(sources, callback) {
        var images = {};
        var loadedImages = 0;
        var numImages = 0;
        // get num of sources
        for(var src in sources) {
          numImages++;
        }
        for(var src in sources) {
          images[src] = new Image();
          images[src].onload = function() {
            if(++loadedImages >= numImages) {
              callback(images);
            }
          };
          images[src].src = sources[src];
        }
      }

Spriidilehte (objekti) haldab funktsioon (siin on lihtsustamiseks piltide laius/kõrgus on sama):

function spriteSheet(img,coords){
	this.coords = coords;
	//coords = [[x0,y0,w0,h0],...[xn,yn,wn,hn]]
	this.sheet = img;
	this.x = 0;
	this.y = 0;
	this.width = this.coords[0][2]; //width of the first image
	this.height = this.coords[0][3]; //height of the first image
	this.nr = this.coords.length; //how many images?
	this.draw = function(nr,x,y){
		ctx.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.width,this.height,x,y,this.width,this.height);
	}
	this.draw1 = function(nr,x,y){ //joonistamine teisele kanvaale
		ctx1.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.width,this.height,x,y,this.width,this.height);
	}
}

Selle parameeter draw joonistab seene numbri (0,1,2,3) ja koordinaatide x,y põhjal kanvaale vajaliku seene, kasutades massiivis coords kirjeldatud seene asukoha kirjeldust [xn,yn,wn,hn] spriidilehel - xn,yn on seene ülanurga koordinaadid spriidilehel ja wn, hn - seene laius ja kõrgus; iga massiivi coords element [xn,yn,wn,hn] vastab pakkimisprogrammi poolt poolt loodud kirjelduse (vt ülal) reale.

Mängu uues versioonis kasutatakse kaht (animeerimata) spriidilehte - ühes on seened, teises (erinevad) puud; neist moodustatakse mängu objektid; koordinaatide massiivid on copy-paste spritesheetpackeri loodud tekstifailist:

tree = new spriteSheet(images.trees,[[0,0,64,64],[65 0 64 64],[0 65 64 64],[65 65 64 64]]);
seen = new spriteSheet(images.seened,[[0,0,32,32],[33, 0, 32, 32],[0,33,32,32],[33, 33, 32, 32]]);

Mitmekihiline kanvaa

Mängu igas kaadris liigub kangelane uude kohta, sellepärast puhastatakse funktsiooni render() algul kogu kanvaa ja joonistatakse siis kangelane ja muud objektid uues kohas:

function render() {
	ctx.clearRect(0,0,w,h);
	drawtrees();
	ctx.drawImage(hero.sprite, hero.x, hero.y);
	ctx.drawImage(currentseen, seen.x, seen.y); //currentseen - number of cuurently used sprite
	showScore("Seeni: " + collected);
}

Puhastamine eemaldab ka puud. Kuid nüüd hakkavad puud vilkuma, sest funktsioon drawtrees valib joonistatava puu juhuslikult, seega igas kaadris tekivad erinevad puud. Et (staatilisi) puid poleks tarvis igas kaadris joonistada, on kõige lihtsam kasutada mitmekihilist kanvaad - puhastatakse kiht, kus on kangelane ja seen(ed), kuid puude kihile joonistatkse vaid kord ja seda hiljem enam ei puhastata.

Mitmekihilise kanvaa saab dokumendis moodustada CSS stiilide abil. Asendame mängu html-dokumendis endise kanvaa kirjelduse sellisega:

<div  style ="width: 640px; height:530px;position:relative;float:right;" >
<canvas id="gamecanvas" width="640" height="512" style="z-index: 0;position:absolute;left:0px;top:0px;background:url('images/taust.png') "></canvas>
<canvas height="512" width="640" id="gamecanvas1" style="z-index: 1;position:absolute;left:0px;top:0px;right:0px;width:640px;"></canvas>
<div id="inf" style="position:relative;top:512px;left:0px;width:640px; padding:10px;text-align:left;background-color:#DEDEBC;">
Korja kiiresti 10 seent, aga hoidu kärbseseentest eemale!<br>
Liikumine: nooleklahvid või AWDS
</div>
</div>
Ka kanvaa(de) definitsiooni mängu skriptis tuleb veidi muuta:
canvas = document.getElementById("gamecanvas");
 ctx = canvas.getContext("2d");
 w=canvas.width;
 h=canvas.height;
 canvas1 = document.getElementById("gamecanvas1");;
 ctx1 = canvas1.getContext("2d");

Salvestame kanvaa mõõdud ja arvutame puude arvu kanvaa äärtel:

d0 = tree.width; //klassi spriteSheet omadus 
h0 = tree.height;
var v = Math.floor(w/d0);
var r = Math.floor(h/d0);

Puude joonistamisel kasutame uut (kõrgemal asuvat) kanvaad:

function drawtrees(v,r){
var n = tree.nr; //how many trees 
	for (var i=0;i < v; i++){
		tree.draw1(Math.floor(Math.random()*n),i*d0,0);
		tree.draw1(Math.floor(Math.random()*n),i*d0,h-d0);
		}
	for (var j=1;j < r; j++){
		tree.draw1(Math.floor(Math.random()*n),0,j*d0);
		tree.draw1(Math.floor(Math.random()*n),w-d0,j*d0);
		}
}

Funktsiooni reset() tuleb juurde seene spriidi numbri currentseen leidmine:

var reset = function () {
	//Put hero to the center
	hero.x = w / 2;
	hero.y = h / 2;
	// Throw the mushroom somewhere on the screen randomly
	seen.x = seen.width + (Math.random() * (w - 2*seen.width));
	seen.y = seen.height + (Math.random() * (h - 2*seen.height));
	currentseen = Math.floor(Math.random()*seen.nr);	
};

Muu funktsioonis initGame ei muutu.

Ülalesitatud seened (klassiga spriteSheet loodud objekt ) ei võimalda eristada mürgiseid ja mittemürgiseid seeni. Eespool kasutatud funktisooni seen_obj tuleb veidi modifitseerida.

Selle priidiks võib määrata klassiga spriteSheet loodud objekti seen, s.t. funktsiooni seen_obj alguses määrame

this.sprite = seen;

Heade ja pahade seente valikul tuleb fikseerida mitte seene spriit, vaid indeks spriidilehel, seega näiteks kärbseseene valikusse tuöleb rida

this.spriteInd = 0;

Ülejäänud seente indksi võib määrata käsuga

this.spriteInd = 1 + Math.floor(Math.random()*seen.nr); //1,2,3

Seene joonistamisel võib kasutada klassi spriteSheet vastavat funktsiooni:

this.sprite.draw(this.spriteInd,this.x,this.y,this.suurus,this.suurus);

Et alati poleks tarvis anda ka kaht viimast parameetrit (suurused), võib klassi spriteSheet joonistamisfunktsiooni veidi täiendada. kui joonistamiskäsus kaks viimast argumenti puuduvad, kasutame spriidikaadri suurust:

this.draw = function(nr,x,y,w,h){
		w = w || this.width;
		h = h || this.height;
		ctx.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.width,this.height,x,y,w,h);

Ja peale funktsiooni draw peab klassil spriteSheet olema ka funktsioon draw1, mis joonistab puud abikanvaale; kuna seda kasutatakse vaid puude joonistamiseks abikanvaale, siis siin pole suurus enam oluline:

this.draw1 = function(nr,x,y){
		ctx1.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.width,this.height,x,y,this.width,this.height);
	}

 

Seente ja takistuste loomine ja kogu mängu käik (seenekorjamine) jääb endiseks.

Klaviatuuri klahvide koodid:
http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes

Ülesandid

1. Tee ka takistustest (kivi, känd, lisa muid9 spriidileht (lae http://spritesheetpacker.codeplex.com/ või mõni teine spriidilehtede loomise programm)

2. Takistusi pole nagu puidki tarvis igas kaadris uuendada - joonista ka takistused puudega samale kanvaale.