Création de jeux 3D basés sur un navigateur à partir de zéro en pur html, css et js. Partie 2/2

Dans cet article, nous continuerons à créer un jeu de labyrinthe par navigateur 3D en pur html, css et javascript. Dans la partie précédente, nous avons fait un monde simple en 3 dimensions, implémenté le mouvement, le contrôle, les collisions du joueur avec des objets statiques. Dans cette partie, nous ajouterons la gravité, la lumière du soleil statique (pas d'ombres), chargerons des sons et créerons des menus. Hélas, comme dans la première partie, il n'y aura pas de démos ici.



Rappelons le code que nous avons fait dans la partie précédente. Nous avons 3 fichiers:



index.html
<!DOCTYPE HTML>
<HTML>
<HEAD>
	<TITLE></TITLE>
	<LINK rel="stylesheet" href="style.css">
	<meta charset="utf-8">
</HEAD>
<BODY>
	<div id="container">
		<div id="world">
		</div>
		<div id="pawn"></div>
	</div>
</BODY>
</HTML>
<script src="script.js"></script>




style.css
#container{
	position:absolute;
	width:1200px;
	height:800px;
	border:2px solid #000000;
	perspective:600px;
	overflow:hidden;
}
#world{
	position:absolute;
	width:inherit;
	height:inherit;
	transform-style:preserve-3d;
}
.square{
	position:absolute;
}
#pawn{
	display:none;
	position:absolute;
	top:400px;
	left:600px;
	transform:translate(-50%,-50%);
	width:100px;
	height:100px;
}




script.js
//  

var pi = 3.141592;
var deg = pi/180;

//  player

function player(x,y,z,rx,ry) {
	this.x = x;
	this.y = y;
	this.z = z;
	this.rx = rx;
	this.ry = ry;
}

//  

