Isomeetra

Näide.

mario_isom
Mario (isomeetriline)
suunad
Liikumissuundade tähistus

tiles tiles isomeetriaRuumilisuse (3D) simuleerimiseks kasutatakse mängudes sageli isomeetrilist (ülalt poolviltu - kaamera on telgede x (horisontaalselt paremale) , y (üles), z(ekraanilt vaataja suunas) pööratud vastavalt 60,0,45 kraadi) vaadet, milles mänguvälakul olev ristkülik muutub rombiks; x-y tasandi objektid (pikslid) esitatakse transformeeritult 2:1, s.t. y-telje ühele ühikule vastab 2 ühikut x-teljel


Isomeetrilises mängus ei liigu tegelased üles-alla-paremale-vasakule, vaid poolviltu, näiteks nagu paremal näidatav tegelane. Spriidilehel on tegelane näidatud liikumas neljas suunas, iga suuna jaoks vaid 3 kaadrit - sammud mõlema jalaga ja paigal; iga kaader on 58 x 96 pikslit.

Isomeetrilises mängus on teljed (maapinnal) tavaliselt pööratud, kas 450 või 600, s.t. tegelased liiguvad poolviltu; sellepärast kasutatakse liikumissuundade tähistustena suundi kaardil: NE (NorthEast - kirre), SE (kagu), SW (edel), NW (loe).

Tegelane luuakse sama funktsiooniga nagu varem:

function animated(img,dx,dy,suunad, n_fr,suund,pos,sp){ //suunad - suunad, pos - animatsioon (1)/seisab(0)
	  this.image = img;
	  this.width = dx;  //kaadri laius
	  this.height = dy;
	  this.suunad = suunad;
	  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;  //in how many *game* frames a frame is shown
	  this.step = 0;
	  this.y = 0;
	  this.x = 0;
	  this.suund = suund;	  
	  this.pos = pos;		  
	  
	  this.setPosition = function(X, Y){
		this.x = X;
		this.y = Y;
	  }

	  this.draw = function(){
	  if (this.pos == 0) //paigal, esimene kaader
		ctx.drawImage(this.image,this.suunad[this.suund][0],this.suunad[this.suund][1],this.dx,this.dy,this.x,this.y,this.dx,this.dy);
		else {   // walking
		ctx.drawImage(this.image,this.suunad[this.suund][0] + this.dx*this.actualFrame,this.suunad[this.suund][1] ,this.dx,this.dy,this.x,this.y,this.dx,this.dy);
		this.step ++;
		if (this.step == this.speed) {
			this.actualFrame++;
			if (this.actualFrame == this.n_frames)
				this.actualFrame = 1;
			this.step = 0;
			}
			}
		}
	}

Spriidilehel on ühe kaadri mõõtmed 58x96 px, seega kangelase loob käsk

hero = new animated(images.hero,58,96,{"NW":[0,0],"NE":[174,0],"SW":[0,96],"SE":[174,96]},3,"SE",0,24);

Kuna metsas on palju ohte, lisame kangelasele omadused, mis mõõdavad tema tervist ja elu:

hero.dead = false;
hero.health = 100;

Objektide sügavus

ees-taga
Kangelane on mõne puu taga
ja mõne puu ees
Kui mänguväljale lisatakse ka muid objekte (puud, seened jne), peab ruumilisuse efekti saavutamiseks tagama õige objektide ekraanile joonistamise järjekorra - mis on ees, mis tagapool.

Ekraanil on (näib) tagapool olevat see, mis on ekraanil kõrgemal. Kõrgemal, s.t. püstsuunas asetust määrab objekti koordinaat 'y' - objekt, mille y-koordinaat on väiksem, peab asetsema tagapool (olema ekraanile joonistatud varem).

