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

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



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



أفترض أنك إذا كنت تقرأ هذا المقال ، فأنت مهتم بموضوع إنشاء ألعاب لـ Google Chrome ، مما يعني أنك تفهم كيفية عمل حزمة html-css-javaScript ، لذلك لن أتطرق إلى الأساسيات ، لكنني سأبدأ في التطوير على الفور. في html5 و css3 ، اللذين تدعمهما جميع المتصفحات الحديثة (لا يحسب المستكشف) ، من الممكن ترتيب الكتل في مساحة ثلاثية الأبعاد. يوجد أيضًا عنصر يمكنك من خلاله رسم الخطوط والأولويات الرسومية. تستخدم معظم محركات المتصفح <canvas> لأنه يمكن القيام بالمزيد من الأشياء عليه ، والأداء أفضل فيه. ولكن بالنسبة للأشياء البسيطة ، من الممكن تمامًا استخدام طرق تحويل ثلاثية الأبعاد ، والتي ستستغرق كودًا أقل.



1. أدوات التطوير



أنا أستخدم متصفحين فقط للتحقق من المواقع والألعاب: Chrome و Mozilla. جميع المتصفحات الأخرى (باستثناء المستكشف نفسه) مبنية على المحرك الأول ، لذلك لا أرى فائدة من استخدامها ، لأن النتائج هي نفسها تمامًا في Chrome. Notepad ++ يكفي لكتابة التعليمات البرمجية.



2. كيف يتم تنفيذ الفضاء ثلاثي الأبعاد في HTML؟



لنلقِ نظرة على نظام إحداثيات الكتلة:







بشكل افتراضي ، تحتوي الكتلة الفرعية على إحداثيات (يسار وأعلى) 0 بكسل في x و 0 بكسل في y. إزاحة (ترجمة) ، أيضًا 0 بكسل على جميع المحاور الثلاثة. دعنا نظهر هذا بمثال سننشئ له مجلدًا جديدًا. في ذلك ، سننشئ ملفات index.html و style.css و script.js. لنفتح 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>
</BODY>
</HTML>
<script src="script.js"></script>


في ملف style.css ، دعنا نضبط أنماط عنصري "الحاوية" و "العالم".



#container{
	position:absolute;
	width:1200px;
	height:800px;
	border:2px solid #000000;
}
#world{
	width:300px;
	height:300px;
        background-color:#C0FFFF;
}


دعونا نحفظ. عند فتح index.html باستخدام Chrome ، نحصل على:







لنحاول تطبيق translate3d على العنصر "world":



#world{
	width:300px;
	height:300px;
        background-color:#C0FFFF;
        transform:translate3d(200px,100px,0px);
}






كما تفهم ، لقد تحولت إلى وضع ملء الشاشة. الآن دعنا

نضبط الإزاحة Z: التحويل: translate3d (200px ، 100px ، -1000px) ؛



إذا فتحت ملف html في المتصفح مرة أخرى ، فلن ترى أي تغييرات. لمشاهدة التغييرات ، تحتاج إلى تعيين منظور لكائن "الحاوية":



#container{
	position:absolute;
	width:1200px;
	height:800px;
	border:2px solid #000000;
	perspective:600px;
}


نتيجة لذلك:







ابتعدت الساحة عنا. كيف يعمل المنظور في لغة تأشير النص الفائق؟ دعنا نلقي نظرة على الصورة:







d هي المسافة من المستخدم إلى الكائن ، و z هي إحداثياته. سالب z (في html this is translateZ) يعني أننا ابتعدنا عن الكائن ، و z موجب هو عكس ذلك. تحدد قيمة المنظور قيمة د. إذا لم يتم تعيين خاصية المنظور ، فمن المفترض أن تكون قيمة d لا نهائية ، وفي هذه الحالة لا يتغير الكائن بصريًا للمستخدم مع التغيير في z. في حالتنا ، قمنا بتعيين d = 600 بكسل. بشكل افتراضي ، تكون وجهة نظر المنظور في وسط العنصر ، ومع ذلك ، يمكن تغييرها عن طريق تعيين خاصية منظور-original.