var map = [
		   [0,0,1000,0,180,0,2000,200,"#F0C0FF"],
		   [0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
		   [1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
		   [-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
		   [0,100,0,90,0,0,2000,2000,"#666666"]
];

//       ?

var PressBack = 0;
var PressForward = 0;
var PressLeft = 0;
var PressRight = 0;
var PressUp = 0;
var MouseX = 0;
var MouseY = 0;

//    ?

var lock = false;

//    ?

var onGround = true;

//     container

var container = document.getElementById("container");

//     

document.addEventListener("pointerlockchange", (event)=>{
	lock = !lock;
});

//    

container.onclick = function(){
	if (!lock) container.requestPointerLock();
};

//   

document.addEventListener("keydown", (event) =>{
	if (event.key == "a"){
		PressLeft = 1;
	}
	if (event.key == "w"){
		PressForward = 1;
	}
	if (event.key == "d"){
		PressRight = 1;
	}
	if (event.key == "s"){
		PressBack = 1;
	}
	if (event.keyCode == 32 && onGround){
		PressUp = 1;
	}
});

//   

document.addEventListener("keyup", (event) =>{
	if (event.key == "a"){
		PressLeft = 0;
	}
	if (event.key == "w"){
		PressForward = 0;
	}
	if (event.key == "d"){
		PressRight = 0;
	}
	if (event.key == "s"){
		PressBack = 0;
	}
	if (event.keyCode == 32){
		PressUp = 0;
	}
});

//   

document.addEventListener("mousemove", (event)=>{
	MouseX = event.movementX;
	MouseY = event.movementY;
});

//   

var pawn = new player(-900,0,-900,0,0);

//     world

var world = document.getElementById("world");

function update(){
	
	//    
	
	dx =   (PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg);
	dz = - (PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg);
	dy = - PressUp;
	drx = MouseY;
	dry = - MouseX;
	
	//   :
	
	MouseX = MouseY = 0;
	
	//    
	
	collision();
	
	//    
	
	pawn.x = pawn.x + dx;
	pawn.y = pawn.y + dy;
	pawn.z = pawn.z + dz;
	console.log(pawn.x + ":" + pawn.y + ":" + pawn.z);
	
	//   ,  
	
	if (lock){
		pawn.rx = pawn.rx + drx;
		pawn.ry = pawn.ry + dry;
	};

	//    ( )
	
	world.style.transform = 
	"translateZ(" + (600 - 0) + "px)" +
	"rotateX(" + (-pawn.rx) + "deg)" +
	"rotateY(" + (-pawn.ry) + "deg)" +
	"translate3d(" + (-pawn.x) + "px," + (-pawn.y) + "px," + (-pawn.z) + "px)";
	
};

function CreateNewWorld(){
	for (let i = 0; i < map.length; i++){
		
		//      
		
		let newElement = document.createElement("div");
		newElement.className = "square";
		newElement.id = "square" + i;
		newElement.style.width = map[i][6] + "px";
		newElement.style.height = map[i][7] + "px";
		newElement.style.background = map[i][8];
		newElement.style.transform = "translate3d(" +
		(600 - map[i][6]/2 + map[i][0]) + "px," +
		(400 - map[i][7]/2 + map[i][1]) + "px," +
		(map[i][2]) + "px)" +
		"rotateX(" + map[i][3] + "deg)" +
		"rotateY(" + map[i][4] + "deg)" +
		"rotateZ(" + map[i][5] + "deg)";
		
		//    world
		
		world.append(newElement);
	}
}

function collision(){
	for(let i = 0; i < map.length; i++){
		
		//       
		
		let x0 = (pawn.x - map[i][0]);
		let y0 = (pawn.y - map[i][1]);
		let z0 = (pawn.z - map[i][2]);
		
		if ((x0**2 + y0**2 + z0**2 + dx**2 + dy**2 + dz**2) < (map[i][6]**2 + map[i][7]**2)){
		
			let x1 = x0 + dx;
			let y1 = y0 + dy;
			let z1 = z0 + dz;
		
			let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
			let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
			let point2 = new Array();
		
			//      
		
			if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
				point1[2] = Math.sign(point0[2])*50;
				point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
				dx = point2[0] - x0;
				dy = point2[1] - y0;
				dz = point2[2] - z0;
			}
			
		}
	};
}

function coorTransform(x0,y0,z0,rxc,ryc,rzc){
	let x1 =  x0;
	let y1 =  y0*Math.cos(rxc*deg) + z0*Math.sin(rxc*deg);
	let z1 = -y0*Math.sin(rxc*deg) + z0*Math.cos(rxc*deg);
	let x2 =  x1*Math.cos(ryc*deg) - z1*Math.sin(ryc*deg);
	let y2 =  y1;
	let z2 =  x1*Math.sin(ryc*deg) + z1*Math.cos(ryc*deg);
	let x3 =  x2*Math.cos(rzc*deg) + y2*Math.sin(rzc*deg);
 	let y3 = -x2*Math.sin(rzc*deg) + y2*Math.cos(rzc*deg);
	let z3 =  z2;
	return [x3,y3,z3];
}

function coorReTransform(x3,y3,z3,rxc,ryc,rzc){
	let x2 =  x3*Math.cos(rzc*deg) - y3*Math.sin(rzc*deg);
	let y2 =  x3*Math.sin(rzc*deg) + y3*Math.cos(rzc*deg);
	let z2 =  z3
	let x1 =  x2*Math.cos(ryc*deg) + z2*Math.sin(ryc*deg);
	let y1 =  y2;
	let z1 = -x2*Math.sin(ryc*deg) + z2*Math.cos(ryc*deg);
	let x0 =  x1;
	let y0 =  y1*Math.cos(rxc*deg) - z1*Math.sin(rxc*deg);
	let z0 =  y1*Math.sin(rxc*deg) + z1*Math.cos(rxc*deg);
	return [x0,y0,z0];
}

CreateNewWorld();
TimerGame = setInterval(update,10);




1. Mise en œuvre de la physique de la gravité et des sauts



Nous avons plusieurs variables créées dans différentes parties du fichier javascript. Ce sera mieux si nous les déplaçons à un seul endroit:



//  

var lock = false;
var onGround = true;
var container = document.getElementById("container");
var world = document.getElementById("world");


Ajoutons-leur une accélération de chute libre:



var g = 0.1;




Ajoutez 3 variables au constructeur du lecteur - vx, vy et vz:



function player(x,y,z,rx,ry) {
	this.x = x;
	this.y = y;
	this.z = z;
	this.rx = rx;
	this.ry = ry;
	this.vx = 3;
	this.vy = 5;
	this.vz = 3;
}


Ce sont des vitesses variables. En les changeant, nous pouvons changer la vitesse de course et la vitesse de saut initiale du joueur. Pour l'instant, appliquons les nouvelles variables dans update ():



 
       dx =   ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
	dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
	dy = PressUp*pawn.vy;
	drx = MouseY;
	dry = - MouseX;


Le joueur se déplace maintenant plus vite. Mais il ne tombe pas et ne saute pas. Il faut autoriser un saut quand il est sur quelque chose. Et il restera debout lorsqu'il entre en collision avec une surface horizontale (ou presque). Comment définir l'horizontalité? Nous devons trouver la normale du plan du rectangle. Cela se fait simplement. Par rapport aux coordonnées du rectangle, la normale est dirigée le long de l'axe z. Ensuite, la normale a transformé les coordonnées en coordonnées mondiales. Trouvez la normale (ajoutez la variable locale normal):



let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
let point2 = new Array();
let normal = coorReTransform(0,0,1,map[i][3],map[i][4],map[i][5]);


Pour que la surface soit horizontale, le produit scalaire de la normale à l'axe y en coordonnées universelles doit être 1 ou -1, et le plan quasi-horizontal doit être proche de 1 ou -1. Définissons la condition pour un plan presque horizontal:



if (Math.abs(normal[1]) > 0.8){
	onGround = true;
}


N'oubliez pas qu'en l'absence de collisions, le joueur ne sera certainement pas au sol, donc par défaut, au début de la fonction collision (), mettez onGround = false:



function collision(){
	
	onGround = false;
	
	for(let i = 0; i < map.length; i++){


Cependant, si le joueur entre en collision avec la surface par le bas, il apparaîtra également sur le sol. Pour éviter cela, vérifions que le joueur est au-dessus du plan (le point3 [1] doit être inférieur au point2 [1]):



let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
				dx = point2[0] - x0;
				dy = point2[1] - y0;
				dz = point2[2] - z0;
				if (Math.abs(normal[1]) > 0.8){
					if (point3[1] > point2[1]) onGround = true;
				}
				else dy = y1 - y0;



Que faisons-nous? Regardez l'image:







la croix rouge doit être sous l'orange dans le système de coordonnées mondial (ou la coordonnée y doit être plus grande). C'est ce que nous vérifions au point3 [1]> point2 [1]. Et point3 n'est que les coordonnées du point rouge. Déplaçons l'initialisation de point2 à l'intérieur de la condition de collision:



let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
			let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
			let normal = coorReTransform(0,0,1,map[i][3],map[i][4],map[i][5]);
		
			//      
		
			if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
				point1[2] = Math.sign(point0[2])*50;
				let point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
				let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
				dx = point2[0] - x0;
				dy = point2[1] - y0;
				dz = point2[2] - z0;
				if (Math.abs(normal[1]) > 0.8){
					if (point3[1] > point2[1]) onGround = true;
				}
			}


Passons à update (). Nous apporterons des modifications ici aussi. Tout d'abord, ajoutez la gravité et supprimez le décalage y en appuyant sur la barre d'espace:



//    

dx =   ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
	dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
	dy = dy + g;
	drx = MouseY;
	dry = - MouseX; 
 


Deuxièmement, si le joueur est au sol, nous interdisons la gravité, interdisons le déplacement en y (sinon, après avoir marché sur une surface inclinée, le joueur décollera) et ajoutons la possibilité de sauter (si condition (onGround)):



//    
	
	dx =   ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
	dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
	dy = dy + g;
	if (onGround){
		dy = 0;
		if (PressUp){
			dy = - PressUp*pawn.vy;
			onGround = false;
		}
	};
	drx = MouseY;
	dry = - MouseX;


Naturellement, immédiatement après le saut, nous interdisons le saut répété en définissant le paramètre onGround sur false. La validité de ce paramètre n'est plus nécessaire dans la condition de la barre d'espace:



if (event.keyCode == 32){
		PressUp = 1;
	}


Pour tester les changements, changeons le monde:



var map = [
		   [0,0,1000,0,180,0,2000,200,"#F0C0FF"],
		   [0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
		   [1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
		   [-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
		   [0,0,-300,70,0,0,200,500,"#F000FF"],
		   [0,-86,-786,90,0,0,200,500,"#F000FF"],
		   [-500,0,-300,20,0,0,200,500,"#00FF00"],
		   [0,100,0,90,0,0,2000,2000,"#666666"]
];


Si nous commençons le jeu, nous verrons que le joueur peut escalader un mur végétal presque vertical. Désactivez ceci en ajoutant else dy = y1 - y0:



if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
				point1[2] = Math.sign(point0[2])*50;
				let point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
				let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
				dx = point2[0] - x0;
				dy = point2[1] - y0;
				dz = point2[2] - z0;
				if (Math.abs(normal[1]) > 0.8){
					if (point3[1] > point2[1]) onGround = true;
				}
				else dy = y1 - y0;
			}


Ainsi, les collisions avec des murs très verticaux ne modifient pas le décalage y. Par conséquent, l'overclocking sur de tels murs est désormais complètement exclu. Essayons de gravir le mur végétal. Nous ne pourrons pas le faire maintenant. Nous avons donc compris la gravité et les sauts, et maintenant nous pouvons grimper de manière assez réaliste sur des surfaces légèrement inclinées. Vérifions le code:



index.html
<!DOCTYPE HTML>
<HTML>
<HEAD>
	<TITLE></TITLE>
	<LINK rel="stylesheet" href="style.css">
	<meta charset="utf-8">
</HEAD>
<BODY>
	<div id="container">
		<div id="world">
		</div>
		<div id="pawn"></div>
	</div>
</BODY>
</HTML>
<script src="script.js"></script>




style.css
#container{
	position:absolute;
	width:1200px;
	height:800px;
	border:2px solid #000000;
	perspective:600px;
	overflow:hidden;
}
#world{
	position:absolute;
	width:inherit;
	height:inherit;
	transform-style:preserve-3d;
}
.square{
	position:absolute;
}
#pawn{
	display:none;
	position:absolute;
	top:400px;
	left:600px;
	transform:translate(-50%,-50%);
	width:100px;
	height:100px;
}




script.js
//  

var pi = 3.141592;
var deg = pi/180;

//  player

function player(x,y,z,rx,ry) {
	this.x = x;
	this.y = y;
	this.z = z;
	this.rx = rx;
	this.ry = ry;
	this.vx = 3;
	this.vy = 5;
	this.vz = 3;
}

//  

var map = [
		   [0,0,1000,0,180,0,2000,200,"#F0C0FF"],
		   [0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
		   [1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
		   [-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
		   [0,0,-300,70,0,0,200,500,"#F000FF"],
		   [0,-86,-786,90,0,0,200,500,"#F000FF"],
		   [-500,0,-300,20,0,0,200,500,"#00FF00"],
		   [0,-800,0,90,0,0,500,500,"#00FF00"],
		   [0,-400,700,60,0,0,500,900,"#FFFF00"],
		   [0,100,0,90,0,0,2000,2000,"#666666"]
];

//       ?

var PressBack = 0;
var PressForward = 0;
var PressLeft = 0;
var PressRight = 0;
var PressUp = 0;
var MouseX = 0;
var MouseY = 0;

//  

var lock = false;
var onGround = false;
var container = document.getElementById("container");
var world = document.getElementById("world");
var g = 0.1;
var dx = dy = dz = 0; 

//     

document.addEventListener("pointerlockchange", (event)=>{
	lock = !lock;
});

//    

container.onclick = function(){
	if (!lock) container.requestPointerLock();
};

//   

document.addEventListener("keydown", (event) =>{
	if (event.key == "a"){
		PressLeft = 1;
	}
	if (event.key == "w"){
		PressForward = 1;
	}
	if (event.key == "d"){
		PressRight = 1;
	}
	if (event.key == "s"){
		PressBack = 1;
	}
	if (event.keyCode == 32){
		PressUp = 1;
	}
});

//   

document.addEventListener("keyup", (event) =>{
	if (event.key == "a"){
		PressLeft = 0;
	}
	if (event.key == "w"){
		PressForward = 0;
	}
	if (event.key == "d"){
		PressRight = 0;
	}
	if (event.key == "s"){
		PressBack = 0;
	}
	if (event.keyCode == 32){
		PressUp = 0;
	}
});

//   

document.addEventListener("mousemove", (event)=>{
	MouseX = event.movementX;
	MouseY = event.movementY;
});

//   

var pawn = new player(0,-900,0,0,0);

function update(){
	
	//    
	
	dx =   ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
	dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
	dy = dy + g;
	if (onGround){
		dy = 0;
		if (PressUp){
			dy = - PressUp*pawn.vy;
			onGround = false;
		}
	};
	drx = MouseY;
	dry = - MouseX;
	
	//   :
	
	MouseX = MouseY = 0;
	
	//    
	
	collision();
	
	//    
	
	pawn.x = pawn.x + dx;
	pawn.y = pawn.y + dy;
	pawn.z = pawn.z + dz;
	
	//   ,  
	
	if (lock){
		pawn.rx = pawn.rx + drx;
		pawn.ry = pawn.ry + dry;
	};

	//    ( )
	
	world.style.transform = 
	"translateZ(" + (600 - 0) + "px)" +
	"rotateX(" + (-pawn.rx) + "deg)" +
	"rotateY(" + (-pawn.ry) + "deg)" +
	"translate3d(" + (-pawn.x) + "px," + (-pawn.y) + "px," + (-pawn.z) + "px)";
	
};

function CreateNewWorld(){
	for (let i = 0; i < map.length; i++){
		
		//      
		
		let newElement = document.createElement("div");
		newElement.className = "square";
		newElement.id = "square" + i;
		newElement.style.width = map[i][6] + "px";
		newElement.style.height = map[i][7] + "px";
		newElement.style.background = map[i][8];
		newElement.style.transform = "translate3d(" +
		(600 - map[i][6]/2 + map[i][0]) + "px," +
		(400 - map[i][7]/2 + map[i][1]) + "px," +
		(map[i][2]) + "px)" +
		"rotateX(" + map[i][3] + "deg)" +
		"rotateY(" + map[i][4] + "deg)" +
		"rotateZ(" + map[i][5] + "deg)";
		
		//    world
		
		world.append(newElement);
	}
}

function collision(){
	
	onGround = false;
	
	for(let i = 0; i < map.length; i++){
		
		//       
		
		let x0 = (pawn.x - map[i][0]);
		let y0 = (pawn.y - map[i][1]);
		let z0 = (pawn.z - map[i][2]);
		
		if ((x0**2 + y0**2 + z0**2 + dx**2 + dy**2 + dz**2) < (map[i][6]**2 + map[i][7]**2)){
		
			let x1 = x0 + dx;
			let y1 = y0 + dy;
			let z1 = z0 + dz;
		
			let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
			let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
			let normal = coorReTransform(0,0,1,map[i][3],map[i][4],map[i][5]);
		
			//      
		
			if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
				point1[2] = Math.sign(point0[2])*50;
				let point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
				let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
				dx = point2[0] - x0;
				dy = point2[1] - y0;
				dz = point2[2] - z0;
				if (Math.abs(normal[1]) > 0.8){
					if (point3[1] > point2[1]) onGround = true;
				}
				else dy = y1 - y0;
			}
			
		}
	};
}

function coorTransform(x0,y0,z0,rxc,ryc,rzc){
	let x1 =  x0;
	let y1 =  y0*Math.cos(rxc*deg) + z0*Math.sin(rxc*deg);
	let z1 = -y0*Math.sin(rxc*deg) + z0*Math.cos(rxc*deg);
	let x2 =  x1*Math.cos(ryc*deg) - z1*Math.sin(ryc*deg);
	let y2 =  y1;
	let z2 =  x1*Math.sin(ryc*deg) + z1*Math.cos(ryc*deg);
	let x3 =  x2*Math.cos(rzc*deg) + y2*Math.sin(rzc*deg);
 	let y3 = -x2*Math.sin(rzc*deg) + y2*Math.cos(rzc*deg);
	let z3 =  z2;
	return [x3,y3,z3];
}

function coorReTransform(x3,y3,z3,rxc,ryc,rzc){
	let x2 =  x3*Math.cos(rzc*deg) - y3*Math.sin(rzc*deg);
	let y2 =  x3*Math.sin(rzc*deg) + y3*Math.cos(rzc*deg);
	let z2 =  z3
	let x1 =  x2*Math.cos(ryc*deg) + z2*Math.sin(ryc*deg);
	let y1 =  y2;
	let z1 = -x2*Math.sin(ryc*deg) + z2*Math.cos(ryc*deg);
	let x0 =  x1;
	let y0 =  y1*Math.cos(rxc*deg) - z1*Math.sin(rxc*deg);
	let z0 =  y1*Math.sin(rxc*deg) + z1*Math.cos(rxc*deg);
	return [x0,y0,z0];
}

CreateNewWorld();
TimerGame = setInterval(update,10);




2. Créez un menu



Créons le menu sous la forme de panneaux html et de blocs html. La conception de l'ensemble du menu sera à peu près la même: l'arrière-plan et le style des boutons peuvent être définis pour tous. Alors, définissons trois panneaux de menu: le menu principal, les instructions et la sortie des résultats à la fin du jeu. Les transitions entre le menu, la transition vers le monde et inversement seront effectuées par des scripts javascript. Afin de ne pas encombrer le fichier script.js, créez un nouveau fichier menu.js pour les transitions de menu et incluez-le dans index.html:



<script src="menu.js"></script>


Dans le conteneur, nous allons créer 3 éléments qui seront des barres de menu:



<div id="container">
    <div id = "world"></div>
    <div id = "pawn"></div>
    <div id = "menu1"></div>
    <div id = "menu2"></div>
    <div id = "menu3"></div>
</div>


Stylisons-les en ajoutant les propriétés de la classe «menu» à style.css:



.menu{
	display:none;
	position:absolute;
	width:inherit;
	height:inherit;
	background-color:#C0FFFF;
}


Ajoutez des boutons avec les légendes appropriées au menu (dans le fichier index.html):



                 <div class = "menu" id = "menu1">
			<div id="button1" class="button">
				<p> </p>
			</div>
			<div id="button2" class="button">
				<p></p>
			</div>
		</div>
		<div class = "menu" id = "menu2">
			<p style="font-size:30px; top:200px">
				<strong>:</strong> <br>
				w -  <br>
				s -  <br>
				d -  <br>
				a -  <br>
				 -  <br>
				!!!    !!!<br>
				<strong>:</strong> <br>
				      
			</p>
			<div id="button3" class="button">
				<p></p>
			</div>
		</div>
		<div class = "menu" id = "menu3">
			<p id = "result" style="top:100px"></p>
			<div id="button4" class="button">
				<p> </p>
			</div>
		</div>


Pour les boutons, nous définirons également les styles dans style.css:



.button{
	margin:0px;
	position:absolute;
	width:900px;
	height:250px;
	background-color:#FFF;
	cursor:pointer;
}
.button:hover{
	background-color:#DDD;
}

#button1{
	top:100px;
	left:150px;
}
#button2{
	top:450px;
	left:150px;
}
#button3{
	top:450px;
	left:150px;
}
#button4{
	top:450px;
	left:150px;
}


