إنشاء ألعاب ثلاثية الأبعاد قائمة على المستعرض من البداية بتنسيق html و css و js خالص. الجزء 2/2

سنستمر في هذه المقالة في إنشاء لعبة متاهة متصفح ثلاثية الأبعاد بلغة html و css و javascript خالصة. في الجزء السابق ، صنعنا عالمًا بسيطًا ثلاثي الأبعاد ، ونفذنا الحركة والتحكم واصطدام اللاعب بأشياء ثابتة. في هذا الجزء سنضيف الجاذبية وضوء الشمس الساكن (بدون ظلال) وتحميل الأصوات وعمل القوائم. للأسف ، كما في الجزء الأول ، لن تكون هناك عروض توضيحية هنا.



دعنا نتذكر الكود الذي فعلناه في الجزء السابق. لدينا 3 ملفات:



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. تنفيذ فيزياء الجاذبية والقفز



لدينا العديد من المتغيرات التي تم إنشاؤها في أجزاء مختلفة من ملف جافا سكريبت. سيكون من الأفضل نقلهم إلى مكان واحد:



//  

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


دعنا نضيف لهم تسارع السقوط الحر:



var g = 0.1;




أضف 3 متغيرات إلى منشئ المشغل - vx و vy و 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;
}


هذه سرعات متغيرة. من خلال تغييرها ، يمكننا تغيير سرعة الجري وسرعة القفز الأولية للاعب. في الوقت الحالي ، دعنا نطبق المتغيرات الجديدة في 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;


يتحرك اللاعب الآن بشكل أسرع. لكنه لا يسقط أو يقفز. من الضروري السماح بالقفزة عندما تكون على شيء ما. وسوف يقف عندما يصطدم بسطح أفقي (أو شبه). كيف تحدد الأفقية؟ علينا إيجاد المستوى العمودي لمستوى المستطيل. يتم ذلك ببساطة. فيما يتعلق بإحداثيات المستطيل ، يتم توجيه الوضع الطبيعي على طول المحور z. ثم تغيرت الإحداثيات العادية في إحداثيات العالم. أوجد المتغير الطبيعي (أضف المتغير المحلي العادي):



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


لكي يكون السطح أفقيًا ، يجب أن يكون حاصل الضرب القياسي للمحور y في إحداثيات العالم 1 أو -1 ، ويجب أن يكون المستوى شبه الأفقي قريبًا من 1 أو -1. دعنا نضبط الشرط لمستوى أفقي تقريبًا:



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


لا تنس أنه في حالة عدم وجود تصادمات ، لن يكون اللاعب بالتأكيد على الأرض ، لذلك افتراضيًا ، في بداية وظيفة التصادم () ، قم بتعيين onGround = false:



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