الآن دعونا ندير "العالم" حول بعض المحاور. هناك طريقتان للتدوير في css. الأول هو الدوران حول المحاور x و y و z. للقيام بذلك ، استخدم خصائص التحويل rotateX () و rotateY () و rotateZ (). والثاني هو الدوران حول محور معين باستخدام خاصية rotate3d (). سنستخدم الطريقة الأولى لأنها أكثر ملاءمة لمهامنا. لاحظ أن محاور الدوران تخرج من مركز المستطيل!







يمكن تغيير النقطة التي تحدث عندها التحولات عن طريق تعيين الخاصية translate-origin: property. لذلك ، دعنا نضبط دوران "العالم" على المحور x:



#world{
	width:300px;
	height:300px;
background-color:#C0FFFF;
transform:translate3d(200px,100px,0px) rotateX(45deg);
}


نحصل على:







تعويض ملحوظ عكس اتجاه عقارب الساعة. إذا أضفنا rotateY () ، فسنحصل على إزاحة على طول المحور Y. من المهم ملاحظة أنه عندما يتم تدوير الكتلة ، فإن محاور الدوران تدور أيضًا. يمكنك أيضًا تجربة قيم دوران مختلفة.

الآن ، داخل كتلة "world" ، سننشئ كتلة أخرى ، لذلك نضيف علامة إلى ملف 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 id="square1"></div>
		</div>
	</div>
</BODY>
</HTML>
<script src="script.js"></script>


أضف أنماطًا إلى هذه الكتلة في style.css:



#square1{
	position:absolute;
	width:200px;
	height:200px;
	background-color:#FF0000;
}


نحصل على:







أي أن العناصر الموجودة داخل كتلة "العالم" ستتحول كجزء من هذه الكتلة. دعنا نحاول تدوير "square1" على طول المحور y عن طريق إضافة نمط دوران إليه:

التحويل: rotateY (30deg)؛



في النهاية:







"أين الدوران؟" - أنت تسأل؟ في الواقع ، هذا هو بالضبط ما يبدو عليه إسقاط الكتلة "المربعة 1" على المستوى الذي شكله عنصر "العالم". لكننا لسنا بحاجة إلى إسقاط ، بل إلى دوران حقيقي. لجعل كل العناصر الموجودة داخل "العالم" ثلاثية الأبعاد ، تحتاج إلى تطبيق نمط التحويل: الحفاظ على الخاصية ثلاثية الأبعاد عليه. بعد استبدال الخاصية داخل قائمة أنماط "العالم" ، تحقق من التغييرات:







ممتاز! نصف الكتلة "المربعة" مخفية خلف الكتلة الزرقاء. لإظهاره بالكامل ، دعنا نزيل لون كتلة "world" ، أي إزالة خط لون الخلفية: # C0FFFF؛ إذا أضفنا المزيد من المستطيلات داخل كتلة "العالم" ، فيمكننا إنشاء عالم ثلاثي الأبعاد. الآن دعنا نزيل إزاحة "العالم" بإزالة سطر خاصية التحويل في أنماط هذا العنصر.



3. خلق الحركة في عالم ثلاثي الأبعاد



لكي يتمكن المستخدم من التنقل في هذا العالم ، تحتاج إلى تحديد معالجات لضغطات المفاتيح وحركات الماوس. ستكون عناصر التحكم قياسية ، وهي موجودة في معظم ألعاب الرماية ثلاثية الأبعاد. باستخدام مفاتيح W ، S ، A ، D ، سنتحرك للأمام ، للخلف ، لليسار ، لليمين ، باستخدام شريط المسافة ، سنقفز (بمعنى آخر ، نتحرك لأعلى) ، وباستخدام الماوس سنغير اتجاه الرؤية. للقيام بذلك ، دعنا نفتح ملف script.js فارغًا. أولاً ، دعنا نضيف المتغيرات التالية هناك:



//   ?

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


لم يتم الضغط على أي مفاتيح في البداية. إذا ضغطنا على مفتاح ، ستتغير قيمة متغير معين إلى 1. إذا حررناه ، فسيصبح 0. سنقوم بتنفيذ هذا عن طريق إضافة معالجات للضغط وتحرير المفاتيح:



//   

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


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



//    ?

var onGround = true;


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



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


لنلصق هذا الرمز في script.js في بداية الملف. في نهاية الملف ، لنقم بإنشاء كائن من هذا النوع:



//   

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


دعنا نكتب ما تعنيه هذه المتغيرات. x ، y ، z هي الإحداثيات الأولية للاعب ، rx ، ry هي زوايا دورانه بالنسبة إلى محوري x و y بالدرجات. يعني آخر سطر مكتوب أننا نقوم بإنشاء كائن "بيدق" من النوع "player" (أنا أكتب نوعًا محددًا ، وليس فئة ، لأن الفئات في جافا سكريبت تعني بعض الأشياء الأخرى) بدون إحداثيات بداية. عندما نحرك الجسم ، يجب ألا يتغير تنسيق العالم ، ولكن يجب أن يتغير تنسيق "البيدق". هذا من حيث المتغيرات. ومن وجهة نظر المستخدم فاللاعب في مكان واحد ولكن العالم يتحرك. وبالتالي ، تحتاج إلى إجبار البرنامج على تغيير إحداثيات المشغل ، والتعامل مع هذه التغييرات ، وفي النهاية ، تحريك العالم. في الواقع ، هذا أسهل مما يبدو.



لذلك ، بعد تحميل المستند في المتصفح ، سنقوم بتشغيل وظيفة تعيد رسم العالم. لنكتب وظيفة إعادة الرسم:



function update(){
	
	//  
	
	let dx = (PressRight - PressLeft);
	let dz = - (PressForward - PressBack);
	let dy = PressUp;
	
	//    
	
	pawn.x = pawn.x + dx;
	pawn.y = pawn.y + dy;
	pawn.z = pawn.z + dz;
	
	//    ( )
	
	world.style.transform = 
	"rotateX(" + (-pawn.rx) + "deg)" +
	"rotateY(" + (-pawn.ry) + "deg)" +
	"translate3d(" + (-pawn.x) + "px," + (-pawn.y) + "px," + (-pawn.z) + "px)";
	
};


في المتصفحات الجديدة ، سيطابق العالم العنصر مع id = "world" ، ولكن من الآمن تعيينه قبل وظيفة update () باستخدام البناء التالي:



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


سنقوم بتغيير موقع العالم كل 10 مللي ثانية (100 تحديث في الثانية) ، والتي سنبدأ من أجلها حلقة لا نهائية:



TimerGame = setInterval(update,10);


دعونا نبدأ اللعبة. مرحى ، الآن يمكننا التحرك! ومع ذلك ، فإن العالم يزحف خارج حدود عنصر "الحاوية". لمنع حدوث ذلك ، فلنقم بتعيين خاصية css لها في style.css. أضف تجاوز السطر: مخفي ؛ ونرى التغييرات. العالم الآن لا يزال داخل الحاوية.



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



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 id="square1"></div>
		</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:300px;
	height:300px;
	transform-style:preserve-3d;
}
#square1{
	position:absolute;
	width:200px;
	height:200px;
	background-color:#FF0000;
	transform:rotateY(30deg);
}


script.js:



//  Pawn

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

//   ?

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

//    ?

var onGround = true;

//   

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

//   

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

//     world

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

function update(){
	
	//    
	
	let dx = (PressRight - PressLeft);
	let dz = - (PressForward - PressBack);
	let dy = - PressUp;
	
	//    
	
	pawn.x = pawn.x + dx;
	pawn.y = pawn.y + dy;
	pawn.z = pawn.z + dz;
	
	//    ( )
	
	world.style.transform = 
	"rotateX(" + (-pawn.rx) + "deg)" +
	"rotateY(" + (-pawn.ry) + "deg)" +
	"translate3d(" + (-pawn.x) + "px," + (-pawn.y) + "px," + (-pawn.z) + "px)";
	
};

TimerGame = setInterval(update,10);


إذا كان لديك شيء مختلف ، فتأكد من تصحيحه!