ees-taga
Seene y-koordinaat
- tipp -
on suurem kui
puu y-koordinaat
See idee ei toimi päris korrektselt, kui objektide kõrgused on erinevad - y-koordinaatide võrdlemise põhjal peaks seen olema puu ees, mitte taga.

Viga tekib sellest, et me järjestame (sügavuti, eemale-lähemale) mitte objektide tippude (y-koordinaat on objekti tipp), vaid nende asendi põhjal maapinnal, s.t. nende alumiste äärte põhjal ; alumine äär on määratud suurusega
object.y + object.height


Korrektse paigutuse saamiseks on kõige lihtsam koguda kõik ekraanil olevad objektid üheks massiiviks allObjects, järjestada see massiv igas kaadris (objektid liiguvad!) objekti omaduse
object.y + object.height
põhjal ja siis joonistada objektid ekraanile selle (järjestatud) massiivi järjekorras.

Et selline algoritm toimiks, peavad kõik objetid (kangelane, puud, seened,...) olema joonistaud samale kanvaale.

Seega - jätame ära eraldi kanvaa puude jaoks, ja lisame skripti massiivi mängu kõigi objektide jaoks

allObjects = [];

NB! Javascripti massiivid on viitade põhjal, s.t. nendes pole 'päris' objekte, ainult viidad neile, seega näiteks objektide ümberjärjestamine massiivis ei muuda objekti; objekti kustutamine (delete) tekitab vaid massiivi augu, sellepärast toimub objekti eemaldamine massivist käsuga splice.

Mängu uute objektide lisamisel lisame need ka massiivi allObjects:

allObjects.push(hero);
...
Objektide ekraanile joonistamise eel järjestame massiivi allObjects objektide alumise ääre, s.t. object.y + object.heigth järgi; järjestuse kirjeldab abifunktsioon compare:
function compare(obj1,obj2) {
  if (obj1.y + obj1.height < obj2.y + obj2.height)
     return -1;
  if (obj1.y + obj1.height >= obj2.y + obj2.height)
    return 1;
  return 0;
}
function drawObjects(){
	allObjects.sort(compare); //järjestamine funktsiooni compare põhjal !
	for (var i = 0; i < allObjects.length; i++) {
		allObjects[i].draw();}
}

Objektide nimetamise (info)loogika

Mängu loomine algab piltide laadimisega:

var sources = {
			trees: "images/trees_512x128.png",
			hero: "images/mario_iso_58x96.png",
			koll:"images/koll_64x64x4.png",
			mushrooms: "images/seened0.png",
		};

Piltidest tehakse klassifunktsioonide spriteSheet abil objektid seened, puud ja klassifunktsiooni animated abil objektid hero, koll:

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?
	console.log(this.width,this.height,this.nr);
	this.draw = function(n,x,y){
		ctx.drawImage(img,this.coords[n][0],this.coords[n][1],this.width,this.height,x,y,this.width,this.height);
	}
}
trees = new spriteSheet(images.trees,[[0,0,128,128],[128,0,128,128],[256,0,128,128],[384,0,128,128]]);
seened = new spriteSheet(images.mushrooms,[[0,0,32,32],[33, 0, 32, 32],[0,33,32,32],[33, 33, 32, 32]]);

Kuna ka puid ja seeni on ekraanile joonistamise jaoks järjestada, tuleb ka neist teha Javascripti objektid. Nendele kangelase (ja hiljem ka kolli) objektidega sama formaadi saamiseks loome veel ühe abiklassi sheet_obj :

function sheet_obj(sheet,n,x,y){
	this.obj = sheet;
	this.type = sheet.type; //puu, seen
	this.n = n;
	this.x = x;
	this.y = y;
	this.width = sheet.width;
	this.height = sheet.height;
	this.draw = function(){
		sheet.draw(this.n, this.x, this.y);
		}
}	

Puud