ومع ذلك ، إذا اصطدم اللاعب بالسطح من الأسفل ، فسيظهر أيضًا كما لو كان على الأرض. لمنع هذا ، دعنا نتحقق من وجود اللاعب في أعلى الطائرة (يجب أن تكون النقطة 3 [1] أقل من النقطة 2 [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;



ماذا نفعل؟ ألق نظرة على الصورة:







يجب أن يكون الصليب الأحمر أقل من البرتقالي في نظام الإحداثيات العالمي (أو يجب أن يكون إحداثيات y أكبر). هذا ما نتحقق منه عند النقطة 3 [1]> النقطة 2 [1]. والنقطة 3 هي إحداثيات النقطة الحمراء فقط. دعنا ننتقل إلى تهيئة النقطة 2 داخل حالة التصادم:



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


دعنا ننتقل إلى التحديث (). سنقوم بإجراء تغييرات هنا أيضًا. أولاً ، دعنا نضيف الجاذبية ونزيل إزاحة y عند الضغط على مفتاح المسافة:



//    

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; 
 


ثانيًا ، إذا كان اللاعب على الأرض ، فإننا نحظر الجاذبية ، ونحظر الإزاحة في y (وإلا ، بعد المشي على سطح مائل ، سيقلع اللاعب) ونضيف القدرة على القفز (إذا (على الأرض)):



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


بطبيعة الحال ، بعد القفزة مباشرة ، نمنع القفزة المتكررة عن طريق ضبط معلمة onGround على false. في حالة شريط المسافة ، لم تعد هناك حاجة إلى صلاحية هذه المعلمة:



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


لاختبار التغييرات ، دعنا نغير العالم:



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


إذا بدأنا اللعبة ، فسنرى أنه يمكن للاعب تسلق جدار أخضر عمودي تقريبًا. قم بتعطيل هذا عن طريق إضافة 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;
			}


لذا ، فإن الاصطدامات بجدران عمودية عالية لا تغير إزاحة y. لذلك ، تم الآن استبعاد رفع تردد التشغيل على هذه الجدران تمامًا. دعنا نحاول تسلق الجدار الأخضر. لن نتمكن من القيام بذلك الآن. لذلك ، اكتشفنا الجاذبية والقفز ، والآن يمكننا التسلق بشكل واقعي إلى حد ما على الأسطح المائلة قليلاً. دعنا نتحقق من الكود:



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. إنشاء قائمة



لنقم بإنشاء القائمة في شكل لوحات html وكتل html. سيكون تصميم القائمة بأكملها متماثلًا تقريبًا: يمكن تعيين خلفية الأزرار ونمطها بشكل مشترك للجميع. لذلك ، دعونا نضع ثلاث لوحات قائمة: القائمة الرئيسية ، والتعليمات ، وإخراج النتائج عند الانتهاء من اللعبة. سيتم تنفيذ الانتقالات بين القائمة والانتقال إلى العالم والعودة بواسطة نصوص جافا سكريبت. لتجنب ازدحام ملف script.js ، قم بإنشاء ملف menu.js جديد للتنقل في القائمة ، وقم بتضمينه في index.html:



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


في الحاوية ، سننشئ 3 عناصر ستكون أشرطة قوائم:



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


لنقم بتصميمها عن طريق إضافة خصائص لفئة "القائمة" إلى style.css:



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


أضف الأزرار ذات التسميات التوضيحية المناسبة إلى القائمة (في ملف 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>


بالنسبة للأزرار ، سنقوم أيضًا بتعيين الأنماط في 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;
}


لكننا لا نرى القائمة ، نظرًا لأنها تحتوي على شاشة العرض: بلا نمط. عند بدء اللعبة ، يجب أن يكون أحد عناصر القائمة مرئيًا ، لذا في html للقائمة الأولى ، أضف إدخال style = "display: block" ، وابدو هكذا سيكون مثل هذا:



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


تبدو القائمة الآن كما يلي:







رائع. ولكن إذا نقرنا على الزر ، فسيتم التقاط المؤشر. لذلك نحتاج إلى السماح بالتقاط الماوس فقط في حالة وجود لعبة. للقيام بذلك ، أدخل متغير canlock في script.js وأضفه إلى متغيرات إنشاء العنصر:



//  

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


يمكننا الآن النقر فوق القائمة. لنقم بتهيئة الانتقالات باستخدام البرامج النصية في ملف 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";
}


تعمل الآن جميع أزرار القائمة باستثناء "بدء اللعبة". لنقم الآن بتهيئة زر الزر 1. إذا كنت تتذكر ، في ملف script.js ، يتم تشغيل وظائف CreateNewWorld () و setInterval () عند تحميل صفحة الويب. دعونا نزيلهم من هناك. سوف نتصل بهم فقط عند الضغط على الزر 1. دعنا نقوم به:



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


لقد أنشأنا قائمة. نعم ، لا يزال الأمر قبيحًا ، لكنه يتحسن بسهولة.



3. لنقم بإنشاء كائنات وانتقال المستويات.