Mais nous ne voyons pas le menu, car ils ont l'affichage: aucun style. Lorsque le jeu démarre, l'un des éléments du menu doit être visible, donc dans le html du 1er menu, ajoutez l'entrée style = "display: block;", et ressemblez à ceci serait comme ça:



<div class = "menu" id = "menu1" style = "display:block;">


Le menu ressemble maintenant à ceci:







Super. Mais si nous cliquons sur le bouton, le curseur sera capturé. Nous devons donc autoriser la capture de la souris uniquement dans le cas d'un jeu. Pour ce faire, entrez la variable canlock dans script.js et ajoutez-la à l'élément create variables:



//  

var lock = false;
var onGround = false;
var container = document.getElementById("container");
var world = document.getElementById("world");
var g = 0.1;
var dx = dy = dz = 0;
var canlock = false;

      :

//    

container.onclick = function(){
	if (!lock && canlock) container.requestPointerLock();
};


Nous pouvons maintenant cliquer sur le menu. Configurons les transitions à l'aide de scripts dans le fichier menu.js:



//  

var menu1 = document.getElementById("menu1");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
var button1 = document.getElementById("button1");
var button2 = document.getElementById("button2");
var button3 = document.getElementById("button3");
var button4 = document.getElementById("button4");

//  

button2.onclick = function(){
	menu1.style.display = "none";
	menu2.style.display = "block";
}

button3.onclick = function(){
	menu1.style.display = "block";
	menu2.style.display = "none";
}

button4.onclick = function(){
	menu1.style.display = "block";
	menu3.style.display = "none";
}


Désormais, tous les boutons de menu, à l'exception de «démarrer le jeu», fonctionnent. Configurons maintenant le bouton button1. Si vous vous en souvenez, dans le fichier script.js, les fonctions CreateNewWorld () et setInterval () sont déclenchées lors du chargement de la page Web. Supprimons-les de là. Nous ne les appellerons que lorsque le bouton1 sera enfoncé. Faisons le:



button1.onclick = function(){
	menu1.style.display = "none";
	CreateNewWorld();
	TimerGame = setInterval(update,10);
}


Nous avons créé un menu. Oui, c'est toujours moche, mais ça va mieux facilement.



3. Créons des objets et des transitions de niveaux.



Tout d'abord, définissons les règles du jeu. Nous avons trois types d'objets: les pièces (carrés jaunes), les clés (carrés rouges) et la finition (carré bleu). Les pièces rapportent des points. Le joueur doit trouver la clé et ensuite seulement arriver à la ligne d'arrivée. S'il arrive à la ligne d'arrivée sans clé, il recevra un message sur la nécessité de trouver d'abord la clé. Les objets seront créés de la même manière que la carte. Nous les écrirons à l'aide de tableaux. Mais nous ne leur ferons pas de fonction distincte. Nous allons simplement écrire une nouvelle fonction qui arrange à la fois les éléments de la carte et du rectangle et reporte les commandes de CreateNewWorld (). Appelons cela CreateSquares (). Ajoutons donc l'entrée suivante à la fin du fichier script.js:



function CreateSquares(squares,string){
	for (let i = 0; i < squares.length; i++){
		
		//      
		
		let newElement = document.createElement("div");
		newElement.className = string + " square";
		newElement.id = string + i;
		newElement.style.width = squares[i][6] + "px";
		newElement.style.height = squares[i][7] + "px";
		newElement.style.background = squares[i][8];
		newElement.style.transform = "translate3d(" +
		(600 - squares[i][6]/2 + squares[i][0]) + "px," +
		(400 - squares[i][7]/2 + squares[i][1]) + "px," +
		(squares[i][2]) + "px)" +
		"rotateX(" + squares[i][3] + "deg)" +
		"rotateY(" + squares[i][4] + "deg)" +
		"rotateZ(" + squares[i][5] + "deg)";
		
		//    world
		
		world.append(newElement);
	}
}


Et nous allons changer le contenu de createNewWorld ():



function CreateNewWorld(){
	CreateSquares(map,”map”);
}


La chaîne est nécessaire pour définir le nom id. Le jeu n'a pas encore changé un peu. Ajoutons maintenant 3 tableaux: pièces (objets), clés (clés) et finition (finition). Insérons-les juste après le tableau de la carte:



var things = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
		    [-400,50,900,0,0,0,50,50,"#FFFF00"],
		    [-400,50,-300,0,0,0,50,50,"#FFFF00"]];
			  
var keys = [[-100,50,600,0,0,0,50,50,"#FF0000"]];	

var start = [[-900,0,-900,0,0]];

var finish = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];


Et dans menu.js, utilisons la fonction CreateSquares () à l'intérieur du gestionnaire pour appuyer sur le bouton «button1»:



button1.onclick = function(){
	menu1.style.display = "none";
	CreateNewWorld();
	CreateSquares(things,”thing”);
	CreateSquares(keys,”key”);
	CreateSquares(finish,”finish”);
	TimerGame = setInterval(update,10);
	canlock = true;
}


Maintenant, mettons en place la disparition des objets. Dans menu.js, créons une fonction pour vérifier les distances entre le joueur et les objets:



function interact(objects,string){
	for (i = 0; i < objects.length; i++){
		let r = (objects[i][0] - pawn.x)**2 + (objects[i][1] - pawn.y)**2 + (objects[i][2] - pawn.z)**2;
		if(r < (objects[i][7]**2)/4){
			document.getElementById(string + i).style.display = "none";
                        document.getElementById(string + i).style.transform = 
			"translate3d(1000000px,1000000px,1000000px)";
		};
	};
}


Aussi, dans le même fichier, créez la fonction repeatFunction () et ajoutez-y les commandes:



function repeatFunction(){
	update();
	interact(things,"thing");
	interact(keys,"key");
}


Et nous allons exécuter son appel cyclique dans setInterval à l'intérieur de button1:



TimerGame = setInterval(repeatFunction,10);


Désormais, les objets disparaissent lorsque nous les approchons. Cependant, ils ne font absolument rien. Et nous voulons que des points nous soient ajoutés lorsque vous prenez des carrés jaunes, et lorsque vous prenez des carrés rouges, nous avons la possibilité de prendre du bleu et de terminer la partie. Modifions la fonction interact ():