Et puud näeks välja isomeetrilised, joonistame nad kaldridadena. Tüüpiline isomeetrilise vaate 2D-formaat on: horisontaalsuunas 2 ühikut vastab vertikaalsuunas ühele ühikule. Seega kui puude spriidileht on loodud klassifunktsiooniga
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?
	console.log(this.width,this.height,this.nr);
	this.draw = function(n,x,y){
		ctx.drawImage(img,this.coords[n][0],this.coords[n][1],this.width,this.height,x,y,this.width,this.height);
	}
}
trees = new spriteSheet(images.trees,[[0,0,128,128],[128,0,128,128],[256,0,128,128],[384,0,128,128]]);
võib puu laiuse ja kõrguse võtta mooduliks ja joonistada puud, nihutades paarituid ridu poole laiusühiku võrra:
w0 = trees.width; //  width of the first tree 
h0 = trees.height; // height of the first tree
vIso = Math.ceil(w/w0);
rIso = Math.ceil(2*h/h0);
drawTrees();
...

function drawTrees() {
	console.log(rIso,vIso);
	for (var j=0; j < rIso; j++){
		treeLine(j);
		}
	}
function treeLine(r){
	//var n = tree.nr; //how many trees 
	var delta = (r%2)*w0;
	for (var i=0; i < vIso; i++){
		var x = i*2*w0+delta;
		var y = r*h0/2;
		var ind = Math.floor(Math.random()*trees.nr);
		var puu = new sheet_obj(trees,ind,x,y);
		puu.draw();
		allObjects.push(puu);
		}
}

Tulemusena peaks ekraanile ilmuma viltused puude read.

Seened

Seente paigutamisel on lihtsam lähtuda puude paigutamisel kasutatud püst- ja rõhtsuunalistest moodulitest, kuid paigutada seened puude vahele, s.t. nihutada mitte paarituid, vaid paaris ridu. Kuna etteantud arvu seeni on raske ühtlaselt paigutada, kasutame iga koha juures tõenäosust - kas siia panna seen või mitte? Mooduli järgi paigutamisel ei teki seente ega ka seente ja puude kokkupõrkeid.

seened = new spriteSheet(images.mushrooms,[[0,0,32,32],[33, 0, 32, 32],[0,33,32,32],[33, 33, 32, 32]]);
seened.type = "seen";
seeni = 0;
collected = 0;
placeSeened();
...
function placeSeened(){
	console.log('paigutan'+rIso,vIso,w0);
	for (var j = 0; j < rIso; j++){
		var delta = ((j+1)%2)*w0;
		for (var i = 0; i < vIso; i++){
			if (Math.random() > 0.85){ //mida väiksem, seda rohkem seeni ilmub
				var x = i*w0+delta;
				var y = j*h0/2;
				var ind = Math.floor(Math.random()*seened.nr);
				//console.log(ind,x,y);
				var seen = new sheet_obj(seened,ind,x,y);
				seen.draw(); //Math.floor(Math.random()*n),x, y); //i*w0,0);
				allObjects.push(seen);
				seeni ++;
			}
		}
	}
	//console.log(seeni);
}

Koll

kollMetsas on mitmesuguseid ohte - näiteks hulgub seal ringi kuri koll, kes sööb seenelisi.

See on vaba programmiga Sculptris loodud väike 3D kujund, millest siis on salvestatud 4 vaadet spriidileheks.

Koll ja kangelane hero luuakse sama klassifunktsiooniga animated, kuid kollil on veel üks muutuja state - kas koll liigub või ei (animatsiooni pole, seega ei saa kasutada hero muutujat pos):

koll = new animated(images.koll,64,64,{"NW":[0,0],"NE":[64,0],"SW":[0,64],"SE":[64,64]},1,"SE",0,24);
koll.x = w/2;
koll.y = h/2;
koll.type = "koll";
koll.state = 0; //ei liigu

allObjects.push(koll);

Kolli AI

