Klõpsa mänguväljal; liikumine - nooleklahvid või AWDS!
Reload, kui mäng on läbi (peatub)!
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.

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

Spriidilehe laeb 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 eeldatud, et kõigi piltide laius/kõrgus on samad):
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){
		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:

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

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 class="container" width="360" height="500">
<canvas id="gamecanvas" width="320" height="252" style="z-index: 0;position:absolute;left:0px;top:0px;background:url('images/taust.png')"></canvas>
<canvas id="gamecanvas1" width="320" height="252" style="z-index: 1;position:absolute;left:0px;top:0px;"></canvas>
<div id="inf" style="position:relative;top:260px;left:auto;width:100%;text-align:left;">
Klõpsa mänguväljal; liikumine - nooleklahvid!<br>
Reload, kui mäng on läbi (peatub)!</div>
</div>

ja muudame veidi mängu skriptis kanvaa(de) definitsiooni:
canvas = document.getElementById("gamecanvas");
 ctx = canvas.getContext("2d");
 w=canvas.width;
 h=canvas.height;
 canvas1 = document.getElementById("gamecanvas1");;
 ctx1 = canvas1.getContext("2d");

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

function drawtrees(){
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);
		}
}
- puu joonistamine kontekstis ctx1 oli spriidikirjeldusfuntsioonis spriteSheet juba olemas.

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);	
};
ja mängu käivitamisel laaditavate resursside (piltide) loetelus tuleb endiste üht puud/seent kujutavate piltide asemel näidata spriidilehed:
var sources = {
            trees: "images/trees.png",
            hero: "images/hero.png",
            seened: "images/seened.png",
		};
window.onload = function() {
        loadImages(sources, initGame);  // calls initGame after *all* images have finished loading
    };

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

Muu funktsioonis initGame ei muutu:

Klaviatuuri klahvide koodid:
http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
var seeni = 10; var w,h,d0,h0,r,v,ctx,pFill,treeImage,hero,seen,collected,keysdown,then; var startTime = Date.now(); function initGame(images) { // Initialize the canvas canvas = document.getElementById("gamecanvas"); ctx = canvas.getContext("2d"); w=canvas.width; h=canvas.height; canvas1 = document.getElementById("gamecanvas1");; ctx1 = canvas1.getContext("2d"); //Trees tree = new spriteSheet(images.trees,[[0,0,32,32],[33,0,32,32],[0,33,32,32],[33,33,32,32]]); seen = new spriteSheet(images.seened,[[0,0,32,32],[33, 0, 32, 32],[0,33,32,32],[33, 33, 32, 32]]); d0 = tree.width; // width of the first tree h0 = tree.height; // height of the first tree v = Math.floor(w/d0); r = Math.floor(h/d0); currentseen = Math.floor(Math.random()*seen.nr); hero = { sprite: images.hero, width: images.hero.width, height: images.hero.height, x: Math.floor(w/2), //to center y: Math.floor(h/2), speed: 128 // movement in pixels per second }; collected = 0; // Handle keyboard controls keysDown = {}; addEventListener("keydown", function (e) { keysDown[e.keyCode] = true; }, false); addEventListener("keyup", function (e) { delete keysDown[e.keyCode]; }, false); // Reset the game when the player catches a mushroom // Let's play this game! drawtrees(); reset(); //render(); then = Date.now(); setTimeout(main,30); } 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){ ctx1.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.width,this.height,x,y,this.width,this.height); } } function drawtrees(){ var n = tree.nr; //how many trees console.log(n); 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); } } // Update game objects function update(modifier) { if (38 in keysDown) { // up key pressed if (hero.y > 0) hero.y -= hero.speed * modifier; if (hero.y < d0) //can not go through trees! hero.y = d0; } if (40 in keysDown) { // down key pressed if (hero.y < h - d0-hero.sprite.height) hero.y += hero.speed * modifier; if (hero.y > h - d0-hero.sprite.height) hero.y = h - d0-hero.sprite.height; } if (37 in keysDown) { // left key pressed if (hero.x > 0) hero.x -= hero.speed * modifier; if (hero.x < d0) hero.x = d0; } if (39 in keysDown) { // right key pressed if (hero.x < w - d0-hero.sprite.width) hero.x += hero.speed * modifier; if (hero.x > w - d0-hero.sprite.width) hero.x = w - d0-hero.sprite.width; } // Are they touching? if (collision(hero,seen)){ ++collected; reset(); //calculate new positions for player and mushroom } }; function collision(obj1,obj2){ return obj2.x <= (obj1.x + obj1.width) && obj1.x <= (obj2.x + obj2.width) && obj2.y <= (obj1.y + obj1.height) && obj1.y <= (obj2.y + obj2.height); } // Draw everything function render() { ctx.clearRect(0,0,w,h); //drawtrees(); ctx.drawImage(hero.sprite, hero.x, hero.y); seen.draw(currentseen,seen.x,seen.y); showScore("Seeni: " + collected); } function showScore(txt){ // Score ctx.fillStyle = "rgb(250, 250, 250)"; ctx.font = "10px Arial"; ctx.textAlign = "left"; ctx.textBaseline = "top"; ctx.fillText(txt, d0, h0); //text, x,y }; 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); }; // The main game loop var main = function () { var now = Date.now(); var delta = now - then; if (collected < seeni) { update(delta / 1000); render(); then = now; setTimeout(main,1); //repeat } else { var time = Math.round(((now-startTime)/1000)) showScore("Said "+time+ " sekundiga " + collected+" seent!"); } };