أولاً ، دعنا نحدد قواعد اللعبة. لدينا ثلاثة أنواع من العناصر: العملات المعدنية (المربعات الصفراء) والمفاتيح (المربعات الحمراء) والنهاية (المربع الأزرق). العملات المعدنية تجلب النقاط. يحتاج اللاعب إلى العثور على المفتاح ، وعندها فقط يصل إلى خط النهاية. إذا وصل إلى خط النهاية بدون مفتاح ، فسوف يتلقى رسالة حول الحاجة إلى العثور على المفتاح أولاً. سيتم إنشاء الكائنات بنفس طريقة إنشاء الخريطة. سنكتبها باستخدام المصفوفات. لكننا لن نجعل لهم وظيفة منفصلة. سنقوم فقط بكتابة وظيفة جديدة تقوم بترتيب عناصر الخريطة والمستطيل وتنقل الأوامر من CreateNewWorld (). دعنا نسميها CreateSquares (). لذلك دعونا نضيف الإدخال التالي إلى نهاية ملف 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);
	}
}


وسنقوم بتغيير محتوى createNewWorld ():



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


السلسلة مطلوبة لتعيين معرف الاسم. اللعبة لم تتغير قليلا بعد. الآن دعنا نضيف 3 مصفوفات: عملات معدنية (أشياء) ، مفاتيح (مفاتيح) وإنهاء (إنهاء). دعنا ندرجهم مباشرة بعد مصفوفة الخريطة:



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


وفي menu.js ، دعنا نستخدم وظيفة CreateSquares () داخل المعالج للضغط على الزر "button1":



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


الآن دعنا نبدأ اختفاء الأشياء. في menu.js ، دعنا ننشئ وظيفة لفحص المسافات من المشغل إلى الكائنات:



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


أيضًا ، في نفس الملف ، أنشئ وظيفة تكرار الوظيفة () وأضف الأوامر إليها:



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


وسنقوم بتشغيل المكالمة الدورية في setInterval inside button1:



TimerGame = setInterval(repeatFunction,10);


تختفي الأشياء الآن عندما نقترب منها. ومع ذلك ، فهم لا يفعلون شيئًا على الإطلاق. ونريد إضافة نقاط إلينا عندما نأخذ المربعات الصفراء ، وعندما نأخذ المربعات الحمراء ، تتاح لنا الفرصة لأخذ اللون الأزرق وإنهاء اللعبة. دعنا نعدل وظيفة التفاعل ():



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 repeatFunction(){
	update();
	interact(things,"thing",m);
	interact(keys,"key",k);
}


وفي بداية الملف ، أضف أربعة متغيرات جديدة:



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


أنت تسأل لماذا أنشأنا مصفوفات من عنصر واحد وليس فقط المتغيرات؟ النقطة المهمة هي أننا أردنا تمرير هذه المتغيرات للتفاعل () بالإشارة ، وليس بالقيمة. في جافا سكريبت ، يتم تمرير المتغيرات العادية بالقيمة فقط ، ويتم تمرير المصفوفات عن طريق المرجع. إذا مررنا متغيرًا فقط للتفاعل () ، فسيكون num نسخة من المتغير. لن يؤدي تغيير الأسطوانات إلى تغيير k أو m. وإذا مررنا مصفوفة ، فإن num سيكون مرجعًا للمصفوفة k أو m ، وعندما نغير الأسطر [0] ، فإن k [0] و m [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";
		};
	};
};


وقم بإعداد clearWorld () في script.js:



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


كما ترى ، فإن تنظيف العالم بسيط للغاية. أضف finishInteract () لتكرار الوظيفة ():



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


ما الذي يحدث في finishInteract ()؟ إذا لم نأخذ المفتاح (k [0] == 0) ، فلن يحدث شيء بعد. إذا فعلوا ذلك ، تنتهي اللعبة ، ويحدث ما يلي: يتم مسح العالم ، وتتوقف وظيفة تكرار الوظيفة () ، ويتوقف المؤشر عن الالتقاط ، ويتم إعادة تعيين عداد المفاتيح ، وننتقل إلى القائمة الرئيسية. دعنا نتحقق من خلال تشغيل اللعبة. كل شيء يعمل. ومع ذلك ، بعد النقر مرة أخرى على اللعبة ، نجد أنفسنا على الفور عند خط النهاية ، وتختفي بعض العناصر. هذا لأننا لم ندخل موقع التفرخ الأولي للاعب ، وتتغير المصفوفات أثناء اللعبة. دعونا نضيف نقطة ولادة للاعب إلى الزر 1 ، أي مساواة إحداثياته ​​بعناصر مصفوفة البداية [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;
}