Koll on arukas - ta hakkab kangelast jälitama, kui see satub talle piisavalt lähedale; kangelase kokkupõrge kolliga tapab kangelase. Objektide kaugust üksteisest mängudes tavaliselt ei mõõdeta analüütilisest geomeetriast tuntud valemiga sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)), sest ruutjuure leidmine on 'kallis', s.t. aeglane operatsioon; selle asemel kasutatakse objektide koordinaatide vahede absoluutväärtuste summat. Järgnev funktsioon paneb kolli kangelast jälitama, kui nende vaheline kaugus on väiksem kui kolmandik ekraani pikkus+laius mõõtmest:

koll.AI = function(delta){
	//console.log('koll vaatab!');
	if ((Math.abs(hero.x - koll.x) + Math.abs(hero.y - koll.y)) < (w+h)/3) {
		koll.state = 1; //liigub
		if ((koll.x < hero.x) && (koll.y < hero.y)){
			koll.suund = "SE";
			}
		else if ((koll.x > hero.x) && (koll.y < hero.y)) 	
			koll.suund = "SW";
		else if ((koll.x > hero.x) && (koll.y > hero.y)) 	
			koll.suund = "NW";
		else if ((koll.x < hero.x) && (koll.y > hero.y)) 	
			koll.suund = "NE";
			}
	else
		koll.state = 0;;
		//console.log(koll.suund);
}

Liikumine

Kangelase ja kolli liikumist kirjeldab funktsioon update, millele on parameetrid nende liikumiskiiruse muutmiseks - sellega saab teha mängu kergemaks/raskemaks.

Funktsioon update koosneb kolmest osast - esimeses juhitakse kangelast, teises kolli ja kolmandas kontrollitakse kangelase ja kolli kokkupõrkeid puude ja seentega. Kui koll või kangelane põrkavad kokku puuga, ei saa nad seda sammu sooritada ja nende esialgne asend taastatakse, sellepärast salvestatakse algul nende esialgne (vana) asend, et puuga kokkupõrke korral saaks taastada esialgse asendi.

Funktsiooni update esimene osa juhib kangelase liikumist:

function update(heroModifier,kollModifier) {
//hero
	var oldHeroX = hero.x;
	var oldHeroY = hero.y;
	if (38 in keysDown || 87 in keysDown) { // Player holding up
	if (hero.y > 0 && hero.x < w-hero.width)
		{hero.suund = "NE";
		hero.x += 2*hero.speed * heroModifier;
		hero.y -= hero.speed * heroModifier;
		hero.pos = 1;}
		if (hero.y < 0 ) //can not go out!
			{hero.y = 0;
			hero.pos = 0;}
		if ( hero.x > w-hero.width)
			{hero.x = w-hero.width;
			hero.pos = 0;}
	}
	if ((39 in keysDown) || (68 in keysDown)) { // Player holding right
	if (hero.y < h - hero.height && hero.x < w - hero.width)
		{hero.suund = "SE";
		hero.x += 2* hero.speed * heroModifier;
		hero.y += hero.speed * heroModifier;
		hero.pos = 1;}
		if (hero.y > h - hero.height )
			{hero.y = h - hero.height;
			hero.pos = 0;}
		if ( hero.x > w- hero.width)
			{hero.x = w - hero.width;
			hero.pos=0;}
	}
	if ((37 in keysDown) || (65 in keysDown)) { // Player holding left
	if (hero.x > 0 && hero.y > 0 )
		{hero.suund = "NW";
		hero.x -= 2*hero.speed * heroModifier;
		hero.y -= hero.speed * heroModifier;
		hero.pos = 1;}
		if (hero.x < 0 )
			{hero.x = 0;
			hero.pos = 0;}
		if ( hero.y < 0 )
			{hero.y = 0;
			hero.pos=0;}
	}
	
	if (40 in keysDown || 83 in keysDown) { // Player holding down
	if (hero.x > 0 && hero.y < h -  hero.height)
		{hero.suund = "SW";
		hero.x -=  2*hero.speed * heroModifier;
		hero.y += hero.speed * heroModifier;
		hero.pos = 1;}
		if (hero.x <  0 )
			{hero.x = 0;
			hero.pos = 0;}
		if ( hero.y > h -  hero.height)
			{hero.y = h -  hero.height;
			hero.pos = 0;}
	}
	
	if (!(37 in keysDown) && !(38 in keysDown) && !(39 in keysDown) && !(40 in keysDown) && !(83 in keysDown) && !(65 in keysDown) && !(68 in keysDown) && !(87 in keysDown) )
		hero.pos = 0; //peatub

Funktsiooni update teine, kolli liikumist kirjeldav osa on analoogiline kangelase liikumist kirjeldavaga, kuid lihtsam - siin ei kontrollita klaviatuuri, koll on 'iseliikuja'. Ka kolli liikumise kiirust saam modifitseerida ja sellega muuta mängu kergemaks-raskemaks:Kolli liikumist

//koll
	var oldKollX = koll.x;
	var oldKollY = koll.y;
	if (koll.state == 1) { //liigub!
	switch (koll.suund) {
	case 'SE': 
		if (koll.x < w - koll.width - 2 * koll.speed * kollModifier)
			koll.x += 2 *koll.speed * kollModifier;
		if (koll.y < h - koll.height - koll.speed * kollModifier)
			koll.y += koll.speed * kollModifier;
		break;	
	case 'SW': 
		if (koll.x > 2 * koll.speed * kollModifier)
			koll.x += - 2 *koll.speed * kollModifier;
		if (koll.y < h - koll.height - koll.speed * kollModifier)
			koll.y += koll.speed * kollModifier;
		break;	
	case 'NW': 
		if (koll.x > 2 * koll.speed * kollModifier)
			koll.x += - 2 *koll.speed * kollModifier;
		if (koll.y >  koll.speed * kollModifier)
			koll.y += - koll.speed * kollModifier;
		break;	
	case 'NE': 
		if (koll.x < w - koll.width - 2 * koll.speed * kollModifier)
			koll.x += 2 *koll.speed * kollModifier;
		if (koll.y > koll.speed * kollModifier)
			koll.y += -koll.speed * kollModifier;
		break;
		}
	}

Funktsiooni update kolmandas osas kontrollitakse kokkupõrkeid, kuid siin ei kontrollita enam kogu spriidi nelinurka (puu latv ei tohiks takistada kangelase liikumist puu taga), vaid ainult objektide projektsiooniga maapinnale (nende varju) tekitatud nelinurki - maske.

Mask

siia0
Mario:
spriit - 58x96 px
mask: 54x24 px

Kui spriidid on suuremad, ei saa nende kokkupõrkeid arvutada kogu spriidikujutise nelinurga põhjal - see näeks välja väga veider, kangelane ei pääse paljudest objektidest (puudest) mööda.

Kokkupõrgete arvutamiseks tuleb kasutada maski - objektide projektsiooni maapinnale (varju), näiteks Mario kaadri mask võiks olla tema jalgade ümber olev nelinurk 54x24px.

Kangelase mask kirjeldatakse kohe pärast kangelase loomist. Mask on objekt, sellel on omadused .x, .y, .width, .height. Omadused mask.x, mask.y muutuvad, kui kangelase asend (omadused hero.x, hero.y) muutuvad, sellepärast lisame maskile ka funktsiooni nende muutmiseks:

hero.mask = {}; //mask on objekt!
hero.mask.width = hero.width/2;
hero.mask.height = hero.height/3;
hero.mask.reset = function() {
	hero.mask.x = hero.x + hero.width/4;
	hero.mask.y = hero.y + hero.height - hero.mask.height;
}
Kolli mask määratakse pärast kolli loomist analoogiliselt; kuna koll on ümmargune, ei erine tema mask erit1 palju tema spriidist, on vaid madalam:
koll.mask = {};
koll.mask.width = koll.width;
koll.mask.height = koll.height/2;
koll.mask.reset = function() {
	koll.mask.x = koll.x;
	koll.mask.y = koll.y+koll.height/2;
}
Puude ja seente mask määratakse samuti pärast puude/seente loomist; kuna seened on väikesed, ei erine nede mask nende spriidist
var puu = new sheet_obj(trees,ind,x,y);
		puu.mask = {};
		puu.mask.width = puu.width/6;
		puu.mask.x = puu.x + (puu.width - puu.mask.width)/2  ;
		puu.mask.height = puu.height/6;
		puu.mask.y = puu.y + puu.height - puu.mask.height;
    ...
 var seen = new sheet_obj(seened,ind,x,y);
		seen.mask = {};
		seen.mask.x = seen.x;
		seen.mask.width = seen.width;
		seen.mask.y = seen.y;
		seen.mask.height = seen.height;
 

Kokkupõrked (collisions)

Kangelase (ka kolli) liikumisel tuleb kontrollida kokkupõrkeid. Kangelase kokkupõrkel seenega korjab kangelane seene, kokkupõrkel kolliga - saab surma, kokkupõrge puuga ei võimalda samas suunas edasi liikumist. Kõik mängu objektid on salvestatud massiivis allObjects, seega tuleb (funktsiooni update lõpus) teha tsükkel üle kõigi selle massiivi objektid; millise objektidga on tegemist, määrab objekti omadus type. Kokkupõrkeid kontrollitakse funktsiooni update kolmandas osas; string userText kirjeldab infot, mis joonistatakse mängija jaoks mänguväljale (mitte mingisse eraldi olevasse menüüaknasse, see on nn. HUD -:heads-up display):

	// Collisions !!
	for(var i = allObjects.length - 1; i >= 0; i--){
	switch (allObjects[i].type){
		case 'hero': 
			//continue;
			break;
		case 'koll' :
		if (collision(hero,koll)){
			userText = "SAID SURMA !";
			koll.state = 0;
			hero.dead = true;
			//reset();
			}
		break;
		
		case 'seen':
		if 	(collision(hero,allObjects[i])){
			allObjects.splice(i,1);
			++collected;
			seeni += -1;
			hero.health += 1;
			userText = "Korjatud "+collected + " seent, metsas on veel "+seeni + "Tervis: "+hero.health;
			}
		break;
		case 'puu':
			if (collision1(hero,allObjects[i])){
			hero.x = oldHeroX;
			hero.y = oldHeroY;
			hero.mask.reset();
			}
			/*
			if (collision(koll,allObjects[i])){
			koll.x = oldKollX;
			koll.y = oldKollY;
			koll.mask.reset();
			//koll.suund = nextSuund(koll.suund);
			}
			*/
		break;
		}
	}

Kolli kokkupõrkel puuga pöörab koll kas paremale või vasakule (kuid mitte tagasi!); uue suuna arvutab funktsioon

function nextSuund(suund){
	var uusSuund;
	if (suund == 'NE' || suund == 'SW')
		uusSuund = (Math.random()>0.5)?'NW':'SE';
	else 
		uusSuund = (Math.random()>0.5)?'NE':'SW';
	return uusSuund;
}

Kokkupõrked arvutatakse maski abil:

function collision1(obj1,obj2){
return 	obj2.mask.x <= (obj1.mask.x + obj1.mask.width) && 
		obj1.mask.x <= (obj2.mask.x + obj2.mask.width) &&
		obj2.mask.y <= (obj1.mask.y + obj1.mask.height) &&
		obj1.mask.y <= (obj2.mask.y + obj2.mask.height);
}

Info väljastamine mängijale

Et mängija surm ilmuks mõjuvamalt, väljastatakse see suurelt ja punasena:
function showScore(txt){
	// Score
	if (hero.dead){
		ctx.fillStyle = "rgb(250, 0, 0)";
		ctx.font = "bold 36px Arial";
		}
	else {
		ctx.fillStyle = "rgb(250, 250, 250)";
		ctx.font = "14px Arial";
		ctx.textAlign = "left";
		ctx.textBaseline = "top";
		}
		ctx.fillText(txt, w0/2, h0); //text, x,y
};

Peafunktsioon

Kogu mängu käivitav funktsioon on täiesti analoogiline varem kasutatud funktsiooniga:

var main = function () {
	render();
	var now = Date.now();
	var delta = now - then;
	//console.log(collected,seeni);
	if ( seeni > 0 && !hero.dead ) {
		koll.AI(0.1);
		update(delta / 100, delta/1000);
		then = now;
		setTimeout(main,30);  //repeat
	}
	else {
		var time=Math.round(((now-startTime)/1000))
		showScore(userText);
	}
};

Kolli laserrelv

koll_laserKollil võib olla veel üks relv - ta tulistab kangelast, kui see on nähtav, s.t. asub kolliga samas puude vahelises koridoris. Puude NW-SE ridade tõus (y2-y1)/(x2-x1) = 0.5 , seega koll tulistab laseriga, kui
- ta näeb kangelast, s.t. nende vaheline kaugus on väiksem kui tema nähemisraadius - siin varem 2*(w+h)/3
- nendevahelise sirge tõus on 'peaaegu' 0.5 (erineb sellest vähem kui ette antud nurk, näit 0.1 radiaani).

Laseriga relvastatud kolli arukuse funktsioon sisaldab nüüd ka kolli tulistamist:

koll.AI = function(angle){
	if ((Math.abs(hero.x - koll.x) + Math.abs(hero.y - koll.y)) < (w+h)/3) {
		koll.state = 1; //liigub
	if ((koll.x < hero.x ) && (koll.y < hero.y)){
		koll.suund = "SE";
		if (Math.abs((koll.y - hero.y)/(koll.x - hero.x) + 0.5) < angle ) {
			koll.fire = true;
			}
		else
			koll.fire = false;
		}
		else if ((koll.x > hero.x) && (koll.y < hero.y)) {	
		koll.suund = "SW";
		if (Math.abs((koll.y - hero.y)/(koll.x - hero.x) + 0.5) < angle ) {
		koll.fire = true;
		}
		else
			koll.fire = false;
			}
		else if ((koll.x > hero.x ) && (koll.y > hero.y )) {	
		koll.suund = "NW";
		if (Math.abs((koll.y - hero.y)/(koll.x - hero.x) - 0.5) < angle ) {
		koll.fire = true;
		}
		else
			koll.fire = false;
			}
		else if ((koll.x < hero.x) && (koll.y > hero.y)) { 	
			koll.suund = "NE";
			if (Math.abs((koll.y - hero.y)/(koll.x - hero.x)+ 0.5) < angle ) {
			koll.fire = true;
			}
			else
			koll.fire = false;
			}
		}
	else 
		koll.state = 0;;
	//console.log(koll.suund);
}

Vastavalt tuleb täiendada ka objektide ekraanile joonistamist: kõigepeal kolli laseri kiir (et see ilmuks kolli alt, mitte pealt!) ja siis kõik ülejäänud objektid:

function drawObjects(){
	if (koll.fire){
	ctx.beginPath();
		ctx.moveTo(koll.x + koll.width/2, koll.y + koll.height/2);
		ctx.lineTo(hero.x + hero.width/2, hero.y + hero.height/2);
		ctx.stroke();
		hero.health += -2; //laser tapab!!!
		if (hero.health < 0) {
			hero.dead = true;
			userText = "Pole enam tervist...";
		}
	}
	allObjects.sort(compare);
	for (var i = 0; i < allObjects.length; i++) {
		allObjects[i].draw();}
}