Pembuatan game 3d berbasis browser dari awal dalam html, css, dan js murni. Bagian 2/2

Pada artikel ini, kami akan terus membuat game labirin browser 3D dalam html, css, dan javascript murni. Di bagian sebelumnya, kami membuat dunia 3 dimensi sederhana, menerapkan gerakan, kontrol, tabrakan pemain dengan objek statis. Pada bagian ini kita akan menambahkan gravitasi, cahaya matahari statis (tanpa bayangan), memuat suara, dan membuat menu. Sayangnya, seperti di bagian pertama, tidak akan ada demo di sini.



Mari kita mengingat kembali kode yang kita lakukan di bagian sebelumnya. Kami memiliki 3 file:



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. Implementasi fisika lompat dan gravitasi



Kami memiliki beberapa variabel yang dibuat di berbagai bagian file javascript. Akan lebih baik jika kita memindahkannya ke satu tempat:



//  

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


Mari tambahkan percepatan jatuh bebas ke dalamnya:



var g = 0.1;




Tambahkan 3 variabel ke konstruktor pemutar - vx, vy, dan 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;
}


Ini adalah kecepatan variabel. Dengan mengubahnya, kita dapat mengubah kecepatan lari dan kecepatan lompatan awal pemain. Untuk saat ini, mari terapkan variabel baru di 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;


Pemain sekarang bergerak lebih cepat. Tapi dia tidak jatuh atau melompat. Hal ini diperlukan untuk memungkinkan lompatan saat berada pada sesuatu. Dan itu akan berdiri ketika bertabrakan dengan permukaan horizontal (atau hampir). Bagaimana mendefinisikan horizontalitas? Kita perlu menemukan bidang normal dari persegi panjang. Ini dilakukan dengan sederhana. Berkenaan dengan koordinat persegi panjang, normal diarahkan sepanjang sumbu z. Kemudian normal telah mengubah koordinat menjadi koordinat dunia. Temukan normal (tambahkan variabel lokal 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]);


Agar permukaan horizontal, perkalian titik dari sumbu normal terhadap sumbu y dalam koordinat dunia harus 1 atau -1, dan bidang hampir horizontal harus mendekati 1 atau -1. Mari kita atur kondisi untuk bidang yang hampir horizontal:



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


Jangan lupa bahwa jika tidak ada tabrakan, pemain pasti tidak akan berada di tanah, jadi secara default, di awal fungsi collision (), setel onGround = false:



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


Namun, jika pemain bertabrakan dengan permukaan dari bawah, maka dia juga akan muncul di tanah. Untuk mencegah hal ini, mari kita periksa pemain di atas pesawat (point3 [1] harus kurang dari 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;



Apa yang kita lakukan? lihat gambarnya: Palang







merah harus berada di bawah oranye di sistem koordinat dunia (atau koordinat y harus lebih besar). Inilah yang kita periksa pada point3 [1]> point2 [1]. Dan point3 hanyalah koordinat dari titik merah. Mari pindahkan inisialisasi point2 ke dalam kondisi tumbukan:



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;
				}
			}


Mari lanjutkan ke update (). Kami juga akan membuat perubahan di sini. Pertama, mari tambahkan gravitasi dan hilangkan y-offset saat menekan bilah spasi:



//    

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; 
 


Kedua, jika pemain berada di tanah, kami melarang gravitasi, melarang perpindahan dalam y (jika tidak, setelah berjalan di permukaan miring, pemain akan lepas landas) dan menambahkan kemampuan untuk melompat (jika kondisi (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;


Biasanya, segera setelah lompatan, kami melarang lompatan berulang dengan menyetel parameter onGround ke false. Validitas parameter ini tidak lagi diperlukan pada kondisi bilah spasi:



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


Untuk menguji perubahan, mari ubah dunia:



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"]
];


Jika kita memulai permainan, kita akan melihat bahwa pemain dapat memanjat tembok hijau hampir vertikal. Nonaktifkan ini dengan menambahkan lain 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;
			}


Jadi, tumbukan dengan dinding yang sangat vertikal tidak mengubah y offset. Oleh karena itu, overclocking di dinding seperti itu sekarang sepenuhnya dikecualikan. Mari kita coba memanjat tembok hijau. Kami tidak dapat melakukan ini sekarang. Jadi, kami menemukan gravitasi dan lompatan, dan sekarang kami dapat memanjat secara realistis pada permukaan yang sedikit miring. Mari kita periksa kodenya:



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. Buat menu



