Ruumiline ring
Kui mängus on tarvis erineva suuruseda ruumilisi (3D) kumeraid kehi (näit palle), tuleb need joonistada Javascriptiga, sest imporditud pikselkujutiste suuruse muutmisel nende kvaliteet kannatab.
Ruumilisuse (3D) effekti tekitavad valguslaigud - seal, kuhu langeb valgus, on pind heledam, varjus - tumedam.
Pallile ruumilse väljanägemise annab palli täitmine gradientvärviga.
Javascripti käsk
ctx.arc(x, y, r, 0, Math.PI*2, true);joonistab kanvaale punkti x,y ümber ringi (kaar mille kesknurk on täispööre, 360 kraadi ehk Math.PI*2) raadiusega r. Määrame selle täitevärviks gradiendi (liugvärvi), millel on kaks värvipunkti - sava värvuse (hue) tumedam/kontrastsem ja heledam toon. Värvuse heledust7tumedust on väga raske määrata RGB-komponentide abil, kuid lihtne HSL (Hue-värvus, Saturation - värviküllasus7kontrastsus, Lightness-heledus) abil. HLS-värv määratakse stringina, näit
hsl_col1 = "hsl(" + c + ", 100%, 40%)"
määrab värvi, mille toon (hue - täisarv vahemikus 0..255 - 0-punane, 120 - roheline, 240 - sinine), värviküllasus on 100%, heledus - 40%
Ruumilise (3D) mulje saamiseks määrame täitevärvi, mis punktis x-r/5,y-r/5 on heledam, lõpus - tumedam. Selliselt värvitud ringi tekitab funktsioon
function circle(x,y,r,c){ // create radial gradient //console.log('circle: '+x+', '+y+', '+c); var x1 = x-r/5; var y1 = y-r/5; var r4 = r/4; //console.log(x1+', '+y1+', '+r4); var grd = ctx.createRadialGradient(x1, y1, r4, x, y, r); var hsl_col1 = "hsl(" + c + ", 100%, 40%)"; var hsl_col2 = "hsl(" + c + ", 60%, 80%)"; grd.addColorStop(0, hsl_col2); //"#8ED6FF"); grd.addColorStop(1, hsl_col1); //"#004CB3"); ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2, true); ctx.fillStyle = grd; //"rgb(0, 0, 255)"; ctx.fill(); ctx.closePath(); // add stroke ctx.lineWidth = 1; ctx.strokeStyle = hsl_col1; ctx.stroke(); }
Ruumiline pall
Pall on objekt, mille visuaalne kujutis ekraanil on eelnenud funktsiooniga cicle joonistatud ring ja mis hüpleb kanvaal, põrkudes kõigist kanvaa äärtest (kanvaa laius ja kõrgus w0, h0 on globaalmuutujad, seega pall "teab" neid). Palli kiiruse määrab vektor vx,vy, kus vx on kiiruse horisontaalkomponent, vy - vertikaalkomponent. Palli liikumise igas kaadris kontrollitakse, kas pall on kusagil kanvaa ääres ja kui on, siis tekib põrkumine - püstseinast põrkumiseks tuleb muuta komponenti vx, horisontaalseinast põrkumiseks - komponenti vy:, sellise palli saab luua konstruktorfunktsiooniga
function ball(x,y,r,col,vx,vy){ //var color = Math.floor(Math.random()*6)*60; this.x = x; this.y = y; this.r = r; this.col = col; this.vx = vx; this.vy = vy; this.draw = function(){ circle(this.x,this.y,this.r,this.col)}; this.move = function(){ if (this.x + this.r + this.vx < 0 || this.x + this.r + this.vx > w0) this.vx *= -1; //põrkumine püstseinalt if (this.y + this.r + this.vy < 0 || this.y + this.vy + this.r > h0){ this.vy *= -1; //põrge horisontaalpinnalt } this.x += this.vx; this.y += this.vy; console.log(this.x+', '+this.y+' - '+this.vx+', '+this.vy); } }
Loo kanvaa ja selle alla nupp, millele klõpsates saab kanvaale lisada või eemaldada palle; meeldiva puhta värvuse saab valides värvitooniks 60-kraadi kordse:
var c = 60*Math.floor(Math.random()*6);
Palli lisamise-eemaldamise võib realiseerida funktsiooniga, mis positiivse argumendi korral lisab massiivi balls uue palli, negatiivse korral - eemaldab:
function modBalls(n){ if (n>0){ var rad = 60; //15 var x0 = rad + Math.random()*(w0 - 2*rad); var y0 = rad + Math.random()*(h0-2*rad); var c = 60*Math.floor(Math.random()*6); //console.log('Pall: '+x0+', '+y0+', '+c); var pall = new ball(x0,y0,rad,c,2+Math.random()*4,2+Math.random()*4); balls.push(pall); } else if (balls.length > 0) balls.pop(); }Selle funktsiooni kasutamiseks tuleb kanvaa alla luua veel üks div(ision), milles on kaks nuppu:
‹button onClick="modBalls(1)">Lisa pall‹/button> ‹button onClick="modBalls(-1)">Eemalda pall‹/button>
Nuppude paigutuse võib määrata CSS-iga või tabeliga.
Palli paneb liikuma järgnev funktsioon (käivitatakse initsialiseerimisefunktsiooni viimael real
function animate(){ ctx.clearRect(0, 0, w0,h0); for(var i=0; i < balls.length; i++){ var pall = balls[i]; pall.move(); pall.draw(); } setTimeout(animate,30); }
Ülesanne
Tee mäng, kus pall enam ei põrku kanvaa alaäärest - sealt peab palli põrgatama (ainult) horisontaalselt liikuva reketiga (lapik nelinurk) mängija (nagu mängus Breakout).
Palli füüsika
setTimeout | regAnimFrame | Summuta |
Paljude mängude trump on füüsikaseaduste kasutamine - füüsikaseaduste kasutamises heaks näiteks HTML5 mängudes on näiteks Angry Birds (kasutab teeki Box2D) ja Cut The Rope . Paljudes multikates kasutatakse nn 'multika füüsikat' (cartoon physics), kus füüsikaseadused toimivad/ei vaid siis, kui see tekitab lõbusa hetke (fun) - näiteks Disney multikates võib kangelane joosta üle kõrge kalju ääre ja õhus edasi siblda, kukkudes alla alles siis, kui ta vaatab allapoole ja märkab, et all on tühi (Walt Disney studio animaator: "Animation follows the laws of physics — unless it is funnier otherwise "). 'Multika füüsika' kasutamine mängudes on väga perpektiive - tehke ära !
Mängudes kasutatavad füüsikaseadused pole tuumafüüsika. Mäng on (praegu veel) 2D ekraanil toimuv liikumine, seega olulised on liikumise dünaamikat juhtivad seadused: f = m*a, v = v0 + at, seega näiteks allal langemisel raskuskiirenduse mõjul kiirus igas kaadris (lugedes kaadrite vahetuse ajaks 1) suureneb raskuskiirenduse g võrra jne. Kiirendus on additiivne, s.t . see liidetakse muudetavale suurusele (kiirus); õhu/veere jne takistus on multiplikatiive, s.t. muudetav suurus (kiirus) koorutatakse takistusteguriga. Enamuses mängudes rakendatakse neid seadusi eeldades, et kehad on jäigad (rigid)
Palli liikumise teeb loomulikumaks füüsikaseaduste kasutamine. Määrame raskuskiirenduse ja liikumise takistuse:
g = 0.8; //raskuskiirendus! tx = 0.96; // takistus
Neid tuleb kasutada palli liigutavas funktsioonis; täiendame palli funtsiooni move liikumistakistuse kasutamisega:
this.vx *= tx; //multiplikatiivne, korrutatakse this.vy *= tx;
Raskuskiirenduse peaks lisama palli kiiruse vertikaalkomponendile vy, kuid seda võib teha vaid siis, kui pall on õhus - põrandal olevale pallile raskuskiirenduse rakendamine veaks palli läbi põranda:
if (this.y < h0-this.r) //palli nullpunkt on palli keskel this.vy -= g; // additiivne, liidetakse
Viimane definitsioon siiski pole päreis hea. Kui palli allapoole langemise kiirus vy on juba nulli lähedal, kuid kaadris pall asub maapinnast veidi kõrgemal (tintgimus this.y < h0-this.r on täidetud), liidetakse palli alla langemise kiirusele raskuskiirendus ja kui järgmises kaadris toimub põrkumine, lendab pall ülespoole juba suurema kiirusega kui alla kukkudes - tulemusena hakkab pall maapinnal värelema (võrdle palle, mis on lisatud, kui 'Summuta' on valitud/valimata).
Kuidas seda vältida?
1. Lisa pallidele ka nende omavaheline põrkumine!
(siin on vajalik vektorarvutus - pallide kiirus/momendi vektorite liitmine, vt näit Piljard, Ball to Ball Collision - Detection and Handling, When Worlds Collide: Simulating Circle-Circle Collisions
2. Milleses mängus (näiteks) kasutatakse elastsete kehade (pallide) põrkumist ?