function interact(objects,string,num){
	for (i = 0; i < objects.length; i++){
		let r = (objects[i][0] - pawn.x)**2 + (objects[i][1] - pawn.y)**2 + (objects[i][2] - pawn.z)**2;
		if(r < (objects[i][7]**2)){
			document.getElementById(string + i).style.display = "none";
			objects[i][0] = 1000000;
			objects[i][1] = 1000000;
			objects[i][2] = 1000000;
			document.getElementById(string + i).style.transform = 
			"translate3d(1000000px,1000000px,1000000px)";
			num[0]++;
		};
	};
}


Modifions les paramètres d'entrée pour les appels à cette fonction:



function repeatFunction(){
	update();
	interact(things,"thing",m);
	interact(keys,"key",k);
}


Et au début du fichier, ajoutez quatre nouvelles variables:



var m = [0];
var k = [0];
var f = [0];
var score = 0;


Pourquoi avons-nous créé des tableaux d'un élément et pas seulement de variables, demandez-vous? Le fait est que nous voulions passer ces variables à interagir () par référence, pas par valeur. En javascript, les variables régulières ne sont passées que par valeur et les tableaux sont passés par référence. Si nous ne passons qu'une variable à interagir (), alors num sera une copie de la variable. Changer num ne changera pas k ou m. Et si nous passons un tableau, alors num sera une référence au tableau k ou m, et lorsque nous changerons num [0], alors k [0] et m [0] changeront. Il était bien sûr possible de créer 2 fonctions presque identiques, mais il vaut mieux s'en tirer avec une, un peu plus universelle.



Pour la finition, vous devez encore créer une fonction distincte:



function finishInteract(){
	let r = (finish[0][0] - pawn.x)**2 + (finish[0][1] - pawn.y)**2 + (finish[0][2] - pawn.z)**2;
	if(r < (finish[0][7]**2)){
		if (k[0] == 0){
			console.log(" ");
		}
		else{
			clearWorld();
                        clearInterval(TimerGame);
			document.exitPointerLock();
                        score = score + m[0];
			k[0] = 0;
                        m[0] = 0;
			menu1.style.display = "block";
		};
	};
};


Et configurez clearWorld () dans script.js:



function clearWorld(){
	world.innerHTML = "";
}


Comme vous pouvez le voir, nettoyer le monde est assez simple. Ajoutez finishInteract () à la fonction repeatFunction ():



function repeatFunction(){
	update();
	interact(things,"thing",m);
	interact(keys,"key",k);
	finishInteract();
}


Que se passe-t-il dans finishInteract ()? Si nous n'avons pas pris la clé (k [0] == 0), alors rien ne se passe encore. S'ils l'ont fait, le jeu se termine et ce qui suit se produit: le monde est effacé, la fonction repeatFunction () s'arrête, le curseur cesse d'être capturé, le compteur de touches est réinitialisé et nous allons au menu principal. Vérifions en exécutant le jeu. Tout fonctionne. Cependant, après avoir cliqué à nouveau sur le jeu, on se retrouve immédiatement à la ligne d'arrivée, et certains objets disparaissent. C'est parce que nous n'avons pas entré l'emplacement du point d'apparition initial du joueur et que les tableaux changent pendant la partie. Ajoutons un point d'apparition pour le joueur à button1, c'est-à-dire assimilons ses coordonnées aux éléments du tableau de départ [0]:



button1.onclick = function(){
	menu1.style.display = "none";
	CreateNewWorld();
	pawn.x = start[0][0];
	pawn.y = start[0][1];
	pawn.z = start[0][2];
	pawn.rx = start[0][3];
	pawn.rx = start[0][4];
	CreateSquares(things,"thing");
	CreateSquares(keys,"key");
	CreateSquares(finish,"finish");
	TimerGame = setInterval(repeatFunction,10);
	canlock = true;
}


Le joueur apparaît maintenant à l'origine. Mais la question est: et s'il y avait plusieurs niveaux dans le jeu? Ajoutons une variable de niveau à menu.js:



//  

var menu1 = document.getElementById("menu1");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
var button1 = document.getElementById("button1");
var button2 = document.getElementById("button2");
var button3 = document.getElementById("button3");
var button4 = document.getElementById("button4");
var m = [0];
var k = [0];
var f = [0];
var score = 0;
var level = 0;


Refaisons la carte des variables, les choses, les clés, le début, la fin à l'intérieur de script.js en tableaux, en changeant légèrement leur nom:



// 1 

mapArray[0] = [
		   [0,0,1000,0,180,0,2000,200,"#F0C0FF"],
		   [0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
		   [1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
		   [-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
		   [0,0,-300,70,0,0,200,500,"#F000FF"],
		   [0,-86,-786,90,0,0,200,500,"#F000FF"],
		   [-500,0,-300,20,0,0,200,500,"#00FF00"],
		   [0,100,0,90,0,0,2000,2000,"#666666"]
];

thingsArray [0] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,-300,0,0,0,50,50,"#FFFF00"]];
			  
keysArray [0] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];	

startArray[0] = [[-900,0,-900,0,0]];

finishArray [0] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];


Ajoutons le 2ème niveau:



// 2 

mapArray [1] = [
		   [0,0,1000,0,180,0,2000,200,"#F0C0FF"],
		   [0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
		   [1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
		   [-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
		   [0,0,-300,70,0,0,200,500,"#F000FF"],
		   [0,-86,-786,90,0,0,200,500,"#F000FF"],
		   [-500,0,-300,20,0,0,200,500,"#00FF00"],
		   [0,100,0,90,0,0,2000,2000,"#666666"]
];

thingsArray [1] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,-300,0,0,0,50,50,"#FFFF00"]];
			  
keysArray [1] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];

startArray[1] = [[0,0,0,0,0]];	

finishArray [1] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];


Et les tableaux eux-mêmes sont initialisés avant les niveaux:



//   

var mapArray = new Array();
var thingsArray = new Array();
var keysArray = new Array();
var startArray = new Array();
var finishArray = new Array();


la fonction CreateNewWorld () devra être modifiée en y ajoutant un argument:



function CreateNewWorld(map){
	CreateSquares(map,"map");
}


Modifions l'appel à CreateNewWorld () dans le fichier menu.js:



button1.onclick = function(){
	menu1.style.display = "none";
	CreateNewWorld(map);
	pawn.x = start[0][0];
	pawn.y = start[0][1];
	pawn.z = start[0][2];
	pawn.rx = start[0][3];
	pawn.rx = start[0][4];
	CreateSquares(things,"thing");
	CreateSquares(keys,"key");
	CreateSquares(finish,"finish");
	TimerGame = setInterval(repeatFunction,10);
	canlock = true;
}


Maintenant, la console donnera une erreur au démarrage. Bien, parce que nous avons renommé la carte des variables, les choses, les clés et la finition, maintenant javascript ne peut pas comprendre ce que sont ces variables. Nous les initialisons à nouveau dans script.js:



//   

var map;
var things;
var keys;
var start;
var finish;


Et dans button1 (dans menu.js) nous affectons des copies des éléments des tableaux mapArray, thingsArray, keysArray et finishArray à ces variables (pour une meilleure lisibilité, mettez des commentaires):



button1.onclick = function(){
	
	//   
	
	map = userSlice(mapArray[level]);
	things = userSlice(thingsArray[level]);
	keys = userSlice(keysArray[level]);
        start = userSlice(startArray[level]);
	finish = userSlice(finishArray[level]);	

	//     
	
	menu1.style.display = "none";
	CreateNewWorld(map);
	pawn.x = start[0][0];
	pawn.y = start[0][1];
	pawn.z = start[0][2];
	pawn.rx = start[0][3];
	pawn.rx = start[0][4];
	CreateSquares(things,"thing");
	CreateSquares(keys,"key");
	CreateSquares(finish,"finish");
	
	//  
	
	TimerGame = setInterval(repeatFunction,10);
	canlock = true;
}


Où userSlice () est la fonction qui copie le tableau:



function userSlice(array){
	let NewArray = new Array();
	for (let i = 0; i < array.length; i++){
		NewArray[i] = new Array();
		for (let j = 0; j < array[i].length; j++){
			NewArray[i][j] = array[i][j];
		}
	}
	return NewArray;
}


Si nous écrivions simplement, par exemple, keys = keysArray [level], alors non pas des copies des tableaux ne seraient transférées vers les variables, mais des pointeurs vers elles, ce qui signifie qu’elles changeraient pendant le jeu, ce qui est inacceptable, car lorsque vous redémarrez la clé sur le lieu d'origine n'existerait plus. Vous vous demandez probablement pourquoi je n'ai pas simplement utilisé keysArray [level] .slice (), mais j'ai inventé mes propres fonctions? Après tout, slice () copie également les tableaux. J'ai essayé de le faire, mais il copiait la référence vers le tableau, pas le tableau lui-même, à la suite de quoi le changement de clés a conduit à changer keysArray [niveau], ce qui signifiait que la clé a disparu au redémarrage. Le fait est que la documentation dit que dans certains cas, il perçoit les tableaux comme des tableaux et les copie, dans d'autres il perçoit les tableaux comme des objets et ne copie que des pointeurs vers eux. Comment il le définit est un mystère pour moi,donc si quelqu'un peut me dire pourquoi slice () ne fonctionne pas comme prévu, je lui en serai très reconnaissant.



Faisons la transition des niveaux. C'est assez simple. Modifions finishInteract () en ajoutant les lignes suivantes à l'intérieur du else:



level++;
if(level >= 2){
	level = 0;
	score = 0;
};


Autrement dit, la valeur du niveau est ajoutée de 1, et si tous les niveaux sont passés (nous en avons 2), les niveaux sont réinitialisés et les points de score sont réinitialisés. Cela est difficile à vérifier, car nos niveaux ne sont plus différents. Changeons alors mapArray [1]:



mapArray[1] = [
		   [0,0,1000,0,180,0,2000,200,"#00FF00"],
		   [0,0,-1000,0,0,0,2000,200,"#00FF00"],
		   [1000,0,0,0,-90,0,2000,200,"#00FF00"],
		   [-1000,0,0,0,90,0,2000,200,"#00FF00"],
		   [0,100,0,90,0,0,2000,2000,"#666666"]
];