Mari buat menu dalam bentuk html-panel dan html-blok. Desain seluruh menu akan kurang lebih sama: latar belakang dan gaya tombol dapat disetel sama untuk semua orang. Jadi, mari kita setel tiga panel menu: menu utama, instruksi, dan keluaran hasil setelah permainan selesai. Transisi antara menu, transisi ke dunia dan kembali akan dilakukan oleh skrip javascript. Agar tidak mengacaukan file script.js, buat file menu.js baru untuk navigasi menu, dan sertakan di index.html:



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


Di dalam wadah, kami akan membuat 3 item yang akan menjadi bilah 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>


Mari kita beri gaya dengan menambahkan properti untuk kelas "menu" ke style.css:



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


Tambahkan tombol dengan teks yang sesuai ke menu (di file 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>


Untuk tombol, kami juga akan mengatur gaya di 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;
}


Tapi kami tidak melihat menu, karena mereka memiliki gaya display: none. Saat permainan dimulai, salah satu item menu harus terlihat, jadi di html untuk menu pertama, tambahkan entri style = "display: block;", dan terlihat seperti ini akan menjadi seperti ini:



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


Menu sekarang terlihat seperti ini:







Hebat. Tetapi jika kita mengklik tombol tersebut, kursor akan tertangkap. Jadi kita perlu mengizinkan penangkapan mouse hanya dalam kasus game. Untuk melakukan ini, masukkan variabel canlock di script.js dan tambahkan ke item buat variabel:



//  

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();
};


Sekarang kita dapat mengklik menu. Mari konfigurasikan transisi menggunakan skrip di file 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";
}


Sekarang semua tombol menu kecuali "mulai permainan" bekerja. Sekarang mari kita konfigurasikan tombol button1. Jika Anda ingat, dalam file script.js, fungsi CreateNewWorld () dan setInterval () dipicu saat halaman web dimuat. Mari kita pindahkan dari sana. Kami hanya akan memanggil mereka ketika tombol1 ditekan. Ayo lakukan:



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


Kami telah membuat menu. Ya, ini masih jelek, tapi mudah sembuh.



3. Mari membuat objek dan transisi level.



Pertama, mari kita tentukan aturan mainnya. Kami memiliki tiga jenis item: koin (kotak kuning), kunci (kotak merah) dan selesai (kotak biru). Koin membawa poin. Pemain perlu menemukan kuncinya, dan baru kemudian tiba di garis finis. Jika dia sampai di garis finis tanpa kunci, dia akan menerima pesan tentang perlunya menemukan kunci terlebih dahulu. Kami akan membuat objek dengan cara yang sama seperti peta. Kami akan menuliskannya menggunakan array. Tapi kami tidak akan membuat fungsi tersendiri untuk mereka. Kami hanya akan menulis fungsi baru yang mengatur elemen peta dan persegi panjang dan membawa perintah dari CreateNewWorld (). Sebut saja CreateSquares (). Jadi, mari tambahkan entri berikut ke akhir file 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);
	}
}


Dan kami akan mengubah konten createNewWorld ():



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


String diperlukan untuk menyetel id nama. Permainan belum berubah sedikit pun. Sekarang mari tambahkan 3 array: koin (benda), kunci (kunci) dan selesai (selesai). Mari masukkan mereka tepat setelah larik peta:



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"]];


Dan di menu.js, mari gunakan fungsi CreateSquares () di dalam handler untuk menekan tombol "button1":



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


Sekarang mari kita siapkan hilangnya objek. Di menu.js, mari buat fungsi untuk memeriksa jarak dari pemutar ke objek:



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)";
		};
	};
}


Juga, di file yang sama, buat fungsi repeatFunction () dan tambahkan perintah ke dalamnya:



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


Dan kita akan menjalankan panggilan sikliknya di setInterval inside button1:



TimerGame = setInterval(repeatFunction,10);


Sekarang objek menghilang saat kita mendekatinya. Namun, mereka sama sekali tidak melakukan apa-apa. Dan kami ingin poin ditambahkan kepada kami saat kami mengambil kotak kuning, dan saat kami mengambil kotak merah, kami memiliki kesempatan untuk mengambil warna biru dan menyelesaikan permainan. Mari ubah fungsi interaksinya ():



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]++;
		};
	};
}


Mari kita ubah parameter input untuk panggilan ke fungsi ini:



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