يولد اللاعب الآن في الأصل. لكن السؤال هو: ماذا لو كانت هناك عدة مستويات في اللعبة؟ دعنا نضيف متغير مستوى إلى 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;


دعونا نعيد تشكيل خريطة المتغيرات والأشياء والمفاتيح والبدء والانتهاء داخل script.js إلى مصفوفات ، مع تغيير اسمها قليلاً:



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


ويتم تهيئة المصفوفات نفسها قبل المستويات:



//   

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


يجب تغيير وظيفة CreateNewWorld () عن طريق إضافة وسيطة هناك:



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


لنقم بتغيير الاستدعاء إلى CreateNewWorld () في ملف 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;
}


الآن ستعطي وحدة التحكم خطأ عند بدء التشغيل. حسنًا ، نظرًا لأننا أعدنا تسمية خريطة المتغيرات والأشياء والمفاتيح والنهاية ، فإن جافا سكريبت الآن لا تستطيع معرفة ماهية هذه المتغيرات. نقوم بتهيئتها مرة أخرى في script.js:



//   

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


وفي button1 (في menu.js) نخصص نسخًا من عناصر صفائف mapArray و ThingsArray و keysArray و finishArray لهذه المتغيرات (لقراءة أفضل ، ضع التعليقات):



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


حيث userSlice () هي الوظيفة التي تنسخ المصفوفة:



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


إذا كتبنا ببساطة ، على سبيل المثال ، keys = keysArray [المستوى] ، فلن يتم نقل نسخ المصفوفات إلى المتغيرات ، ولكن سيتم نقل المؤشرات إليها ، مما يعني أنها ستتغير أثناء اللعبة ، وهو أمر غير مقبول ، لأنه عند إعادة تشغيل المفتاح على المكان الأصلي لم يعد موجودًا. ربما تسأل لماذا لم أستخدم فقط keysArray [level] .slice () ، لكنني اخترعت الوظائف الخاصة بي؟ بعد كل شيء ، slice () تنسخ المصفوفات أيضًا. حاولت القيام بذلك ، لكنه كان ينسخ المرجع إلى المصفوفة ، وليس المصفوفة نفسها ، ونتيجة لذلك أدى تغيير المفاتيح إلى تغيير keysArray [المستوى] ، مما يعني اختفاء المفتاح عند إعادة التشغيل. والحقيقة هي أن الوثائق تقول إنه في بعض الحالات يرى المصفوفات كمصفوفات وينسخها ، وفي حالات أخرى يرى المصفوفات ككائنات وينسخها فقط تشير إليها. كيف يحدد ذلك هو لغزا بالنسبة لي ،لذلك إذا كان بإمكان أي شخص أن يخبرني لماذا لا تعمل slice () كما هو مخطط لها ، فسأكون ممتنًا جدًا له.



دعونا نجعل الانتقال من المستويات. انها بسيطة جدا. دعنا نعدل finishInteract () عن طريق إضافة الأسطر التالية داخل الآخر:



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


أي أن قيمة المستوى تضاف بمقدار 1 ، وإذا تم تمرير جميع المستويات (لدينا 2 منهم) ، فسيتم إعادة تعيين المستويات وإعادة تعيين نقاط النتيجة. من الصعب التحقق من ذلك ، لأن مستوياتنا لا تختلف الآن. دعنا ثم نغير 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"]
];


قمنا بتغيير لون الجدران. لنلعب لعبة. نرى أنه بعد اجتياز المستوى الأول (بجدران أرجوانية وعدة مستطيلات) ، ننتقل إلى المستوى الثاني (بجدران خضراء) ، وعندما نجتاز المستوى الثاني ، نعود إلى الأول. لذلك ، لقد انتهينا من انتقال المستويات. يبقى فقط تصميم اللعبة عن طريق تغيير الخطوط وتلوين العالم وجعل المستويات أكثر صعوبة. لم نقم بتغيير ملفي index.html و style.css ، لذا تحقق من البرامج النصية:



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. دعونا نصمم اللعبة.



4.1 تغيير المستويات