Nous avons changé la couleur des murs. Jouons à un jeu. On voit qu'après avoir passé le premier niveau (avec des murs violets et plusieurs rectangles), on passe au second (avec des murs verts), et quand on passe le second, on revient au premier. Donc, nous avons terminé la transition des niveaux. Il ne reste plus qu'à concevoir le jeu, à changer les polices, à colorer le monde et à rendre les niveaux un peu plus difficiles. Nous n'avons pas modifié les fichiers index.html et style.css, vérifiez donc les scripts:



script.js
//  

var pi = 3.141592;
var deg = pi/180;

//  player

function player(x,y,z,rx,ry) {
	this.x = x;
	this.y = y;
	this.z = z;
	this.rx = rx;
	this.ry = ry;
	this.vx = 3;
	this.vy = 5;
	this.vz = 3;
}

//   

var mapArray = new Array();
var thingsArray = new Array();
var keysArray = new Array();
var startArray = new Array();
var finishArray = new Array();

// 1 

mapArray[0] = [
		   [0,0,1000,0,180,0,2000,200,"#F0C0FF"],
		   [0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
		   [1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
		   [-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
		   [0,0,-300,70,0,0,200,500,"#F000FF"],
		   [0,-86,-786,90,0,0,200,500,"#F000FF"],
		   [-500,0,-300,20,0,0,200,500,"#00FF00"],
		   [0,100,0,90,0,0,2000,2000,"#666666"]
];

thingsArray[0] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,-300,0,0,0,50,50,"#FFFF00"]];
			  
keysArray[0] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];	

startArray[0] = [[-900,0,-900,0,0]];

finishArray[0] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];


// 2 

mapArray[1] = [
		   [0,0,1000,0,180,0,2000,200,"#00FF00"],
		   [0,0,-1000,0,0,0,2000,200,"#00FF00"],
		   [1000,0,0,0,-90,0,2000,200,"#00FF00"],
		   [-1000,0,0,0,90,0,2000,200,"#00FF00"],
		   [0,100,0,90,0,0,2000,2000,"#666666"]
];

thingsArray[1] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,-300,0,0,0,50,50,"#FFFF00"]];
			  
keysArray[1] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];	

startArray[1] = [[0,0,0,0,0]];

finishArray[1] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];

//   

var map = new Array();
var things = new Array();
var keys = new Array();
var start = new Array();
var finish = new Array();

//       ?

var PressBack = 0;
var PressForward = 0;
var PressLeft = 0;
var PressRight = 0;
var PressUp = 0;
var MouseX = 0;
var MouseY = 0;

//  

var lock = false;
var onGround = false;
var container = document.getElementById("container");
var world = document.getElementById("world");
var g = 0.1;
var dx = dy = dz = 0;
var canlock = false; 

//      

document.addEventListener("pointerlockchange", (event)=>{
	lock = !lock;
});

//    

container.onclick = function(){
	if (!lock && canlock) container.requestPointerLock();
};

//   

document.addEventListener("keydown", (event) =>{
	if (event.key == "a"){
		PressLeft = 1;
	}
	if (event.key == "w"){
		PressForward = 1;
	}
	if (event.key == "d"){
		PressRight = 1;
	}
	if (event.key == "s"){
		PressBack = 1;
	}
	if (event.keyCode == 32){
		PressUp = 1;
	}
});

//   

document.addEventListener("keyup", (event) =>{
	if (event.key == "a"){
		PressLeft = 0;
	}
	if (event.key == "w"){
		PressForward = 0;
	}
	if (event.key == "d"){
		PressRight = 0;
	}
	if (event.key == "s"){
		PressBack = 0;
	}
	if (event.keyCode == 32){
		PressUp = 0;
	}
});

//   

document.addEventListener("mousemove", (event)=>{
	MouseX = event.movementX;
	MouseY = event.movementY;
});

//   

var pawn = new player(0,0,0,0,0);

function update(){
	
	//    
	
	dx =   ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
	dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
	dy = dy + g;
	if (onGround){
		dy = 0;
		if (PressUp){
			dy = - PressUp*pawn.vy;
			onGround = false;
		}
	};
	drx = MouseY;
	dry = - MouseX;
	
	//   :
	
	MouseX = MouseY = 0;
	
	//    
	
	collision();
	
	//    
	
	pawn.x = pawn.x + dx;
	pawn.y = pawn.y + dy;
	pawn.z = pawn.z + dz;
	
	//   ,  
	
	if (lock){
		pawn.rx = pawn.rx + drx;
		pawn.ry = pawn.ry + dry;
	};

	//    ( )
	
	world.style.transform = 
	"translateZ(" + (600 - 0) + "px)" +
	"rotateX(" + (-pawn.rx) + "deg)" +
	"rotateY(" + (-pawn.ry) + "deg)" +
	"translate3d(" + (-pawn.x) + "px," + (-pawn.y) + "px," + (-pawn.z) + "px)";
	
};

function CreateNewWorld(map){
	CreateSquares(map,"map");
}

function clearWorld(){
	world.innerHTML = "";
}

function collision(){
	
	onGround = false;
	
	for(let i = 0; i < map.length; i++){
		
		//       
		
		let x0 = (pawn.x - map[i][0]);
		let y0 = (pawn.y - map[i][1]);
		let z0 = (pawn.z - map[i][2]);
		
		if ((x0**2 + y0**2 + z0**2 + dx**2 + dy**2 + dz**2) < (map[i][6]**2 + map[i][7]**2)){
		
			let x1 = x0 + dx;
			let y1 = y0 + dy;
			let z1 = z0 + dz;
		
			let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
			let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
			let normal = coorReTransform(0,0,1,map[i][3],map[i][4],map[i][5]);
		
			//      
		
			if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
				point1[2] = Math.sign(point0[2])*50;
				let point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
				let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
				dx = point2[0] - x0;
				dy = point2[1] - y0;
				dz = point2[2] - z0;
				if (Math.abs(normal[1]) > 0.8){
					if (point3[1] > point2[1]) onGround = true;
				}
				else dy = y1 - y0;
			}
			
		}
	};
}

function coorTransform(x0,y0,z0,rxc,ryc,rzc){
	let x1 =  x0;
	let y1 =  y0*Math.cos(rxc*deg) + z0*Math.sin(rxc*deg);
	let z1 = -y0*Math.sin(rxc*deg) + z0*Math.cos(rxc*deg);
	let x2 =  x1*Math.cos(ryc*deg) - z1*Math.sin(ryc*deg);
	let y2 =  y1;
	let z2 =  x1*Math.sin(ryc*deg) + z1*Math.cos(ryc*deg);
	let x3 =  x2*Math.cos(rzc*deg) + y2*Math.sin(rzc*deg);
 	let y3 = -x2*Math.sin(rzc*deg) + y2*Math.cos(rzc*deg);
	let z3 =  z2;
	return [x3,y3,z3];
}

function coorReTransform(x3,y3,z3,rxc,ryc,rzc){
	let x2 =  x3*Math.cos(rzc*deg) - y3*Math.sin(rzc*deg);
	let y2 =  x3*Math.sin(rzc*deg) + y3*Math.cos(rzc*deg);
	let z2 =  z3
	let x1 =  x2*Math.cos(ryc*deg) + z2*Math.sin(ryc*deg);
	let y1 =  y2;
	let z1 = -x2*Math.sin(ryc*deg) + z2*Math.cos(ryc*deg);
	let x0 =  x1;
	let y0 =  y1*Math.cos(rxc*deg) - z1*Math.sin(rxc*deg);
	let z0 =  y1*Math.sin(rxc*deg) + z1*Math.cos(rxc*deg);
	return [x0,y0,z0];
};

function CreateSquares(squares,string){
	for (let i = 0; i < squares.length; i++){
		
		//      
		
		let newElement = document.createElement("div");
		newElement.className = string + " square";
		newElement.id = string + i;
		newElement.style.width = squares[i][6] + "px";
		newElement.style.height = squares[i][7] + "px";
		newElement.style.background = squares[i][8];
		newElement.style.transform = "translate3d(" +
		(600 - squares[i][6]/2 + squares[i][0]) + "px," +
		(400 - squares[i][7]/2 + squares[i][1]) + "px," +
		(squares[i][2]) + "px)" +
		"rotateX(" + squares[i][3] + "deg)" +
		"rotateY(" + squares[i][4] + "deg)" +
		"rotateZ(" + squares[i][5] + "deg)";
		
		//    world
		
		world.append(newElement);
	}
}




menu.js
//  

var menu1 = document.getElementById("menu1");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
var button1 = document.getElementById("button1");
var button2 = document.getElementById("button2");
var button3 = document.getElementById("button3");
var button4 = document.getElementById("button4");
var m = [0];
var k = [0];
var f = [0];
var level = 0;

//  

button1.onclick = function(){
	
	//   
	
	map = userSlice(mapArray[level]);
	things = userSlice(thingsArray[level]);
	keys = userSlice(keysArray[level]);
	start = userSlice(startArray[level]);
	finish = userSlice(finishArray[level]);
	
	//     
	
	menu1.style.display = "none";
	CreateNewWorld(map);
	pawn.x = start[0][0];
	pawn.y = start[0][1];
	pawn.z = start[0][2];
	pawn.rx = start[0][3];
	pawn.rx = start[0][4];
	CreateSquares(things,"thing");
	CreateSquares(keys,"key");
	CreateSquares(finish,"finish");
	
	//  
	
	TimerGame = setInterval(repeatFunction,10);
	canlock = true;
}

button2.onclick = function(){
	menu1.style.display = "none";
	menu2.style.display = "block";
}

button3.onclick = function(){
	menu1.style.display = "block";
	menu2.style.display = "none";
}

button4.onclick = function(){
	menu1.style.display = "block";
	menu3.style.display = "none";
}

//   