Dan di awal file, tambahkan empat variabel baru:



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


Mengapa kami membuat array dari satu elemen, Anda bertanya, dan bukan hanya variabel? Intinya adalah kita ingin meneruskan variabel ini untuk berinteraksi () dengan referensi, bukan dengan nilai. Dalam javascript, variabel biasa dilewatkan hanya dengan nilai, dan array dilewatkan oleh referensi. Jika kita mengirimkan hanya variabel untuk berinteraksi (), maka num akan menjadi salinan variabel. Mengubah angka tidak akan mengubah k atau m. Dan jika kita melewatkan sebuah larik, maka num akan menjadi acuan bagi larik tersebut k atau m, dan jika kita mengubah num [0], maka k [0] dan m [0] akan berubah. Tentu saja dimungkinkan untuk membuat 2 fungsi yang hampir identik, tetapi lebih baik bertahan dengan satu fungsi, sedikit lebih universal.



Untuk menyelesaikannya, Anda masih harus membuat fungsi terpisah:



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";
		};
	};
};


Dan siapkan clearWorld () di script.js:



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


Seperti yang Anda lihat, membersihkan dunia itu cukup sederhana. Tambahkan finishInteract () ke repeatFunction ():



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


Apa yang terjadi di finishInteract ()? Jika kita belum mengambil kunci (k [0] == 0), maka belum ada yang terjadi. Jika ya, maka permainan berakhir, dan hal berikut terjadi: dunia dibersihkan, fungsi repeatFunction () berhenti, kursor berhenti ditangkap, penghitung tombol disetel ulang, dan kami pergi ke menu utama. Mari kita periksa dengan menjalankan gamenya. Semuanya bekerja. Namun, setelah mengklik lagi pada game, kami segera menemukan diri kami di garis finish, dan beberapa item menghilang. Ini karena kami tidak memasuki lokasi pemunculan awal pemain, dan susunannya berubah selama permainan. Mari tambahkan titik pemijahan untuk pemain ke tombol1, yaitu, samakan koordinatnya dengan elemen array awal [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;
}


Pemain sekarang muncul di asalnya. Namun pertanyaannya adalah: bagaimana jika ada beberapa level dalam game tersebut? Tambahkan variabel level ke 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;


Mari kita membuat ulang peta variabel, hal, kunci, mulai, selesaikan di dalam script.js menjadi array, sedikit mengubah namanya:



// 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"]];


Mari tambahkan level ke-2:



// 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"]];


Dan array itu sendiri diinisialisasi sebelum level:



//   

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


fungsi CreateNewWorld () harus diubah dengan menambahkan argumen di sana:



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


Mari kita ubah panggilan ke CreateNewWorld () di file 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;
}


Sekarang konsol akan memberikan kesalahan saat startup. Itu benar, karena kami mengganti nama variabel map, things, keys dan finish, sekarang javascript tidak dapat mengetahui apa variabel ini. Kami menginisialisasi mereka lagi di script.js:



//   

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


Dan di tombol1 (di menu.js) kami menetapkan salinan elemen dari array mapArray, thingsArray, keysArray dan finishArray ke variabel ini (untuk lebih mudah dibaca, beri komentar):



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;
}


Di mana userSlice () adalah fungsi yang menyalin array:



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;
}


Jika kita hanya menulis, misalnya, keys = keysArray [level], maka bukan salinan array yang akan ditransfer ke variabel, tetapi penunjuk ke variabel, yang berarti bahwa mereka akan berubah selama permainan, yang tidak dapat diterima, karena ketika Anda me-restart kunci pada tempat aslinya tidak ada lagi. Anda mungkin bertanya mengapa saya tidak hanya menggunakan keysArray [level] .slice (), tetapi menemukan fungsi saya sendiri? Lagipula, slice () juga menyalin array. Saya mencoba melakukan ini, tetapi itu menyalin referensi ke array, bukan ke array itu sendiri, akibatnya mengubah kunci menyebabkan perubahan dalam keysArray [level], yang berarti bahwa kunci tersebut menghilang saat restart. Faktanya adalah bahwa dokumentasi mengatakan bahwa dalam beberapa kasus dia menganggap array sebagai array dan menyalinnya, dalam kasus lain dia menganggap array sebagai objek dan salinan hanya menunjuk ke array tersebut. Bagaimana dia mendefinisikannya adalah misteri bagiku,jadi jika ada yang bisa memberi tahu saya mengapa slice () tidak berfungsi sesuai rencana, maka saya akan sangat berterima kasih padanya.