تعلمنا كيفية تحريك الشخصية ، لكننا لا نعرف كيفية تدويرها بعد! بالطبع ، سيتم تدوير الشخصية باستخدام الماوس. بالنسبة للماوس ، سنضيف متغيرات حالة حركة الماوس إلى متغيرات الحالة للضغط ... مفاتيح:



//       ?

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


وبعد معالجات الدفع ، أدخل معالج الحركة:



//   

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


أضف دورانًا إلى وظيفة التحديث:



	//    
	
	let dx = (PressRight - PressLeft);
	let dz = - (PressForward - PressBack);
	let dy = - PressUp;
	let drx = MouseY;
	let dry = - MouseX;
	
	//    
	
	pawn.x = pawn.x + dx;
	pawn.y = pawn.y + dy;
	pawn.z = pawn.z + dz;
	pawn.rx = pawn.rx + drx;
	pawn.ry = pawn.ry + dry;


لاحظ أن تحريك الماوس على المحور الصادي يؤدي إلى تدوير البيدق على طول المحور السيني والعكس صحيح. إذا نظرنا إلى النتيجة ، فسوف نشعر بالرعب مما رأيناه. النقطة المهمة هي أنه إذا لم يكن هناك إزاحة ، فسيظل MouseX و MouseY كما هو ، ولا يساوي الصفر. هذا يعني أنه بعد كل تكرار للتحديث ، يجب إعادة تعيين تعويضات misha إلى الصفر:



//    
	
	let dx = (PressRight - PressLeft);
	let dz = - (PressForward - PressBack);
	let dy = - PressUp;
	let drx = MouseY;
	let dry = - MouseX;

//   :
	
	MouseX = MouseY = 0;

//    
	
	pawn.x = pawn.x + dx;
	pawn.y = pawn.y + dy;
	pawn.z = pawn.z + dz;
	pawn.rx = pawn.rx + drx;
	pawn.ry = pawn.ry + dry;


والأفضل من ذلك ، تخلصنا من الجمود الدوراني ، لكن الدوران لا يزال غريبًا! لفهم ما يحدث ، دعنا نضيف "البيدق" div داخل "الحاوية":



	<div id="container">
		<div id="world">
			<div id="square1"></div>
		</div>
		<div id="pawn"></div>
	</div>


دعونا نصممها في style.css:



#pawn{
	position:absolute;
	width:100px;
	height:100px;
	top:400px;
	left:600px;
	transform:translate(-50%,-50%);
	background-color:#0000FF;
}


دعنا نتحقق من النتيجة. الآن كل شيء على نحو سلس! الشيء الوحيد هو أن المربع الأزرق لا يزال في المقدمة ، ولكن الآن دعنا نترك ذلك. لجعل اللعبة من منظور الشخص الأول وليس الثالث ، تحتاج إلى تقريب العالم إلينا من خلال قيمة المنظور. لنفعل ذلك في script.js في وظيفة update ():



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


الآن يمكنك جعل اللعبة من أول شخص. إخفاء البيدق بإضافة سطر إلى style.css:



#pawn{
	display:none;
	position:absolute;
	top:400px;
	left:600px;
	width:100px;
	height:100px;
	transform:translate(-50%,-50%);
	background-color:#0000FF;
}


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



	<div id="world">
			<div id="square1"></div>
			<div id="square2"></div>
		</div>


وفي style.css ، أضف أنماطًا لها:



#square2{
	position:absolute;
	width:1000px;
	height:1000px;
	top:400px;
	left:600px;
	background-color:#00FF00;
	transform:translate(-50%,-50%) rotateX(90deg) translateZ(-100px);
}


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



//  

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


الدرجة هي pi / 180 راديان. سيتعين علينا تطبيق الجيب وجيب التمام ، والتي يتم حسابها من الراديان. ما الذي يجب إنجازه؟ ألقِ نظرة على الصورة:







عندما يتم توجيه نظرنا بزاوية ونريد المضي قدمًا ، فحينئذٍ سيتغير كلا الإحداثيين: X و Z. في حالة الانتقال إلى الجانب ، ستغير الدوال المثلثية أماكنها ببساطة ، وستتغير الإشارة الموجودة أمام الجيب الناتج. لنقم بتغيير معادلات الإزاحة في update ():



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