function interact(objects,string,num){
	for (i = 0; i < objects.length; i++){
		let r = (objects[i][0] - pawn.x)**2 + (objects[i][1] - pawn.y)**2 + (objects[i][2] - pawn.z)**2;
		if(r < (objects[i][7]**2)){
			document.getElementById(string + i).style.display = "none";
			objects[i][0] = 1000000;
			objects[i][1] = 1000000;
			objects[i][2] = 1000000;
			document.getElementById(string + i).style.transform = 
			"translate3d(1000000px,1000000px,1000000px)";
			num[0]++;
		};
	};
}

//     

function finishInteract(){
	let r = (finish[0][0] - pawn.x)**2 + (finish[0][1] - pawn.y)**2 + (finish[0][2] - pawn.z)**2;
	if(r < (finish[0][7]**2)){
		if (k[0] == 0){
			console.log(" ");
		}
		else{
			clearWorld();
			clearInterval(TimerGame);
			document.exitPointerLock();
			score = score + m[0];
			k[0] = 0;
			m[0] = 0;
			menu1.style.display = "block";
			level++;
			if(level >= 2){
				level = 0;
				score = 0;
			};
		};
	};
};

// ,   

function repeatFunction(){
	update();
	interact(things,"thing",m);
	interact(keys,"key",k);
	finishInteract();
} 

//  slice

function userSlice(array){
	let NewArray = new Array();
	for (let i = 0; i < array.length; i++){
		NewArray[i] = new Array();
		for (let j = 0; j < array[i].length; j++){
			NewArray[i][j] = array[i][j];
		}
	}
	return NewArray;
}




4. Concevons le jeu.



4.1 Changer les niveaux



La construction de niveau est une activité très amusante. En règle générale, cela est fait par des personnes appelées concepteurs de niveaux. Notre niveau est un tableau de nombres qui sont convertis par des scripts de script.js en un monde en trois dimensions. Il est possible d'écrire un programme séparé pour simplifier la création de mondes, mais maintenant nous ne le ferons pas. Ouvrons le fichier script.js et y chargerons les tableaux de labyrinthes prêts à l'emploi:



Tableaux de niveau
// 1 

mapArray[0] = [
		   //
		   [0,0,1000,0,180,0,2000,200,"#F0C0FF"],
		   [0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
		   [1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
		   [-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
		   [0,100,0,90,0,0,2000,2000,"#EEEEEE"],
		   
		   //1
		   [-700,0,-800,0,180,0,600,200,"#F0C0FF"],
		   [-700,0,-700,0,0,0,600,200,"#F0C0FF"],
		   [-400,0,-750,0,90,0,100,200,"#F0C0FF"],
		   
		   //2
		   [100,0,-800,0,180,0,600,200,"#F0C0FF"],
		   [50,0,-700,0,0,0,500,200,"#F0C0FF"],
		   [400,0,-550,0,90,0,500,200,"#F0C0FF"],
		   [-200,0,-750,0,-90,0,100,200,"#F0C0FF"],
		   [300,0,-500,0,-90,0,400,200,"#F0C0FF"],
		   [350,0,-300,0,0,0,100,200,"#F0C0FF"],
		   
		   //3
		   [700,0,-800,0,180,0,200,200,"#F0C0FF"],
		   [700,0,500,0,0,0,200,200,"#F0C0FF"],
		   [700,0,-150,0,90,0,1100,200,"#F0C0FF"],
		   [600,0,-150,0,-90,0,1300,200,"#F0C0FF"],
		   [800,0,-750,0,90,0,100,200,"#F0C0FF"],
		   [800,0,450,0,90,0,100,200,"#F0C0FF"],
		   [750,0,400,0,180,0,100,200,"#F0C0FF"],
		   [750,0,-700,0,0,0,100,200,"#F0C0FF"],
		   
		   //4
		   [850,0,-100,0,180,0,300,200,"#F0C0FF"],
		   [850,0,0,0,0,0,300,200,"#F0C0FF"],
		   
		   //5
		   [400,0,300,0,90,0,800,200,"#F0C0FF"],
		   [300,0,300,0,-90,0,800,200,"#F0C0FF"],
		   [350,0,-100,0,180,0,100,200,"#F0C0FF"],
		   
		   //6
		   [400,0,800,0,0,0,800,200,"#F0C0FF"],
		   [450,0,700,0,180,0,700,200,"#F0C0FF"],
		   [800,0,750,0,90,0,100,200,"#F0C0FF"],
		   [100,0,550,0,90,0,300,200,"#F0C0FF"],
		   [0,0,650,0,-90,0,300,200,"#F0C0FF"],
		   [-100,0,500,0,0,0,200,200,"#F0C0FF"],
		   [-100,0,400,0,180,0,400,200,"#F0C0FF"],
		   [-200,0,750,0,90,0,500,200,"#F0C0FF"],
		   [-300,0,700,0,-90,0,600,200,"#F0C0FF"],
		   
		   //7
		   [100,0,-250,0,90,0,900,200,"#F0C0FF"],
		   [0,0,-300,0,-90,0,800,200,"#F0C0FF"],
		   [-350,0,200,0,0,0,900,200,"#F0C0FF"],
		   [-350,0,100,0,180,0,700,200,"#F0C0FF"],
		   [-700,0,-50,0,90,0,300,200,"#F0C0FF"],
		   [-800,0,0,0,-90,0,400,200,"#F0C0FF"],
		   [-750,0,-200,0,180,0,100,200,"#F0C0FF"],
		   
		   //8
		   [-500,0,600,0,90,0,800,200,"#F0C0FF"],
		   [-600,0,600,0,-90,0,800,200,"#F0C0FF"],
		   
		   //9
		   [-600,0,-500,0,180,0,800,200,"#F0C0FF"],
		   [-650,0,-400,0,0,0,700,200,"#F0C0FF"],
		   [-200,0,-300,0,90,0,400,200,"#F0C0FF"],
		   [-300,0,-300,0,-90,0,200,200,"#F0C0FF"],
		   [-350,0,-100,0,0,0,300,200,"#F0C0FF"],
		   [-400,0,-200,0,180,0,200,200,"#F0C0FF"],
		   [-500,0,-150,0,-90,0,100,200,"#F0C0FF"],
		   
		   //10
		   [-900,0,500,0,0,0,200,200,"#F0C0FF"],
		   [-900,0,400,0,180,0,200,200,"#F0C0FF"],
		   [-800,0,450,0,90,0,100,200,"#F0C0FF"]
		   ];

thingsArray[0] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,900,0,0,0,50,50,"#FFFF00"],
			  [-400,50,-300,0,0,0,50,50,"#FFFF00"]];
			  
keysArray[0] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];	

startArray[0] = [[-900,0,-900,0,0]];

finishArray[0] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];

// 2 