Mari kita lakukan transisi level. Ini sangat mudah. Mari modifikasi finishInteract () dengan menambahkan baris berikut di dalam else:



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


Artinya, nilai level ditambahkan 1, dan jika semua level dilewati (kami memiliki 2 level), maka level akan disetel ulang dan poin skor disetel ulang. Ini sulit untuk diverifikasi, karena level kami sekarang tidak berbeda. Mari kita ubah, lalu 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"]
];


Kami mengubah warna dinding. Ayo main game. Kami melihat bahwa setelah melewati level pertama (dengan dinding ungu dan beberapa persegi panjang), kami melanjutkan ke level kedua (dengan dinding hijau), dan ketika kami melewati level kedua, kami kembali ke level pertama. Jadi, kami telah menyelesaikan transisi level. Tinggal mendesain game, mengubah font, mewarnai dunia, dan membuat level sedikit lebih sulit. Kami tidak mengubah file index.html dan style.css, jadi periksa skripnya:



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. Mari mendesain game.



4.1 Ubah level



Membangun level adalah aktivitas yang sangat menyenangkan. Biasanya, ini dilakukan oleh individu yang disebut desainer level. Level kami diwakili oleh array angka yang diubah oleh skrip dari script.js menjadi dunia tiga dimensi. Dimungkinkan untuk menulis program terpisah untuk menyederhanakan penciptaan dunia, tetapi sekarang kami tidak akan melakukannya. Mari buka file script.js dan muat array labirin yang sudah jadi di sana:



Array level
// 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"]];




Sekarang kita bisa memainkan gamenya. Akibatnya, levelnya terlihat seperti ini:







Sangat sulit untuk dinavigasi di dunia seperti itu. Ditambah lagi, pergerakan di sepanjang dinding mengandung bug, karena pemain bisa terjebak di sudut-sudut dinding. Mari kita perbaiki di collision (), ganti 98 dengan 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 Tambahkan pencahayaan statis



Untuk mempermudah navigasi, kami menerapkan pencahayaan matahari statis (tanpa bayangan). Mari tambahkan vektor sinar matahari:



var sun = [0.48,0.8,0.36];


Bagaimana cara membuat iluminasi? Perhatikan gambar:







Jika vektor matahari berlawanan dengan vektor n, maka pencahayaan dimaksimalkan. Intensitas cahaya tergantung pada sudut datangnya cahaya di permukaan. Jika seberkas cahaya jatuh sejajar dengan bidang atau jatuh dari sisi yang berlawanan, maka bidang tersebut tidak diterangi. Sudut datang dapat dihitung menggunakan produk skalar n * sun: jika negatif, maka iluminasi bergantung pada modulus perkalian titik, dan jika positif, maka tidak ada iluminasi. Kami akan membuat iluminasi permukaan saat membuat dunia, yaitu di CreateNewWorld (). Dan karena hanya ada fungsi CreateSquare (), kami akan menerapkan iluminasi di sana. Tapi kita mungkin hanya akan menerapkan pemberitaan ke dunia, tetapi tidak pada hal-hal, jadi kita akan menambahkan argumen pencahayaan di sana, dan kita akan mengubah CreateSquare () itu sendiri:



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);
	}
}


Nyalakan pencahayaan saat membuat dunia di CreateNewWorld ():



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


Dan tambahkan mematikan iluminasi untuk item di button1.onclick (di CreateSquares, parameter terakhir untuk mereka adalah salah):



//     
	
	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);


Mari kita mulai permainan dan perhatikan bahwa pencahayaan menjadi lebih realistis, dan jauh lebih mudah dinavigasi di luar angkasa:







Tambahkan langit biru. Mari atur latar belakang untuk #container di style.css:



background-color:#C0FFFF;


Langit membiru:







Kami merancang levelnya. Tetapi mencari item masih sulit, karena bersifat statis, dan secara intuitif sulit bagi pemain untuk memahami bahwa mereka dapat dikumpulkan.



4.3 Tambahkan rotasi dan cahaya ke objek



Mari buat fungsi rotasi terpisah di menu.js:



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)";
	};
}


Dan kami akan memanggilnya dari 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();
}


