Alustame piltide ja helide (võib esialgu ära jätta) laadimisega:
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]; } } window.onload = function() { var canvas = document.getElementById("gameCanvas"); ctx = canvas.getContext("2d"); var sources = { treesprite: "./images/winter/puud_lumes.gif", pakid: "./images/winter/pakid.png", santa_ani: "images/winter/santad1.png", flake: "images/winter/flake.gif", lamp: "images/winter/lamp64.png" }; loadImages(sources, initGame); }
Kui kõik on laetud käivitub mängu initsialiseerimisfunktsioon:
var initGame = function(images){ wt = 64; // module - width of a tree tile ht = 64; //kui ebasümmeetrilised var k = 2; // how many tiles per side var n = 6; // how many squares in row/column on screen var vl = n+10; //number of columns in (virtual) labyrinth var rl = 20; //number of rows in (virtual) labyrinth d = k * wt; // width of one labyrinth square wc = n * d+wt; //canvas width hc = (n-1) * d+ht; //canvas height w0 = vl*d+wc; //width of virtual canvas h0 = rl*d+wt; //height of virtual canvas xl = 0; //start position of labyrinth on screen yl = 0; //start position of labyrinth ctx.canvas.width = wc; ctx.canvas.height = hc; canvas0 = document.createElement('canvas'); ctx0 = canvas0.getContext('2d'); //canvas0.id = "VirtualCanvas"; canvas0.width = w0; canvas0.height = h0; pick_snd = document.getElementById("pick"); fanfare_snd = document.getElementById("fanfare"); trees = new spriteSheet(images.treesprite,[[0,0,64,64],[65,0,64,64],[128,0,64,64],[192,0,64,64]]); pakid = new spriteSheet(images.pakid,[[0,0,64,64],[64, 0, 64, 64],[128,0,64, 64],[192, 0, 64, 64],[0,64,64,64],[64, 64, 64, 64],[128,64,64, 64],[192, 64, 64, 64],[0,128,64,64],[64, 128, 64, 64],[128,128,64, 64],[192, 128, 64, 64],[0,192,64,64],[64, 192, 64, 64],[128,192,64, 64],[191, 192, 64, 64]]); rooms = create(vl,rl,n,wt,ht,d,w0,h0,k); map = draw_labyrinth(rooms,trees,d,k,vl,rl,wt,ht,w0,h0); labyr = ctx0.getImageData(0, 0, w0, h0); ctx.putImageData(labyr, xl,yl,-xl, -yl,wc,hc) }
Nüüd võib juba testida, näit käsuga ctx.drawImage(images.treesprite,0,0) peaks kanvaale ilmuma puude spriit.
Spriidilehe põhjal loodud animeerimata staatilised objektid - puud (ja pakid) luuakse juba tuttava klassiga :
function spriteSheet(img,coords){ this.coords = coords; //coords = [[x0,y0,w0,h0],...[xn,yn,wn,hn]] this.sheet = img; this.draw = function(nr,x,y){ ctx.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.coords[nr][2],this.coords[nr][3],x,y,this.coords[nr][2],this.coords[nr][3]); } this.draw0 = function(nr,x,y){ ctx0.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.coords[nr][2],this.coords[nr][3],x,y,this.coords[nr][2],this.coords[nr][3]); } }
Animatsiooniga objekt jõuluvana luuakse klassiga animated; jõuluvanal on 4 animatsiooni:
function animated(img,dx,dy,states, n_fr,state,pos,sp){ //states - array of coordinates this.image = img; //console.log('santa: '+this.image.height); this.width = dx; this.height = dy; this.states = states; this.dx = dx; //this.width; this.dy = dy; //this.height/3; //32 this.n_frames = n_fr; //number of frames this.actualFrame = 0; this.speed = sp; //how many *game* frames a frame is shown this.step = 0; this.y = 0; this.x = 0; this.state = state; this.pos = pos; this.setPosition = function(X, Y){ this.x = X; this.y = Y; } this.draw = function(cx,cy){ var cx=cx||0; var cy=cy||0; if (this.pos == 0) //still ctx.drawImage(this.image,santa.states[santa.state][0],santa.states[santa.state][1],this.dx,this.dy,this.x+xl,this.y+yl,this.dx,this.dy); else { // walking ctx.drawImage(this.image,santa.states[santa.state][0],santa.states[santa.state][1] + this.dy*this.actualFrame,this.dx,this.dy,this.x+xl,this.y+yl,this.dx,this.dy); this.step ++; if (this.step == this.speed) { this.actualFrame++; //this.actualFrame = this.actualFrame%this.n_frames 1; if (this.actualFrame == this.n_frames) this.actualFrame = 1; this.step = 0; } } } }
Labyrint luuakse nn 'uuristamismeetodiga' - algul on labyrindiga ühendatud vaid sissepääs (vasak ülanurk); igal sammul liidetakse uus veel ühendamata ruum, kui selle kõrval on juba ühendatud ruum (luuakse nende vahele uks):
function create(vl,rl,n,wt,ht,d,w0,h0,k){ var ruudud = []; var connected = []; var free = []; var dirs = [0, 1, 2, 3]; for (i = 0; i < rl; i++) { for (var j = 0; j < vl; j++) { ruudud.push(new ruut(i, j)); } } var i0 = random(0,ruudud.length); connected.push(ruudud[i0]); //esimene ruum labyrindis free = ruudud.slice(0); free.splice(i0, 1); //Next line starts Firebug debugger! //debugger; while (free.length > 0) { var found = 0; while (found == 0) { i0 = random(0, connected.length); var r = connected[i0]; //kandidaat! var dirs = [0, 1, 2, 3]; for (i = 0; i < 4; i++) { if (r.doors.indexOf(dirs[i]) > 0) dirs.splice(i, 1); } while (dirs.length > 0) { var j0 = random(0, dirs.length); var d1 = dirs[j0]; var i1 = next(r, d1,free); if (i1 >= 0) { var r1 = free[i1]; found = 1; r.doors.push(d1); var d2 = reverse(d1); r1.doors.push(d2); connected.push(r1); free.splice(i1, 1); break; } else { dirs.splice(j0, 1); } } } } return connected; } function draw_labyrinth(connected,trees,d,k,vl,rl,wt,ht,w0,h0){ map = []; for(var i=0;i < vl*k+1;i++) //colums in virtual map[i]=[]; for(var i=0;i < vl*k+1;i++) for(var j=0;j < rl*k+1;j++) map[i][j]=0; for (var i = 0; i < connected.length; i++) { var xx = connected[i].m; var yy = connected[i].n; if (connected[i].doors.indexOf(0) == -1){ treeline((xx + 1) * d, yy * d, (xx + 1) * d, (yy + 1) * d, wt,ht, trees); for(var i1=yy*k;i1 < (yy+1)*k;i1++) map[(xx+1)*k][i1]=1; } if (connected[i].doors.indexOf(1) == -1){ treeline(xx * d, (yy + 1) * d, (xx + 1) * d+1, (yy + 1) * d, wt,ht, trees); for(var i1=xx*k;i1 < =(xx+1)*k;i1++) map[i1][(yy+1)*k]=1; } if (xx == 0 && connected[i].doors.indexOf(2) == -1){ treeline(xx * d, yy * d, xx * d, (yy + 1) * d, wt,ht, trees); for(var i1=yy*k;i1 < (yy+1)*k;i1++) map[xx*k][i1]=1; } if (yy == 0 && connected[i].doors.indexOf(3) == -1){ treeline(xx * d, yy * d, (xx + 1) * d, yy * d, wt,ht, trees); for(var i1=xx*k;i1 < (xx+1)*k;i1++) map[i1][yy*k]=1; } } ctx0.clearRect(w0-wt,h0-2*ht,wt,ht); // door out! map[vl*k][rl*k-1]=0; return map; } function next(r, dir,free) { var nn1; var mm1; switch (dir) { case 0: nn1 = r.n; mm1 = r.m + 1; break; case 1: nn1 = r.n + 1; mm1 = r.m; break; case 2: nn1 = r.n; mm1 = r.m - 1; break; case 3: nn1 = r.n - 1; mm1 = r.m; break; default: nn1 = r.n; mm1 = r.m; } for (var i = 0; i < free.length; i++) { if ((free[i].n == nn1) && (free[i].m == mm1)) { return i; break; } } return -1; } function connection(n, m, direction) { //direction=1 - right //direction=2 - down //direction=3 - left //direction=4 - up switch (direction) { case 0: this.n1 = n; this.m1 = m; this.n2 = n + 1; this.m2 = m; this.dir = 0; break; case 1: this.n1 = n; this.m1 = m; this.n2 = n; this.m2 = m + 1; this.dir = 1; break; case 2: this.n1 = n; this.m1 = m; this.n2 = n - 1; this.m2 = m; this.dir = 2; break; case 3: this.n1 = n; this.m1 = m; this.n2 = n; this.m2 = m - 1; this.dir = 3; break; default: this.n1 = n; this.m1 = m; this.n2 = n; this.m2 = m; this.dir = 0; //no connection } } function reverse(dir) { return (dir + 2) % 4; } function ruut(n, m) { this.n = n; this.m = m; this.doors = []; } function treeline(x0,y0,x1,y1,wt,ht, trees){ var nx = (x1 - x0)/wt; var ny = (y1 - y0)/ht; var n = Math.max(nx,ny); var dx = (x1 - x0)/n; var dy = (y1 - y0)/n; for(var i=0;i < n;i++){ trees.draw0(Math.floor(Math.random()*trees.coords.length),x0 + i*dx, y0 + i*dy); } }
Kui need klassid on lisatud, võib juba kontrollida: lisada initsialiseerimisfunktsiooni lõppu metsa ja jõuluvana joonistamise:
xb = wt/2+(d-santa.width)/2; //santai (bogy) (alg)asend virtuaalses labyrindis yb = ht/2 + (d-santa.height)/2; santa.setPosition(xb,yb); santa.draw(); rooms = create(vl,rl,n,wt,ht,d,w0,h0,k); map = draw_labyrinth(rooms,trees,d,k,vl,rl,wt,ht,w0,h0); labyr = ctx0.getImageData(0, 0, w0, h0); ctx.putImageData(labyr, xl,yl,-xl, -yl,wc,hc)
-ekraanile peaks ilmuma mets ja jõuluvana.
Pakid jagab metsa laiali funktdsioon placepakid, nad joonistab 8lisab metsale) drawpakid:
function placepakid(v,r,d,n){ //select random squares for mushrooms var indexis=[]; var pakikohad1 = []; //ajutine massiiv otsimiseks for(var i = 0; i < v*r-1; i++) indexis[i] = i+1; //ruudud alates teisest - esimeses on var i0 = Math.floor(Math.random()*indexis.length); pakikohad1.push(indexis[i0]); indexis.splice(i0,1); //korduste vältimine ! i0 = Math.floor(Math.random()*indexis.length); //next! while(pakikohad1.length < pakke && pakikohad1.indexOf(indexis[i0])==-1){ pakikohad1.push(indexis[i0]); indexis.splice(i0,1); i0 = Math.floor(Math.random()*indexis.length); } //console.log(pakikohad); for (var i = 0; i < pakke; i++) { var v1 = pakikohad1[i]%v; var r1 = Math.floor(pakikohad1[i]/v); var sx = v1*d + d/2; var sy = r1*d + d/2; pakikohad.push({r:indexis[i0],x:sx,y:sy, seen:Math.floor(Math.random()*n)}); } return pakikohad; } function drawpakid() { ctx0.clearRect(0,0,w0,h0); ctx0.putImageData(labyr, 0,0); for(var i=0;i < pakikohad.length; i++) { pakid.draw0(pakikohad[i].seen,pakikohad[i].x, pakikohad[i].y ); }
Pakkide lisamiseks lisame vastavate funktsioonide käivitamise ka mängu initsialiseerimisfunktsioonile:
pakke = 40; //kui palju lisatakse mängu (peab olema < v*r-1 - esimene ruut santa jaoks!) pakke = Math.min(vl*rl-1,pakke); pakikohad = []; //ruudud, kus pakid paiknevad korjatud = 0; pakikohad = placepakid(vl,rl,d,pakid.coords.length); drawpakid(); pakimets = ctx0.getImageData(0, 0, w0, h0); ctx.putImageData(pakimets, xl,yl,-xl, -yl,wc,hc)
- viimase reaga peaks ekraanile ilmuma juba pakke täis mets.
Jõuluvana juhitakse klaviatuuriga, lisame klahvide seisu salvestamise:
keysDown = []; keysDown[37]=keysDown[38]=keysDown[39]=keysDown[40]=false;
Jõuluvana juhtimise testimiseks käivitame mängu kaadrivahetuse: lisame initsialiseerimisfunktsiooni lõppu
GameLoop();
Funktsioon GameLoop juhib kogu mängu; igas kaadris kontrollib funktsioon update jõuluvana liikumist (kasutab kokkupõrkefunktsioone)
function GameLoop(){ update(speed); //speed - mitu pikslit kaadris, tuleb anda initsialiseerimisel if (santa.x+xl > wc/2+d && wc - xl < w0) {xl -= speed; //metsa skrollimine } else if (santa.x+xl < wc/2 && xl > 0) {xl += speed; } if (santa.y+yl > hc/2 && hc - yl < h0) {yl -= speed; } else if (santa.y+yl < hc/2-d && yl < 0) {yl += speed;} ctx.clearRect(0,0,wc,hc); ctx.drawImage(canvas0, -xl,-yl,wc,hc,0,0,wc,hc); //pakimets santa.draw(); //lumehelbed ja valgus - kui veel pole, tuleb välja kommenteerida /* ctx.globalAlpha=0.4; ctx.globalCompositeOperation = "source-over"; ctx.globalCompositeOperation = "lighter"; ctx.drawImage(light,0,0,64,64,0,0,wc,hc); for(var i=0;i < nrOfFlakes;i++) {flakes[i].update();} ctx.globalAlpha = 1; */ setTimeout(GameLoop, 30); } function update(sp){ var dx = 0; var dy = 0; if (keysDown[37]) { // left arrow dx = -sp; santa.state = "vasakule"; santa.pos = 1; } else if (keysDown[38]) { // up arrow dy = -sp; santa.state = "sinna"; santa.pos = 1; } else if (keysDown[39]) { // right arrow dx = sp; santa.state = "paremale"; santa.pos = 1; } else if (keysDown[40]) { // down arrow dy = sp; santa.state = "siia"; santa.pos = 1; } else santa.pos = 0; if(dx !=0 || dy !=0) { santa.pos = 1; //walking check_collision1(dx,dy,santa); } else santa.pos = 0; //still }; function collision(x1,y1,w1,h1,x2,y2,w2,h2){ return x1 < x2 + w2 && x2 < x1 + w1 && y1 < y2 + h2 && y2 < y1 + h1 } function check_collision(dx,dy,obj1,obj2){ //obj1 is moving, obj2 is bitmapdata to check var x = obj1.x; var y = obj1.y; var wb = obj1.width; var hb = obj1.height; var points1; if (dx < 0) { if (dy == 0) points1 = [[x,y],[x,y+hb]]; else if (dy > 0) points1 = [[x,y],[x,y+hb],[x+wb,y+hb]]; else if (dy < 0) points1 = [[x+wb,y],[x,y],[x,y+hb]]; }; if (dx == 0) { if (dy > 0) points1 = [[x,y+hb],[x+wb,y+hb]]; else if (dy < 0) points1 = [[x+wb,y],[x,y]]; }; if (dx > 0) { if (dy == 0) points1 = [[x+wb,y+hb],[x+wb,y]]; else if (dy > 0) points1 = [[x,y+hb],[x+wb,y+hb],[x+wb,y]]; else if (dy < 0) points1 = [[x+wb,y+hb],[x+wb,y],[x,y]]; }; var t = 0; var j = Math.max(Math.abs(dx),Math.abs(dy)); //how many steps to check var ddx = dx/j; var ddy = dy/j; found: do{ t++; var i = 0; do{ var x1 = points1[i][0]; //coordinate of the faremost corner var y1 = points1[i][1]; var xx1 = (x1+t*ddx); var yy1 = (y1+t*ddy); var p = 4*(xx1 + w0*yy1); if (obj2.data[p+3]!=0 ) // not transparent! { break found; }; i++; } while (i < points1.length); } while (t < j) ; xb = Math.floor(xb + (t-1)*ddx); //move obj1 ! yb = Math.floor(yb + (t-1)*ddy); santa.setPosition(xb,yb); korja(); } function check_collision1(vx,vy,obj1){ //vx,vy - move of obj1 on next step // collision is based on map var x = obj1.x; var y = obj1.y; var wb = obj1.width; var hb = obj1.height; var points1; if (vx < 0) { if (vy == 0) points1 = [[x,y],[x,y+hb]]; else if (vy > 0) points1 = [[x,y],[x,y+hb],[x+wb,y+hb]]; else if (vy < 0) points1 = [[x+wb,y],[x,y],[x,y+hb]]; }; if (vx == 0) { if (vy > 0) points1 = [[x,y+hb],[x+wb,y+hb]]; else if (vy < 0) points1 = [[x+wb,y],[x,y]]; }; if (vx > 0) { if (vy == 0) points1 = [[x+wb,y+hb],[x+wb,y]]; else if (vy > 0) points1 = [[x,y+hb],[x+wb,y+hb],[x+wb,y]]; else if (vy < 0) points1 = [[x+wb,y+hb],[x+wb,y],[x,y]]; }; var t = 0; var j = Math.max(Math.abs(vx),Math.abs(vy)); //how many steps to check var dvx = vx/j; var dvy = vy/j; found: do{ t++; var i = 0; do{ var x1 = points1[i][0]; //coordinate of the faremost corner var y1 = points1[i][1]; var xx1 = (x1+t*dvx); var yy1 = (y1+t*dvy); var v = Math.floor(xx1/wt); var r = Math.floor(yy1/ht); if (map[v][r]!=0 ) // not transparent! { break found; }; i++; } while (i < points1.length); } while (t < j) ; xb = Math.floor(xb + (t-1)*dvx); //move obj1 ! yb = Math.floor(yb + (t-1)*dvy); obj1.setPosition(xb,yb); korja(); }
Järgnev funktsioon on pakkide korjamiseks:
function korja(){ // kontrollime ruudu [xb,yb,32,32] lõikumist // ruuduga [pakikohad[i].x, pakikohad[i].y, 32,32] var i = 0; while (i < pakikohad.length){ if (collision(xb,yb,wt,ht,pakikohad[i].x,pakikohad[i].y,wt,ht)) break; else i++;} if (i < pakikohad.length) //oli seen! { korjatud ++; pick_snd.cloneNode(true).play(); inf.innerHTML = 'Korjatud '+korjatud + ' pakki!'; pakikohad.splice(i,1); drawpakid(); //uued! } }
Helbed luuakse klassidega
function snowFlake(img, xPos, yPos) { // set initial snowflake properties this.img = img; this.w = img.width; this.h = img.height; //this.r = r; this.v = 1+4*Math.random(); this.xPos = xPos; this.yPos = yPos; this.suund = (1+2*Math.random())*((Math.random()>0.5)?1:-1); // declare variables used for snowflake's motion //this.counter = 0; this.sign = (Math.random()>0.5)?1:-1; ////Math.floor(Math.random() * 2); // setting an initial opacity and size for our snowflake this.opacity = 0.1+Math.random(); this.size = 0.5 + Math.random(); //0.5 .. 1.5 // the function responsible for actually moving our snowflake this.update = function () { this.xPos += this.suund; //this.sign*this.v*Math.cos(this.counter)/20; this.yPos += this.v; if (Math.random() < 0.01) this.suund = (1+2*Math.random())*((Math.random()>0.5)?1:-1); // setting our snowflake's position ctx.globalAlpha = this.opacity; ctx.drawImage(this.img,this.xPos,this.yPos); // if snowflake goes below the browser window, move it back to the top if (this.yPos > hc) { this.yPos = -50; } } } function createFlakes(img,nr){ for (var i = 0; i < nr; i++) { // create new instance var x0 = 10 + (w0-20)*Math.random(); var y0 = (h0-20)*math.random(); var flake = new SnowFlake(img,x0,y0); flakes.push(flake); } return flakes; }
Helbed salvestatakse massiivis, mis luuakse mängu initsialiseerimisel:
nrOfFlakes = 25; flakes = []; for (var i = 0; i < nrOfFlakes; i++) { // create new instance var x0 = 10 + Math.floor((wc-20)*Math.random()); var y0 = Math.floor((hc-20)*Math.random()); //console.log(x0+','+y0); var flake = new snowFlake(images.flake,x0,y0); flakes.push(flake); }Valgustus on vaid üks spriit, mis mängu initsialiseerimisel salvestatakse eraldi kanvaal
light = document.createElement('canvas'); ctxL = light.getContext('2d'); ctxL.drawImage(images.lamp,0,0);
Mängu ajal) funktsioonis GameLoop) venitatakse see üle kogu kanvaa (ülal välja kommenteeritud).
Ülesandid
1. Modifitseeri helveste skripti, nii et tekiks erineva suurusega helbeid.
2. Modifitseeri valgustust - praegu ripub lamp mänguvälja keskel - tee nii, et see valgustaks jõuluvana ja liiguks koos temaga.