راجع جميع الملفات بعناية! إذا تبين لك أن هناك خطأ ما ، فستكون هناك بالتأكيد أخطاء ستكسر رأسك!



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 id="square1"></div>
			<div id="square2"></div>
		</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;
}
#square1{
	position:absolute;
	width:200px;
	height:200px;
	top:400px;
	left:600px;
	background-color:#FF0000;
	transform:translate(-50%,-50%) rotateY(30deg);
}
#square2{
	position:absolute;
	width:1000px;
	height:1000px;
	top:400px;
	left:600px;
	background-color:#00FF00;
	transform:translate(-50%,-50%) rotateX(90deg) translateZ(-100px);
}
#pawn{
	display:none;
	position:absolute;
	top:400px;
	left:600px;
	transform:translate(-50%,-50%);
	width:100px;
	height:100px;
	background-color:#0000FF;
}


script.js:



//  

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

//  Pawn

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

//       ?

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

//    ?

var onGround = true;

//   

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


//     player

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

//     world

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

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

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

TimerGame = setInterval(update,10);


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



//     container

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

//    

container.onclick = function(){
	container.requestPointerLock();
};


الآن الأمر مختلف تمامًا. ومع ذلك ، فمن الأفضل عمومًا إجراء الاستدارة فقط عند التقاط المؤشر. دعنا نقدم متغير جديد بعد الضغط ...



//    ?

var lock = false;


دعنا نضيف معالجًا لتغيير حالة التقاط المؤشر (تم التقاطه أم لا) قبل معالج التقاط المؤشر (آسف على الحشو):



//     

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


وفي التحديث () أضف شرط التناوب "بيدق":



//   ,  

	if (lock){
		pawn.rx = pawn.rx + drx;
		pawn.ry = pawn.ry + dry;
	};


ولا يُسمح بالتقاط الماوس نفسه عند النقر فوق الحاوية إلا في حالة عدم التقاط المؤشر بعد:



//    

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


لقد تعاملنا مع الحركة بالكامل. دعنا ننتقل إلى توليد العالم



4. تحميل الخريطة



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



لنفتح index.html ونزيل كل الكتل الداخلية من كتلة "world":



<BODY>
	<div id="container">
		<div id="world"></div>
		<div id="pawn"></div>
	</div>
</BODY>


كما ترون ، لا يوجد شيء في "العالم" الآن. في style.css ، قم بإزالة الأنماط لـ # square1 و # square2 (قم بإزالة # square1 و # square2 من هذا الملف تمامًا) ، وبدلاً من ذلك أنشئ أنماطًا لفئة .square ، والتي ستكون مشتركة لجميع المستطيلات. وسنضع لها خاصية واحدة فقط:




.square{
	position:absolute;
}


لنقم الآن بإنشاء مجموعة من المستطيلات (على سبيل المثال ، سنقوم بدفعها بين مُنشئ المشغل ومتغيرات الضغط ... في script.js):



//  

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


كان من الممكن القيام بذلك في شكل مُنشئ ، ولكن في الوقت الحالي سوف نتعامل مع مصفوفة نقية ، لأنه من الأسهل بدء دورة ترتيب المستطيلات من خلال المصفوفات ، وليس من خلال المنشئات. سأشرح ما تعنيه الأرقام الموجودة فيه. تحتوي مصفوفة الخريطة على مصفوفات أحادية البعد من 9 متغيرات: [،،،،،،،،]. أعتقد أنك تفهم أن الأرقام الثلاثة الأولى هي إحداثيات مركز المستطيل ، والأرقام الثلاثة الثانية هي زوايا الدوران بالدرجات (بالنسبة إلى نفس المركز) ، ثم رقمان هما أبعاده والرقم الأخير هو الخلفية. علاوة على ذلك ، يمكن أن تكون الخلفية بلون خالص أو تدرج أو صورة فوتوغرافية. هذا الأخير مناسب جدًا للاستخدام كقوام.



لقد كتبنا المصفوفة ، والآن سنكتب دالة ستحول هذه المصفوفة إلى مستطيلات فعلية:



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


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



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