mapArray[1] = [
		   //
		   [0,0,1200,0,180,0,2400,200,"#C0FFE0"],
		   [0,0,-1200,0,0,0,2400,200,"#C0FFE0"],
		   [1200,0,0,0,-90,0,2400,200,"#C0FFE0"],
		   [-1200,0,0,0,90,0,2400,200,"#C0FFE0"],
		   [0,100,0,90,0,0,2400,2400,"#EEEEEE"],
		   
		   //1
		   [1100,0,-800,0,180,0,200,200,"#C0FFE0"],
		   [1000,0,-900,0,90,0,200,200,"#C0FFE0"],
		   [850,0,-1000,0,180,0,300,200,"#C0FFE0"],
		   [700,0,-950,0,-90,0,100,200,"#C0FFE0"],
		   [800,0,-900,0,0,0,200,200,"#C0FFE0"],
		   [900,0,-700,0,-90,0,400,200,"#C0FFE0"],
		   [750,0,-500,0,180,0,300,200,"#C0FFE0"],
		   [600,0,-450,0,-90,0,100,200,"#C0FFE0"],
		   [800,0,-400,0,0,0,400,200,"#C0FFE0"],
		   [1000,0,-550,0,90,0,300,200,"#C0FFE0"],
		   [1100,0,-700,0,0,0,200,200,"#C0FFE0"],
		   
		   //2
		   [800,0,-200,0,180,0,800,200,"#C0FFE0"],
		   [400,0,-300,0,90,0,200,200,"#C0FFE0"],
		   [300,0,-400,0,180,0,200,200,"#C0FFE0"],
		   [200,0,-700,0,90,0,600,200,"#C0FFE0"],
		   [50,0,-1000,0,180,0,300,200,"#C0FFE0"],
		   [-100,0,-950,0,-90,0,100,200,"#C0FFE0"],
		   [0,0,-900,0,0,0,200,200,"#C0FFE0"],
		   [100,0,-600,0,-90,0,600,200,"#C0FFE0"],
		   [200,0,-300,0,0,0,200,200,"#C0FFE0"],
		   [300,0,-200,0,-90,0,200,200,"#C0FFE0"],
		   [750,0,-100,0,0,0,900,200,"#C0FFE0"],
		   
		   //3
		   [500,0,-950,0,90,0,500,200,"#C0FFE0"],
		   [450,0,-700,0,0,0,100,200,"#C0FFE0"],
		   [400,0,-950,0,-90,0,500,200,"#C0FFE0"],
		   
		   //4
		   [-700,0,-600,0,0,0,1000,200,"#C0FFE0"],
		   [-200,0,-500,0,-90,0,200,200,"#C0FFE0"],
		   [-300,0,-400,0,180,0,200,200,"#C0FFE0"],
		   [-400,0,-250,0,-90,0,300,200,"#C0FFE0"],
		   [-350,0,-100,0,0,0,100,200,"#C0FFE0"],
		   [-300,0,-200,0,90,0,200,200,"#C0FFE0"],
		   [-200,0,-300,0,0,0,200,200,"#C0FFE0"],
		   [-100,0,-500,0,90,0,400,200,"#C0FFE0"],
		   [-650,0,-700,0,180,0,1100,200,"#C0FFE0"],
		   
		   //5
		   [-300,0,-850,0,90,0,300,200,"#C0FFE0"],
		   [-350,0,-1000,0,180,0,100,200,"#C0FFE0"],
		   [-400,0,-850,0,-90,0,300,200,"#C0FFE0"],
		   
		   //6
		   [-600,0,-1050,0,90,0,300,200,"#C0FFE0"],
		   [-650,0,-900,0,0,0,100,200,"#C0FFE0"],
		   [-700,0,-1050,0,-90,0,300,200,"#C0FFE0"],
		   
		   //7
		   [-900,0,-850,0,90,0,300,200,"#C0FFE0"],
		   [-950,0,-1000,0,180,0,100,200,"#C0FFE0"],
		   [-1000,0,-850,0,-90,0,300,200,"#C0FFE0"],
		   
		   //8
		   [-600,0,-250,0,90,0,700,200,"#C0FFE0"],
		   [-650,0,100,0,0,0,100,200,"#C0FFE0"],
		   [-700,0,-250,0,-90,0,700,200,"#C0FFE0"],
		   
		   //9
		   [-900,0,-150,0,90,0,900,200,"#C0FFE0"],
		   [-500,0,300,0,180,0,800,200,"#C0FFE0"],
		   [-100,0,650,0,90,0,700,200,"#C0FFE0"],
		   [-300,0,1000,0,0,0,400,200,"#C0FFE0"],
		   [-500,0,950,0,-90,0,100,200,"#C0FFE0"],
		   [-350,0,900,0,180,0,300,200,"#C0FFE0"],
		   [-200,0,650,0,-90,0,500,200,"#C0FFE0"],
		   [-600,0,400,0,0,0,800,200,"#C0FFE0"],
		   [-1000,0,-100,0,-90,0,1000,200,"#C0FFE0"],
		   
		   //10
		   [-300,0,200,0,90,0,200,200,"#C0FFE0"],
		   [-350,0,100,0,180,0,100,200,"#C0FFE0"],
		   [-400,0,200,0,-90,0,200,200,"#C0FFE0"],
		   
		   //11
		   [-800,0,600,0,180,0,800,200,"#C0FFE0"],
		   [-400,0,650,0,90,0,100,200,"#C0FFE0"],
		   [-800,0,700,0,0,0,800,200,"#C0FFE0"],
		   
		   //12
		   [-700,0,1050,0,90,0,300,200,"#C0FFE0"],
		   [-850,0,900,0,180,0,300,200,"#C0FFE0"],
		   [-1000,0,950,0,-90,0,100,200,"#C0FFE0"],
		   [-900,0,1000,0,0,0,200,200,"#C0FFE0"],
		   [-800,0,1100,0,-90,0,200,200,"#C0FFE0"],
		   
		   //13
		   [1050,0,700,0,180,0,300,200,"#C0FFE0"],
		   [900,0,800,0,-90,0,200,200,"#C0FFE0"],
		   [550,0,900,0,180,0,700,200,"#C0FFE0"],
		   [200,0,650,0,90,0,500,200,"#C0FFE0"],
		   [300,0,400,0,0,0,200,200,"#C0FFE0"],
		   [400,0,300,0,90,0,200,200,"#C0FFE0"],
		   [550,0,200,0,0,0,300,200,"#C0FFE0"],
		   [700,0,150,0,90,0,100,200,"#C0FFE0"],
		   [500,0,100,0,180,0,400,200,"#C0FFE0"],
		   [300,0,200,0,-90,0,200,200,"#C0FFE0"],
		   [200,0,300,0,180,0,200,200,"#C0FFE0"],
		   [100,0,650,0,-90,0,700,200,"#C0FFE0"],
		   [550,0,1000,0,0,0,900,200,"#C0FFE0"],
		   [1000,0,900,0,90,0,200,200,"#C0FFE0"],
		   [1100,0,800,0,0,0,200,200,"#C0FFE0"],
		   
		   //14
		   [700,0,700,0,90,0,400,200,"#C0FFE0"],
		   [850,0,500,0,0,0,300,200,"#C0FFE0"],
		   [1000,0,300,0,90,0,400,200,"#C0FFE0"],
		   [950,0,100,0,180,0,100,200,"#C0FFE0"],
		   [900,0,250,0,-90,0,300,200,"#C0FFE0"],
		   [750,0,400,0,180,0,300,200,"#C0FFE0"],
		   [600,0,650,0,-90,0,500,200,"#C0FFE0"],
		   
		   //15
		   [500,0,600,0,180,0,200,200,"#C0FFE0"],
		   [400,0,650,0,-90,0,100,200,"#C0FFE0"],
		   [500,0,700,0,0,0,200,200,"#C0FFE0"]
		   ];

thingsArray[1] = [[1100,50,900,0,0,0,50,50,"#FFFF00"],
			  [500,50,800,0,0,0,50,50,"#FFFF00"],
			  [-800,50,-500,0,0,0,50,50,"#FFFF00"],
			  [-900,50,1100,0,0,0,50,50,"#FFFF00"],
			  [-1100,50,-800,0,0,0,50,50,"#FFFF00"]
			  ];
			  
keysArray[1] = [[1100,50,-900,0,0,0,50,50,"#FF0000"]];	

startArray[1] = [[0,0,0,0,0]];

finishArray[1] = [[-1100,50,-500,0,0,0,50,50,"#00FFFF"]];




Nous pouvons maintenant jouer au jeu. En conséquence, les niveaux ressemblent à ceci:







Il est extrêmement difficile de naviguer dans un tel monde. De plus, le mouvement le long des murs contient des bugs, car le joueur peut rester coincé aux coins des murs. Corrigeons cela dans collision (), en remplaçant 98 par 90:



//      
		
			if (Math.abs(point1[0])<(map[i][6]+90)/2 && Math.abs(point1[1])<(map[i][7]+90)/2 && Math.abs(point1[2]) < 50){


4.2 Ajouter un éclairage statique



Pour faciliter la navigation, nous implémentons un éclairage solaire statique (pas d'ombres). Ajoutons un vecteur de lumière du soleil:



var sun = [0.48,0.8,0.36];


Comment créer un éclairage? Regardez l'image:







si le vecteur soleil est exactement opposé au vecteur n, alors l'éclairage est maximisé. L'intensité lumineuse dépend de l'angle d'incidence de la lumière sur la surface. Si le rayon lumineux tombe parallèlement au plan ou tombe du côté opposé, le plan n'est pas éclairé. L'angle d'incidence peut être calculé à l'aide du produit scalaire n * soleil: s'il est négatif, alors l'éclairage dépend du module du produit scalaire, et s'il est positif, il n'y a pas d'éclairage. Nous allons créer l'illumination des surfaces lors de la génération du monde, c'est-à-dire dans CreateNewWorld (). Et comme il n'y a que la fonction CreateSquare (), nous y appliquerons l'illumination. Mais nous n'appliquerons probablement l'annonciation qu'au monde, mais pas aux choses, donc nous ajouterons l'argument d'éclairage ici, et nous changerons CreateSquare () lui-même:



function CreateSquares(squares,string,havelight){
	for (let i = 0; i < squares.length; i++){
		
		//      
		
		let newElement = document.createElement("div");
		newElement.className = string + " square";
		newElement.id = string + i;
		newElement.style.width = squares[i][6] + "px";
		newElement.style.height = squares[i][7] + "px";
		if (havelight){
			let normal = coorReTransform(0,0,1,squares[i][3],squares[i][4],squares[i][5]);
			let light = -(normal[0]*sun[0] + normal[1]*sun[1] + normal[2]*sun[2]);
			if (light < 0){
				light = 0;
			};
			newElement.style.background = "linear-gradient(rgba(0,0,0," + (0.2 - light*0.2) + "),rgba(0,0,0," + (0.2 - light*0.2) + ")), " +  squares[i][8];
		}
		else{
			newElement.style.background = squares[i][8];
		}
		newElement.style.transform = "translate3d(" +
		(600 - squares[i][6]/2 + squares[i][0]) + "px," +
		(400 - squares[i][7]/2 + squares[i][1]) + "px," +
		(squares[i][2]) + "px)" +
		"rotateX(" + squares[i][3] + "deg)" +
		"rotateY(" + squares[i][4] + "deg)" +
		"rotateZ(" + squares[i][5] + "deg)";
		
		//    world
		
		world.append(newElement);
	}
}


Allumons l'éclairage lors de la génération du monde dans CreateNewWorld ():



function CreateNewWorld(map){
	CreateSquares(map,"map",true);
}


Et ajoutez la désactivation de l'éclairage pour les éléments dans button1.onclick (dans CreateSquares, le dernier paramètre pour eux est false):



//     
	
	menu1.style.display = "none";
	CreateNewWorld(map);
	pawn.x = start[0][0];
	pawn.y = start[0][1];
	pawn.z = start[0][2];
	pawn.rx = start[0][3];
	pawn.rx = start[0][4];
	CreateSquares(things,"thing",false);
	CreateSquares(keys,"key",false);
	CreateSquares(finish,"finish",false);


Commençons le jeu et notons que l'éclairage est devenu plus réaliste, et qu'il est beaucoup plus facile de naviguer dans l'espace:







ajoutez un ciel bleu. Définissons l'arrière-plan de #container dans style.css:



background-color:#C0FFFF;


Le ciel est devenu bleu:







nous avons conçu les niveaux. Mais la recherche d'objets reste difficile, car ils sont statiques, et il est intuitivement difficile pour le joueur de comprendre qu'ils peuvent être collectés.



4.3 Ajouter une rotation et de la lumière aux objets



Dans menu.js, créons une fonction de rotation distincte:



function rotate(objects,string,wy){
	for (i = 0; i < objects.length; i++){
		objects[i][4] = objects[i][4] + wy;
		document.getElementById(string + i).style.transform = "translate3d(" +
		(600 - objects[i][6]/2 + objects[i][0]) + "px," +
		(400 - objects[i][7]/2 + objects[i][1]) + "px," +
		(objects[i][2]) + "px)" +
		"rotateX(" + objects[i][3] + "deg)" +
		"rotateY(" + objects[i][4] + "deg)" +
		"rotateZ(" + objects[i][5] + "deg)";
	};
}


Et nous l'appellerons à partir de repeatFunction ():



function repeatFunction(){
	update();
	interact(things,"thing",m);
	interact(keys,"key",k);
	rotate(things,"thing",0.5);
	rotate(keys,"key",0.5);
	rotate(finish,"finish",0.5);
        finishInteract();
}


Certes, la fonction de rotation peut être utilisée non seulement pour faire pivoter des objets, mais aussi pour les déplacer. Les objets tournent donc. Mais si nous rendons ces objets lumineux, alors ce sera généralement super. Définissons des ombres colorées pour eux dans style.css:



.thing{
	box-shadow: 0 0 10px #FFFF00;
}
.key{
	box-shadow: 0 0 10px #FF0000;
}
.finish{
	box-shadow: 0 0 10px #00FFFF;
}


Maintenant, le joueur comprend avec certitude que ces objets peuvent être interagis.



4.4 Ajouter des widgets



En règle générale, les widgets affichent le score, la santé et d'autres données numériques nécessaires. Ici, ils afficheront le nombre de pièces collectées (carrés jaunes) et de clés (carrés rouges), et vous pouvez les changer à partir de javascript. Tout d'abord, ajoutons de nouveaux éléments au html:



<div id="container">
		<div id="world"></div>
		<div id="pawn"></div>
		<div class = "widget" id = "widget1"></div>
		<div class = "widget" id = "widget2"></div>
                <div class = "widget" id = "widget3"></div>


Dans menu.js, lions-leur des variables:



var widget1 = document.getElementById("widget1");
var widget2 = document.getElementById("widget2");
var widget3 = document.getElementById("widget3");


Et à l'intérieur de button1.onclick (), ajoutez-leur du texte:



widget1.innerHTML = "<p style='font-size:30px'>: 0  0" </p>";
widget2.innerHTML = "<p style='font-size:30px'>:0</p>";
widget3.innerHTML = "<p style='font-size:40px'>  !</p>";


Stylisons-les dans style.css ():



/*   */

.widget{
	display:none;
	position:absolute;
	background-color:#FFF;
	opacity:0.8;
	z-index:300;
}
#widget1{
	top:0px;
	left:0px;
	width:300px;
	height:100px;
}
#widget2{
	top:0px;
	right:0px;
	width:300px;
	height:100px;
}
#widget3{
	bottom:0px;
	left:0px;
	width:500px;
	height:200px;
}