بناء المستوى هو نشاط ممتع للغاية. عادة ، يتم ذلك من قبل الأفراد الذين يطلق عليهم مصممو المستوى. يتم تمثيل مستوانا بمصفوفات من الأرقام التي يتم تحويلها بواسطة البرامج النصية من script.js إلى عالم ثلاثي الأبعاد. من الممكن كتابة برنامج منفصل لتبسيط عملية إنشاء العوالم ، لكننا الآن لن نفعل ذلك. لنفتح ملف script.js ونحمّل مصفوفات المتاهات الجاهزة هناك:



صفائف المستوى
// 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"]];




يمكننا الآن لعب اللعبة. نتيجة لذلك ، تبدو المستويات كما يلي:







من الصعب للغاية التنقل في مثل هذا العالم. بالإضافة إلى ذلك ، فإن الحركة على طول الجدران تحتوي على أخطاء ، حيث يمكن للاعب أن يعلق عند زوايا الجدران. لنصلح ذلك في حالة التصادم () ، باستبدال 98 بـ 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 إضافة إضاءة ثابتة



لتسهيل التنقل ، نقوم بتنفيذ الإضاءة الشمسية الثابتة (بدون ظلال). دعنا نضيف متجه لضوء الشمس:



var sun = [0.48,0.8,0.36];


كيف تصنع الإضاءة؟ انظر إلى الصورة:







إذا كانت الشمس المتجهة تمامًا عكس المتجه n ، فسيتم تكبير الإضاءة. تعتمد شدة الضوء على زاوية سقوط الضوء على السطح. إذا كان شعاع الضوء موازيًا للمستوى أو سقط من الجانب الآخر ، فإن الطائرة غير مضاءة. يمكن حساب زاوية السقوط باستخدام المنتج القياسي n * sun: إذا كانت سالبة ، فإن الإضاءة تعتمد على معامل المنتج النقطي ، وإذا كان موجبًا ، فلا توجد إضاءة. سنقوم بإنشاء إضاءة الأسطح عند إنشاء العالم ، أي في CreateNewWorld (). ونظرًا لوجود وظيفة CreateSquare () فقط ، سنقوم بتطبيق الإضاءة هناك. لكن من المحتمل أننا سنطبق فقط البشارة على العالم ، ولكن ليس على الأشياء ، لذلك سنضيف حجة الإضاءة هناك ، وسنغير CreateSquare () نفسه:



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


لنقم بتشغيل الإضاءة عند إنشاء العالم في CreateNewWorld ():



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


وأضف إيقاف تشغيل الإضاءة للعناصر الموجودة في button1.onclick (في CreateSquares ، المعلمة الأخيرة لها خاطئة):



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


لنبدأ اللعبة ونلاحظ أن الإضاءة أصبحت أكثر واقعية ، وأنه من الأسهل بكثير التنقل في الفضاء:







أضف سماء زرقاء. لنقم بتعيين خلفية #container في style.css:



background-color:#C0FFFF;


تحولت السماء إلى اللون الأزرق:







لقد صممنا المستويات. لكن لا يزال من الصعب العثور على العناصر ، لأنها ثابتة ، ويصعب على اللاعب حدسيًا فهم إمكانية جمعها.



4.3 إضافة الدوران والضوء للأشياء



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


وسوف نسميها من وظيفة التكرار ():



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


صحيح ، يمكن استخدام وظيفة التدوير ليس فقط لتدوير الكائنات ، ولكن أيضًا لتحريكها. لذا فإن الأجسام تدور. ولكن إذا جعلنا هذه الأشياء مضيئة ، فستكون رائعة بشكل عام. دعونا نضع لهم الظلال الملونة في style.css:



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


الآن يدرك اللاعب بالتأكيد أنه يمكن التفاعل مع هذه العناصر.



4.4 إضافة الحاجيات



عادةً ما تُظهر عناصر واجهة المستخدم النتيجة والصحة والبيانات الرقمية الضرورية الأخرى. سيظهرون هنا عدد العملات المعدنية المجمعة (المربعات الصفراء) والمفاتيح (المربعات الحمراء) ، ويمكنك تغييرها من جافا سكريبت. أولاً ، دعنا نضيف عناصر جديدة إلى 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>