الآن نرى منطقة بجدران وردية وأرضية رمادية. كما ترى ، فإن إنشاء خريطة ليس صعب التنفيذ من الناحية الفنية. نتيجة لذلك ، يجب أن تبدو شفرتك في ثلاثة ملفات مثل



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(0,0,0,0,0);

//     world

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

function update(){
	
	//    
	
	let dx =   (PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg);
	let dz = - (PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg);
	let dy = - PressUp;
	let drx = MouseY;
	let dry = - MouseX;
	
	//   :
	
	MouseX = MouseY = 0;
	
	//    
	
	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);
	}
}

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


إذا كان كل شيء على ما يرام ، فانتقل إلى العنصر التالي.



5. اصطدام اللاعب مع كائنات العالم



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



function collision(){
	
}


وسوف نسميها في التحديث ():



//   :
	
	MouseX = MouseY = 0;
	
	//    
	
	collision();


كيف يحدث هذا؟ لنتخيل أن اللاعب كرة نصف قطرها r. وهو يتحرك باتجاه المستطيل: من







الواضح أنه إذا كانت المسافة من الكرة إلى مستوى المستطيل أكبر من r ، فإن الاصطدام لا يحدث بالتأكيد. لمعرفة هذه المسافة ، يمكنك ترجمة إحداثيات المشغل إلى نظام إحداثيات المستطيل. لنكتب وظيفة التحويل من النظام العالمي إلى نظام المستطيل:



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


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







في هذه الحالة ، تصبح حالة الاصطدام كما يلي: إذا ، بعد إزاحة الكرة بالقيمة v (v متجه) ، يكون الإحداثي z بين –r و r ، وإحداثيات x و y تقع داخل المستطيل أو مفصولة عنه بمقدار لا يزيد عن r ، إذن تم الإعلان عن تصادم. في هذه الحالة ، سيكون إحداثي z للاعب بعد الإزاحة هو r أو - r (حسب الجانب الذي يأتي منه اللاعب). وفقًا لذلك ، يتم تغيير إزاحة اللاعب. نحن نسمي التصادم على وجه التحديد قبل تحديث () إحداثيات المشغل لتغيير الإزاحة في الوقت المناسب. وبالتالي ، لن تتقاطع الكرة أبدًا مع المستطيل ، كما يحدث في خوارزميات الاصطدام الأخرى. على الرغم من أن اللاعب سيكون على الأرجح مكعبًا ماديًا ، إلا أننا لن ننتبه إلى ذلك. لذلك ، دعنا ننفذ هذا في جافا سكريبت:



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


x0 و y0 و z0 هي الإحداثيات الأولية للاعب في نظام إحداثيات المستطيل (لا توجد دورات. x1 و y1 و z1 هي إحداثيات اللاعب بعد الإزاحة بدون تصادم. النقطة 0 والنقطة 0 والنقطة 1 والنقطة 2 هي متجه نصف القطر الأولي ، متجه نصف القطر بعد الإزاحة بدون التصادمات ومتجه نصف القطر مع الاصطدامات ، على التوالي. خريطة [i] [3] وغيرها ، إذا كنت تتذكر ، فهذه هي زوايا دوران المستطيل. لاحظ أننا في الحالة لا نضيف 100 إلى حجم المستطيل ، بل 98. هذا عكاز ، لماذا ، فكر ابدأ اللعبة وسترى بعض الاصطدامات عالية الجودة.



كما ترى ، تتم كل هذه الإجراءات في حلقة for لجميع المستطيلات. مع وجود عدد كبير منها ، تصبح هذه العملية باهظة الثمن ، نظرًا لوجود 3 استدعاءات بالفعل لوظائف التحويل الإحداثية ، والتي تؤدي أيضًا الكثير من العمليات الحسابية. من الواضح ، إذا كانت المستطيلات بعيدة جدًا عن المشغل ، فلا معنى لحساب الاصطدام. دعنا نضيف هذا الشرط:




if ((x0**2 + y0**2 + z0**2 + dx**2 + dy**2 + dz**2) < (map[i][1]**2 + map[i][2]**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;
			}
			
		} 


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



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



All Articles