Ils sont initialement invisibles. Rendons les 2 premiers widgets visibles lors du démarrage du niveau à l'intérieur de button1.onclick:



       //       
	
	widget1.style.display = "block";
	widget2.style.display = "block";
	widget1.innerHTML = "<p style='font-size:30px'>: 0  " + things.length + " </p>";
	widget2.innerHTML = "<p style='font-size:30px'>:0</p>";
	widget3.innerHTML = "<p style='font-size:40px'>  !</p>";


Il existe des widgets, mais rien ne se passe lors de l'interaction avec des objets. Nous changerons les étiquettes des widgets lors de l'interaction à partir des fonctions d'interaction (à l'intérieur de if (r <(objects [i] [7] ** 2)) {...}):



			widget1.innerHTML = "<p style='font-size:30px'>: " + m[0] + "  " + things.length + " </p>";
			widget2.innerHTML = "<p style='font-size:30px'>: " + k[0] + "</p>";


Désormais, lors de la prise de pièces et d'une clé, les informations des widgets changent. Mais lorsque le jeu se termine, les widgets ne sont pas cachés. Masquons-les à la fin du jeu en ajoutant les lignes suivantes à finishInteract () à l'intérieur else:



widget1.style.display = "none";

widget2.style.display = "aucun";

widget3.style.display = "aucun";



Les widgets sont masqués. Reste à mettre en place un widget qui vous demande de prendre une clé si vous arrivez à la ligne d'arrivée sans elle. Dans finishInteract (), au lieu de console.log ("find the key"), insérez les lignes suivantes:



widget3.style.display = "block";
setTimeout(() => widget3.style.display = "none",5000);


Si la tentative de terminer le jeu échoue, nous recevons un message qui disparaît après 5 secondes. Notre jeu ressemble maintenant à ceci:











4.5 Formalisons le texte.



Créons un dossier Fonts dans le dossier files. Téléchargez le fichier font1.woff à partir d'ici et collez-le dans Fonts. Ajoutez des styles de texte à style.css:



/*   */

p{
	margin:0px;
	font-size:60px;
	position:absolute;
	display:block;
	top:50%;
	left:50%;
	transform:translate(-50%,-50%);
	user-select:none;
	font-family:fontlab;
}

@font-face{
	font-family:fontlab;
	src:url("Fonts/font1.woff");
}


Le menu et le jeu ont changé:











4.6 Ajouter des sons.



Téléchargez l'archive Sounds.zip à partir d'ici . Créez un dossier Sounds dans le dossier du projet et insérez-y des sons (ils sont au format mp3). Faisons des références variables à ces sons:



//  

var clickSound = new Audio;
clickSound.src = "Sounds/click.mp3";

var keySound = new Audio;
keySound.src = "Sounds/key.mp3";

var mistakeSound = new Audio;
mistakeSound.src = "Sounds/mistake.mp3";

var thingSound = new Audio;
thingSound.src = "Sounds/thing.mp3";

var winSound = new Audio;
winSound.src = "Sounds/win.mp3";


Dans la fonction interact, ajoutez un argument au fichier son et jouez le son (soundObject.play ()):



function interact(objects,string,num,soundObject){
	for (i = 0; i < objects.length; i++){
		let r = (objects[i][0] - pawn.x)**2 + (objects[i][1] - pawn.y)**2 + (objects[i][2] - pawn.z)**2;
		if(r < (objects[i][7]**2)){
			soundObject.play();
			document.getElementById(string + i).style.display = "none";
			objects[i][0] = 1000000;
			objects[i][1] = 1000000;
			objects[i][2] = 1000000;
			document.getElementById(string + i).style.transform = 
			"translate3d(1000000px,1000000px,1000000px)";
			num[0]++;
			widget1.innerHTML = "<p style='font-size:30px'>: " + m[0] + "  " + things.length + " </p>";
			widget2.innerHTML = "<p style='font-size:30px'>: " + k[0] + "</p>";
		};
	};
}


Dans repeatFunction (), modifiez les appels à cette fonction en conséquence:



interact(things,"thing",m,thingSound);
interact(keys,"key",k,keySound);


Et dans finishInteract (), ajoutez les sons errorSound et winSound:



function finishInteract(){
	let r = (finish[0][0] - pawn.x)**2 + (finish[0][1] - pawn.y)**2 + (finish[0][2] - pawn.z)**2;
	if(r < (finish[0][7]**2)){
		if (k[0] == 0){
			widget3.style.display = "block";
			setTimeout(() => widget3.style.display = "none",5000);
			mistakeSound.play();
		}
		else{
			clearWorld();
			clearInterval(TimerGame);
			document.exitPointerLock();
			score = score + m[0];
			k[0] = 0;
			m[0] = 0;
			level++;
			menu1.style.display = "block";
			widget1.style.display = "none";
			widget2.style.display = "none";
			widget3.style.display = "none";
			winSound.play();
			if(level >= 2){
				level = 0;
				score = 0;
			};
		};
	};
};


Lorsque vous cliquez sur un bouton de menu, nous lirons le son clickSound:



button1.onclick = function(){
	
	clickSound.play();
	
	...

}

button2.onclick = function(){
	
	clickSound.play();
	
	menu1.style.display = "none";
	menu2.style.display = "block";
}

button3.onclick = function(){
	
	clickSound.play();
	
	menu1.style.display = "block";
	menu2.style.display = "none";
}

button4.onclick = function(){
	
	clickSound.play();
	
	menu1.style.display = "block";
	menu3.style.display = "none";
}


Le jeu a joué plus brillant. Il reste à configurer la sortie des résultats après avoir passé tous les niveaux:



4.7 Sortie des résultats.



Dans menu.js dans finishInteract () à l'intérieur de if (level> = 2) {…} ajoutez les lignes suivantes:



if(level >= 2){
menu1.style.display = "none";
	menu3.style.display = "block";
	document.getElementById("result").innerHTML = "  " + score + " ";
	level = 0;
	score = 0;
};


Nous voyons le nombre de points marqués après avoir terminé tous les niveaux.

Au fait, n'oublions pas d'ajouter la ligne à la même fonction:



canlock = false;


Et:



button1.innerHTML = "<p></p>";


et



button1.innerHTML = "<p> </p>";


Par conséquent:



function finishInteract(){
	let r = (finish[0][0] - pawn.x)**2 + (finish[0][1] - pawn.y)**2 + (finish[0][2] - pawn.z)**2;
	if(r < (finish[0][7]**2)){
		if (k[0] == 0){
		}
		else{
			canlock = false;
			button1.innerHTML = "<p></p>";
			if(level >= 2){
				menu1.style.display = "none";
				menu3.style.display = "block";
				document.getElementById("result").innerHTML = "  " + score + " ";
				level = 0;
				score = 0;
				button1.innerHTML = "<p> </p>";
			};
		};
	};
};


Maintenant, le bouton de démarrage du jeu change en fonction du passage des niveaux. Déplacez également le «conteneur» au centre de la fenêtre en ajoutant les lignes suivantes à ses styles:



top:50%;
left:50%;
transform: translate(-50%,-50%);


Et supprimez les retraits dans le corps:



body{
	margin:0px;
}


Nous avons donc complètement écrit un jeu de labyrinthe 3D basé sur un navigateur. Grâce à elle, nous avons attiré l'attention sur certains aspects du langage javascript, appris des fonctions dont vous n'aviez peut-être pas entendu parler auparavant. Et surtout, nous avons montré qu'il n'est pas si difficile de créer des jouets simples pour le navigateur, même en code pur. Vous pouvez télécharger le code source complet ici (sources.zip) . Les scripts eux-mêmes peuvent être considérablement améliorés en y ajoutant différentes bibliothèques, en écrivant de nouveaux constructeurs ou en faisant autre chose.



Merci de votre attention!



All Articles