Benar, fungsi putar dapat digunakan tidak hanya untuk memutar objek, tetapi juga untuk memindahkannya. Jadi benda-benda itu berputar. Tetapi jika kita membuat benda-benda ini bercahaya, maka umumnya benda itu super. Mari kita atur bayangan berwarna untuk mereka di style.css:



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


Sekarang pemain mengerti dengan pasti bahwa barang-barang ini dapat berinteraksi.



4.4 Menambahkan widget



Biasanya, widget menunjukkan skor, kesehatan, dan data numerik lain yang diperlukan. Di sini mereka akan menunjukkan jumlah koin yang dikumpulkan (kotak kuning) dan kunci (kotak merah), dan Anda dapat mengubahnya dari javascript. Pertama, mari tambahkan elemen baru ke 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>


Di menu.js, mari ikat variabel ke mereka:



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


Dan di dalam button1.onclick () tambahkan teks ke dalamnya:



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>";


Mari kita beri gaya dalam 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;
}


Mereka awalnya tidak terlihat. Mari kita buat 2 widget pertama terlihat saat memulai level di dalam 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>";


Ada widget, tetapi tidak terjadi apa-apa saat berinteraksi dengan objek. Kami akan mengubah label widget saat berinteraksi dari fungsi interaksi (di dalam if (r <(objek [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>";


Sekarang, saat mengambil koin dan kunci, informasi di widget berubah. Namun saat permainan berakhir, widget tidak disembunyikan. Mari kita sembunyikan mereka di akhir permainan dengan menambahkan baris berikut ke finishInteract () di dalam else:



widget1.style.display = "none";

widget2.style.display = "tidak ada";

widget3.style.display = "tidak ada";



Widget disembunyikan. Tetap menyiapkan widget yang meminta Anda untuk mengambil kunci jika Anda sampai di garis akhir tanpanya. Di finishInteract (), sebagai ganti console.log ("find the key"), masukkan baris berikut:



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


Jika upaya untuk mengakhiri permainan tidak berhasil, kami menerima pesan yang menghilang setelah 5 detik. Game kami sekarang terlihat seperti ini:











4.5 Mari memformat teks.



Mari buat folder Font di folder file. Unduh file font1.woff dari sini dan tempelkan ke Font. Tambahkan gaya teks ke 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");
}


Menu dan permainan telah berubah:











4.6 Tambahkan suara.



Unduh arsip Sounds.zip dari sini . Buat folder Suara di folder proyek dan masukkan suara di sana (ada dalam format mp3). Mari membuat referensi variabel untuk suara-suara ini:



//  

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";


Dalam fungsi interaksi, tambahkan argumen ke file suara dan putar suara (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>";
		};
	};
}


Di repeatFunction (), ubah panggilan ke fungsi ini sesuai:



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


Dan di finishInteract (), tambahkan kesalahanSound dan 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;
			};
		};
	};
};


Saat Anda mengklik tombol menu apa pun, kami akan memainkan suara 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";
}


Permainan dimainkan lebih cerah. Tetap mengonfigurasi keluaran hasil setelah melewati semua level:



4.7 Keluaran hasil.



Dalam menu.js di finishInteract () di dalam if (level> = 2) {…} tambahkan baris berikut:



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


Kami melihat jumlah poin yang dicetak setelah menyelesaikan semua level.

Ngomong-ngomong, jangan lupa untuk menambahkan baris ke fungsi yang sama:



canlock = false;


Dan:



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


dan



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


Hasil dari:



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>";
			};
		};
	};
};


Sekarang tombol start game berubah tergantung pada bagian level. Selain itu, pindahkan "container" ke tengah jendela dengan menambahkan baris berikut ke gayanya:



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


Dan hapus indentasi di tubuh:



body{
	margin:0px;
}


Jadi, kami telah sepenuhnya membuat game labirin 3D berbasis browser. Berkat dia, kami menarik perhatian ke beberapa aspek bahasa javascript, mempelajari tentang fungsi yang mungkin belum pernah Anda dengar sebelumnya. Dan yang paling penting, kami telah menunjukkan bahwa tidak terlalu sulit untuk membuat mainan sederhana untuk browser, bahkan dalam kode murni. Anda dapat mengunduh kode sumber lengkap dari sini (sources.zip) . Skrip itu sendiri dapat ditingkatkan secara signifikan dengan menambahkan pustaka yang berbeda di sana, menulis konstruktor baru, atau melakukan sesuatu yang lain.



Terima kasih atas perhatian Anda!



All Articles