في menu.js ، دعنا نربط المتغيرات بها:



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


وداخل button1.onclick () إضافة نص إليها:



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


دعونا نصممها في 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;
}


هم في البداية غير مرئيين. لنجعل أول عنصرين مرئيين عند بدء المستوى داخل 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>";


هناك عناصر واجهة مستخدم ، لكن لا شيء يحدث عند التفاعل مع الكائنات. سنقوم بتغيير تسميات الأدوات عند التفاعل من وظائف التفاعل (في الداخل إذا (r <(الكائنات [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>";


الآن ، عند أخذ العملات المعدنية والمفتاح ، تتغير المعلومات الموجودة في الأدوات. ولكن عندما تنتهي اللعبة ، لا يتم إخفاء الأدوات. دعنا نخفيهم في نهاية اللعبة عن طريق إضافة الأسطر التالية لإنهاء () finishInteract () داخل else:



widget1.style.display = "none"؛

widget2.style.display = "بلا" ؛

widget3.style.display = "لا شيء" ؛



الحاجيات مخفية. يبقى إعداد عنصر واجهة مستخدم يطلب منك أخذ مفتاح إذا وصلت إلى خط النهاية بدونه. في finishInteract () ، بدلاً من console.log ("اعثر على المفتاح") ، أدخل الأسطر التالية:



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


في حالة فشل محاولة إنهاء اللعبة ، نتلقى رسالة تختفي بعد 5 ثوانٍ. لعبتنا الآن تبدو كما يلي:











4.5 لنقم بتنسيق النص.



لنقم بإنشاء مجلد الخطوط في مجلد الملفات. قم بتنزيل ملف font1.woff من هنا والصقه في Fonts. أضف أنماط النص إلى 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");
}


تغيرت القائمة واللعبة:











4.6 إضافة أصوات.



قم بتنزيل أرشيف Sounds.zip من هنا . أنشئ مجلد الأصوات في مجلد المشروع وأدخل الأصوات هناك (بصيغة mp3). دعونا نجعل مراجع متغيرة لهذه الأصوات:



//  

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


في وظيفة التفاعل ، أضف وسيطة إلى ملف الصوت وقم بتشغيل الصوت (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>";
		};
	};
}


في وظيفة التكرار () ، قم بتغيير المكالمات إلى هذه الوظيفة وفقًا لذلك:



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


وفي النهاية Interact () ، أضف الخطأأصوات الصوت والفوز:



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


عند النقر فوق أي زر قائمة ، سنقوم بتشغيل صوت 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";
}


لعبت اللعبة أكثر إشراقًا. يبقى تخصيص مخرجات النتائج بعد اجتياز جميع المستويات:



4.7 إخراج النتائج.



في menu.js في finishInteract () داخل if (level> = 2) {…} أضف الأسطر التالية:



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


نرى عدد النقاط التي تم تسجيلها بعد استكمال جميع المستويات.

بالمناسبة ، دعونا لا ننسى إضافة السطر إلى نفس الوظيفة:



canlock = false;


و:



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


و



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


كنتيجة ل:



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


الآن يتغير زر بدء اللعبة حسب مرور المستويات. أيضًا ، انقل "الحاوية" إلى وسط النافذة ، مضيفًا الأسطر التالية إلى أنماطها:



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


وإزالة الفراغات في الجسم:



body{
	margin:0px;
}


لذلك ، قمنا بكتابة لعبة متاهة ثلاثية الأبعاد تعتمد على المتصفح بالكامل. بفضلها ، لفتنا الانتباه إلى بعض جوانب لغة جافا سكريبت ، وتعلمنا عن وظائف ربما لم تسمع بها من قبل. والأهم من ذلك ، لقد أظهرنا أنه ليس من الصعب صنع ألعاب بسيطة للمتصفح ، حتى في كود نقي. يمكنك تنزيل كود المصدر الكامل من هنا (sources.zip) . يمكن تحسين البرامج النصية نفسها بشكل كبير عن طريق إضافة مكتبات مختلفة هناك أو كتابة مُنشئات جديدة أو القيام بشيء آخر.



شكرآ لك على أهتمامك!



All Articles