Erstellung von browserbasierten 3D-Spielen von Grund auf in reinem HTML, CSS und JS. Teil 1/2

Mit moderner Computertechnologie können Sie coole Computerspiele erstellen! Und jetzt sind Spiele mit 3D-Grafiken sehr beliebt, da Sie beim Spielen in eine fiktive Welt eintauchen und jegliche Verbindung zur Realität verlieren. Die Entwicklung der Internet- und Browsertechnologien hat es möglich gemacht, Rätsel und Shooter in Ihrem Lieblings-Chrome, Mozilla oder etwas anderem (lassen Sie uns über den Explorer schweigen) online auszuführen, ohne sie herunterzuladen. Hier erkläre ich Ihnen, wie Sie ein einfaches dreidimensionales Browsergame erstellen.



Die Wahl des Genres, der Handlung und des Stils des Spiels ist eine interessante Aufgabe, und der Erfolg des Spiels kann von der Lösung dieser Probleme abhängen. Darüber hinaus bringt die Wahl der Technologie, auf deren Grundlage das Produkt hergestellt wird, auch eigene Nuancen mit sich. Mein Ziel ist es, die grundlegenden Grundlagen dieses unterhaltsamen Prozesses zu zeigen, damit ich ein dreidimensionales Labyrinth mit einem einfachen Design erstellen kann. Darüber hinaus werde ich es in reinem Code tun, ohne Bibliotheken und Engines wie three.js zu verwenden (obwohl es besser ist, große Projekte darauf durchzuführen), um zu zeigen, wie Sie eine Engine für Ihre Anforderungen erstellen können. Ein vollständig selbst geschriebenes Spiel kann originell und daher interessant sein. Im Allgemeinen haben beide Ansätze ihre Vor- und Nachteile.



Ich nehme an, wenn Sie diesen Artikel lesen, interessieren Sie sich für das Thema Spiele für Google Chrome. Das bedeutet, dass Sie verstehen, wie das HTML-CSS-JavaScript-Bundle funktioniert. Ich werde mich also nicht mit den Grundlagen befassen, sondern sofort mit der Entwicklung beginnen. In HTML5 und CSS3, die von allen modernen Browsern unterstützt werden (der Explorer zählt nicht), ist es möglich, Blöcke im dreidimensionalen Raum anzuordnen. Es gibt auch ein Element, in dem Sie Linien und grafische Grundelemente zeichnen können. Die meisten Browser-Engines verwenden <canvas>, da mehr Dinge daran getan werden können und die Leistung besser ist. Für einfache Dinge ist es jedoch durchaus möglich, Transform-3D-Methoden zu verwenden, die weniger Code benötigen.



1. Entwicklungswerkzeuge



Ich benutze nur 2 Browser, um Websites und Spiele zu überprüfen: Chrome und Mozilla. Alle anderen Browser (mit Ausnahme des Explorers selbst) basieren auf der ersten Engine, daher sehe ich keinen Sinn darin, sie zu verwenden, da die Ergebnisse genau die gleichen sind wie in Chrome. Notepad ++ reicht aus, um Code zu schreiben.



2. Wie wird 3D-Raum in HTML implementiert?



Schauen wir uns das Blockkoordinatensystem an:







Standardmäßig hat der untergeordnete Block Koordinaten (links und oben) von 0 Pixel in x und 0 Pixel in y. Offset (übersetzen), ebenfalls 0 Pixel auf allen drei Achsen. Lassen Sie uns dies anhand eines Beispiels zeigen, für das wir einen neuen Ordner erstellen. Darin erstellen wir die Dateien index.html, style.css und script.js. Öffnen wir index.html und schreiben Sie dort Folgendes:



<!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>


Legen Sie in der Datei style.css die Stile für die Elemente "container" und "world" fest.



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


Lass uns sparen.







Wenn wir index.html mit Chrome öffnen, erhalten wir: Versuchen wir, translate3d auf das Element "world" anzuwenden:



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






Wie Sie verstehen, habe ich in den Vollbildmodus gewechselt.

Stellen wir nun den Z-Offset ein: transform: translate3d (200px, 100px, -1000px);



Wenn Sie die HTML-Datei erneut im Browser öffnen, werden keine Änderungen angezeigt. Um die Änderungen zu sehen, müssen Sie die Perspektive für das Objekt "container" festlegen:



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


Infolgedessen: Der







Platz hat sich von uns entfernt. Wie funktioniert die Perspektive in HTML? Schauen wir uns das Bild an:







d ist der Abstand vom Benutzer zum Objekt und z ist seine Koordinate. Negatives z (in HTML ist das translateZ) bedeutet, dass wir das Objekt entfernt haben, und positives - umgekehrt. Der perspektivische Wert bestimmt den Wert von d. Wenn die perspektivische Eigenschaft nicht festgelegt ist, wird angenommen, dass der d-Wert unendlich ist, und in diesem Fall ändert sich das Objekt für den Benutzer mit einer Änderung in z nicht visuell. In unserem Fall setzen wir d = 600px. Standardmäßig befindet sich der perspektivische Blickpunkt in der Mitte des Elements. Er kann jedoch durch Festlegen der Eigenschaft perspektivischer Ursprung: geändert werden.



Drehen wir nun die "Welt" um eine Achse. Es gibt zwei Arten der Rotation, die in CSS verwendet werden können. Die erste ist die Drehung um die x-, y- und z-Achse. Verwenden Sie dazu die Transformationseigenschaften rotateX (), rotateY () und rotateZ (). Die zweite ist die Drehung um eine bestimmte Achse mithilfe der Eigenschaft rotate3d (). Wir werden die erste Methode anwenden, da sie für unsere Aufgaben besser geeignet ist. Beachten Sie, dass die Drehachsen von der Mitte des Rechtecks ​​ausgehen!







Der Punkt, an dem die Transformationen stattfinden, kann durch Festlegen der Eigenschaft translate-origin: geändert werden. Stellen wir also die Rotation von "Welt" entlang der x-Achse ein:



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


Wir erhalten:







Auffälliger Versatz gegen den Uhrzeigersinn. Wenn wir rotateY () hinzufügen, erhalten wir die Verschiebung entlang der Y-Achse. Es ist wichtig zu beachten, dass sich beim Drehen des Blocks auch die Rotationsachsen drehen. Sie können auch mit verschiedenen Rotationswerten experimentieren.

Jetzt erstellen wir innerhalb des "Welt" -Blocks einen weiteren Block. Dazu fügen wir der HTML-Datei ein Tag hinzu:



<!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>


Fügen Sie in style.css diesem Block Stile hinzu:



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


Wir bekommen:







Das heißt, die Elemente innerhalb des "Welt" -Blocks werden als Teil dieses Blocks transformiert. Versuchen wir, "square1" entlang der y-Achse zu drehen, indem wir einen Rotationsstil hinzufügen:

transform: rotateY (30deg);



Am Ende:







"Wo ist die Rotation?" - du fragst? Genau so sieht die Projektion des Blocks „square1“ auf die Ebene aus, die vom Element „world“ gebildet wird. Wir brauchen aber keine Projektion, sondern eine echte Rotation. Um alle Elemente innerhalb der "Welt" volumetrisch zu machen, müssen Sie die Eigenschaft im Transformationsstil darauf anwenden: keep-3d. Überprüfen Sie die Änderungen, nachdem Sie die Eigenschaft in der Liste der "Welt" -Stile ersetzt haben:







Ausgezeichnet! Die Hälfte des "quadratischen" Blocks ist hinter dem blauen Block versteckt. Um es vollständig anzuzeigen, entfernen Sie die Farbe des "Welt" -Blocks, nämlich die Hintergrundfarbenlinie: # C0FFFF; Wenn wir innerhalb des "Welt" -Blocks weitere Rechtecke hinzufügen, können wir eine 3D-Welt erstellen. Entfernen wir nun den "Welt" -Versatz, indem wir die Transformations-Eigenschaftslinie in den Stilen für dieses Element entfernen.



3. Erstellen Sie Bewegung in einer dreidimensionalen Welt



Damit sich der Benutzer in dieser Welt bewegen kann, müssen Sie Handler für Tastenanschläge und Mausbewegungen definieren. Die Steuerung ist Standard, was bei den meisten 3D-Shootern der Fall ist. Mit den Tasten W, S, A, D bewegen wir uns vorwärts, rückwärts, links, rechts, mit der Leertaste springen wir (mit anderen Worten nach oben) und mit der Maus ändern wir die Blickrichtung. Öffnen Sie dazu eine noch leere Datei script.js. Fügen wir dort zunächst die folgenden Variablen hinzu:



//   ?

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


Anfangs wurden keine Tasten gedrückt. Wenn wir eine Taste drücken, ändert sich der Wert einer bestimmten Variablen auf 1. Wenn wir sie freigeben, wird sie zu 0. Wir implementieren dies, indem wir Handler zum Drücken und Freigeben von Tasten hinzufügen:



//   

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


Nummer 32 ist ein Leerzeichen. Wie Sie sehen können, gibt es eine Variable onGround, die angibt, ob wir am Boden sind. Lassen Sie uns zunächst eine Aufwärtsbewegung zu, indem Sie die Variable onGround nach dem Drücken von ... Variablen hinzufügen:



//    ?

var onGround = true;


Deshalb haben wir einen Push-and-Pull-Algorithmus hinzugefügt. Jetzt müssen wir die Bewegung selbst hinzufügen. Was bewegen wir eigentlich? Stellen wir uns vor, wir haben ein Objekt, das wir bewegen. Nennen wir es "Bauer". Wie es für normale Entwickler üblich ist, erstellen wir dafür eine separate "Player" -Klasse. Klassen in JavaScript werden seltsamerweise mit folgenden Funktionen erstellt:



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


Fügen wir diesen Code ganz am Anfang der Datei in script.js ein. Erstellen wir am Ende der Datei ein Objekt dieses Typs:



//   

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


Schreiben wir auf, was diese Variablen bedeuten. x, y, z sind die Anfangskoordinaten des Spielers, rx, ry sind die Winkel seiner Drehung relativ zur x- und y-Achse in Grad. Die letzte geschriebene Zeile bedeutet, dass wir ein "Bauern" -Objekt vom Typ "Spieler" (ich schreibe einen Typ speziell, keine Klasse, da Klassen in Javascript ein paar andere Dinge bedeuten) mit Null-Startkoordinaten erstellen. Wenn wir das Objekt bewegen, sollte sich die Weltkoordinate nicht ändern, aber die "Bauern" -Koordinate sollte sich ändern. Dies ist in Bezug auf Variablen. Und aus der Sicht des Benutzers befindet sich der Spieler an einem Ort, aber die Welt bewegt sich. Daher müssen Sie das Programm zwingen, die Koordinaten des Spielers zu ändern, diese Änderungen zu handhaben und am Ende die Welt zu bewegen. In der Tat ist dies einfacher als es klingt.



Nachdem wir das Dokument in den Browser geladen haben, führen wir eine Funktion aus, die die Welt neu zeichnet. Schreiben wir eine Neuzeichnungsfunktion:



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


In neuen Browsern stimmt world mit dem Element mit id = "world" überein. Es ist jedoch sicherer, es vor der update () - Funktion mit der folgenden Konstruktion zuzuweisen:



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


Wir werden die Position der Welt alle 10 ms ändern (100 Aktualisierungen pro Sekunde), für die wir eine Endlosschleife starten werden:



TimerGame = setInterval(update,10);


Lass uns das Spiel starten. Hurra, jetzt können wir umziehen! Die Welt kriecht jedoch aus den Grenzen des "Container" -Elements heraus. Um dies zu verhindern, legen wir in style.css eine CSS-Eigenschaft fest. Fügen Sie den Zeilenüberlauf hinzu: hidden; und sehen Sie die Änderungen. Die Welt bleibt jetzt im Container.



Es ist möglich, dass Sie nicht immer verstehen, wo Sie bestimmte Codezeilen schreiben müssen. Deshalb werde ich Ihnen jetzt die Dateien präsentieren, die Sie meines Erachtens erhalten sollten:



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


Wenn Sie etwas anderes haben, korrigieren Sie es unbedingt!



Wir haben gelernt, wie man den Charakter bewegt, aber wir wissen noch nicht, wie man ihn dreht! Die Drehung des Zeichens erfolgt natürlich mit der Maus. Für die Maus fügen wir zu den Statusvariablen der Drucktasten ... die Statusvariablen für die Mausbewegung hinzu:



//       ?

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


Fügen Sie nach den Push-Release-Handlern den Motion-Handler ein:



//   

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


Fügen Sie der Aktualisierungsfunktion eine Rotation hinzu:



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


Beachten Sie, dass durch Bewegen der Maus entlang der y-Achse der Bauer entlang der x-Achse gedreht wird und umgekehrt. Wenn wir uns das Ergebnis ansehen, werden wir entsetzt sein über das, was wir gesehen haben. Der Punkt ist, dass wenn es keinen Versatz gibt, MouseX und MouseY gleich bleiben und nicht gleich Null sind. Dies bedeutet, dass nach jeder Iteration der Aktualisierung die Offsets von Mischa auf Null zurückgesetzt werden sollten:



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


Noch besser, wir haben die Rotationsträgheit beseitigt, aber die Rotation ist immer noch seltsam! Um zu verstehen, was los ist, fügen wir das "Bauer" -Div in den "Container" ein:



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


Lassen Sie es uns in style.css stylen:



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


Lassen Sie uns das Ergebnis überprüfen. Jetzt ist alles glatt! Das einzige ist, dass das blaue Quadrat vorne bleibt, aber lassen wir das jetzt. Um das Spiel in der ersten und nicht in der dritten Person zu machen, müssen Sie uns die Welt durch einen perspektivischen Wert näher bringen. Machen wir es in script.js in der Funktion update ():



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


Jetzt können Sie das Spiel von der ersten Person aus machen. Blenden Sie den Bauern aus, indem Sie style.css eine Linie hinzufügen:



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


Ausgezeichnet. Ich muss sofort sagen, dass es extrem schwierig ist, in einer Welt mit einem Quadrat zu navigieren, also werden wir eine Site erstellen. Fügen wir der "Welt" den Block "square2" hinzu:



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


Fügen Sie in style.css Stile hinzu:



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


Jetzt ist alles klar. Nicht ganz. Wenn wir die Tasten drücken, bewegen wir uns streng entlang der X- und Z-Achse. Und wir möchten die Bewegung in Richtung der Ansicht ausführen. Gehen wir wie folgt vor: Fügen Sie ganz am Anfang der Datei script.js zwei Variablen hinzu:



//  

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


Ein Grad ist pi / 180 eines Bogenmaßes. Wir müssen Sinus und Cosinus anwenden, die aus dem Bogenmaß berechnet werden. Was soll getan werden? Schauen Sie sich das Bild an:







Wenn unser Blick auf einen Winkel gerichtet ist und wir vorwärts gehen möchten, ändern sich beide Koordinaten: X und Z. Wenn wir uns zur Seite bewegen, tauschen die trigonometrischen Funktionen einfach die Plätze und das Vorzeichen vor dem resultierenden Sinus ändert sich. Lassen Sie uns die Offset-Gleichungen in update () ändern:



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


Überprüfen Sie alle Dateien sorgfältig! Wenn sich herausstellt, dass etwas mit Ihnen nicht stimmt, wird es definitiv Fehler geben, die Ihnen den Kopf brechen!



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


Wir haben die Bewegung fast herausgefunden. Es gab jedoch eine Unannehmlichkeit: Der Mauszeiger kann sich nur innerhalb des Bildschirms bewegen. Bei dreidimensionalen Schützen können Sie die Maus so lange und so weit drehen, wie Sie möchten. Lassen Sie uns auch Folgendes tun: Wenn wir auf den Spielbildschirm (auf "Container") klicken, verschwindet der Cursor und wir können die Maus ohne Einschränkungen der Bildschirmgröße drehen. Wir aktivieren die Erfassung der Maus beim Klicken auf den Bildschirm, für die wir einen Handler zum Klicken der Maus auf "Container" vor den Tastenanschlag-Handlern platzieren:



//     container

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

//    

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


Jetzt ist es eine ganz andere Sache. Im Allgemeinen ist es jedoch am besten, die Drehung nur dann durchzuführen, wenn der Cursor erfasst wird. Lassen Sie uns nach der Presse eine neue Variable einführen ...



//    ?

var lock = false;


Fügen wir einen Handler zum Ändern des Status der Cursorerfassung (erfasst oder nicht erfasst) vor dem Cursorerfassungshandler hinzu (Entschuldigung für die Tautologie):



//     

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


Und in update () fügen Sie die Rotationsbedingung "Bauer" hinzu:



//   ,  

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


Das Erfassen der Maus selbst beim Klicken auf den Container ist nur zulässig, wenn der Cursor noch nicht erfasst wurde:



//    

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


Wir haben uns vollständig mit der Bewegung befasst. Fahren wir fort, um die Welt zu generieren



4. Laden der Karte



In unserem Fall wird die Welt am bequemsten als eine Reihe von Rechtecken mit unterschiedlichen Positionen, Rotationen, Größen und Farben dargestellt. Anstelle von Farbe können auch Texturen verwendet werden. Tatsächlich sind alle modernen 3D-Welten in Spielen eine Sammlung von Dreiecken und Rechtecken, die als Polygone bezeichnet werden. In coolen Spielen kann ihre Anzahl in nur einem Frame Zehntausende erreichen. Wir werden ungefähr hundert davon haben, da der Browser selbst eine geringe Grafikleistung aufweist. In den vorherigen Absätzen haben wir "div" -Blöcke in die "Welt" eingefügt. Wenn es jedoch viele solcher Blöcke (Hunderte) gibt, ist das Einsetzen jedes einzelnen in den Behälter sehr mühsam. Und es kann viele Ebenen geben. Lassen Sie also JavaScript diese Rechtecke einfügen, nicht uns. Wir werden ein spezielles Array dafür erstellen.



Öffnen wir index.html und entfernen Sie alle internen Blöcke aus dem "Welt" -Block:



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


Wie Sie sehen können, gibt es jetzt nichts in der "Welt". Entfernen Sie in style.css die Stile für # square1 und # square2 (entfernen Sie # square1 und # square2 insgesamt aus dieser Datei) und erstellen Sie stattdessen Stile für die .square-Klasse, die allen Rechtecken gemeinsam sind. Und wir werden nur eine Eigenschaft dafür festlegen:




.square{
	position:absolute;
}


Erstellen wir nun ein Array von Rechtecken (zum Beispiel platzieren wir es zwischen dem Player-Konstruktor und den Press-Variablen ... in 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"]
]


Es war möglich, dies in Form eines Konstruktors zu tun, aber im Moment werden wir mit einem reinen Array auskommen, da es einfacher ist, den Zyklus zum Anordnen von Rechtecken durch Arrays und nicht durch Konstruktoren zu starten. Ich werde erklären, was die Zahlen darin bedeuten. Das Kartenarray enthält eindimensionale Arrays mit 9 Variablen: [,,,,,,,,]. Ich denke, Sie verstehen, dass die ersten drei Zahlen die Koordinaten der Mitte des Rechtecks ​​sind, die zweiten drei Zahlen die Drehwinkel in Grad (relativ zur gleichen Mitte), dann zwei Zahlen die Abmessungen und die letzte Zahl der Hintergrund. Darüber hinaus kann der Hintergrund einfarbig, ein Farbverlauf oder ein Foto sein. Letzteres ist sehr praktisch als Texturen zu verwenden.



Wir haben das Array geschrieben, jetzt schreiben wir eine Funktion, die dieses Array in die tatsächlichen Rechtecke umwandelt:



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


Lassen Sie mich erklären, was passiert: Wir erstellen eine neue Variable, die auf das gerade erstellte Element verweist. Wir weisen ihm eine ID- und eine CSS-Klasse zu (dies ist das, was wir mit der Wortklasse in JavaScript meinen), legen die Breite mit Höhe, Hintergrund und Transformation fest. Es ist bemerkenswert, dass wir bei der Transformation zusätzlich zu den Koordinaten des Mittelpunkts des Rechtecks ​​einen Versatz von 600 und 400 und die Hälfte der Abmessungen angeben, sodass der Mittelpunkt des Rechtecks ​​genau am Punkt mit den gewünschten Koordinaten liegt. Lassen Sie uns den Weltgenerator vor dem Timer starten:



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


Wir sehen jetzt einen Bereich mit rosa Wänden und einem grauen Boden. Wie Sie sehen, ist das Erstellen einer Karte technisch nicht schwierig zu implementieren. Daher sollte Ihr Code in drei Dateien folgendermaßen aussehen:



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


Wenn alles in Ordnung ist, fahren Sie mit dem nächsten Punkt fort.



5. Spielerkollisionen mit Weltobjekten



Wir haben eine Bewegungstechnik geschaffen, einen Generator der Welt aus einem Array. Wir können uns in einer Welt bewegen, die schön sein kann. Unser Spieler interagiert jedoch noch nicht mit ihm. Damit diese Interaktion stattfinden kann, müssen wir überprüfen, ob der Player mit einem Rechteck kollidiert oder nicht. Das heißt, wir werden nach Kollisionen suchen. Fügen wir zunächst eine leere Funktion ein:



function collision(){
	
}


Und wir werden es in update () aufrufen:



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


Wie kommt es dazu? Stellen wir uns vor, der Spieler ist ein Ball mit dem Radius r. Und es bewegt sich in Richtung des Rechtecks:







Wenn der Abstand von der Kugel zur Ebene des Rechtecks ​​größer als r ist, tritt die Kollision definitiv nicht auf. Um diesen Abstand herauszufinden, können Sie die Koordinaten des Spielers in das Koordinatensystem des Rechtecks ​​übersetzen. Schreiben wir die Funktion der Übertragung vom Weltsystem zum Rechtecksystem:



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


Und die Umkehrfunktion:



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


Fügen wir diese Funktionen nach der Funktion update () ein. Ich werde nicht erklären, wie es funktioniert, weil ich keine Lust habe, einen Kurs in analytischer Geometrie zu geben. Ich werde sagen, dass es solche Formeln für die Übersetzung von Koordinaten während der Rotation gibt und wir sie nur verwendet haben. Aus der Sicht des Rechtecks ​​ist unser Player folgendermaßen positioniert:







In diesem Fall lautet die Kollisionsbedingung wie folgt: Wenn nach dem Verschieben der Kugel um den Wert v (v ist ein Vektor) die z-Koordinate zwischen –r und r liegt und die x- und y-Koordinaten innerhalb des Rechtecks ​​liegen oder durch einen Betrag getrennt sind, der nicht größer als r ist, dann Eine Kollision wird deklariert. In diesem Fall ist die z-Koordinate des Spielers nach dem Versatz r oder - r (abhängig davon, von welcher Seite der Spieler kommt). Dementsprechend wird der Versatz des Spielers geändert. Wir rufen die Kollision absichtlich auf, bevor wir () die Spielerkoordinaten aktualisieren, um den Versatz zeitlich zu ändern. Somit schneidet sich der Ball niemals mit dem Rechteck, wie dies bei anderen Kollisionsalgorithmen der Fall ist. Obwohl der Spieler physisch eher ein Würfel sein wird, werden wir dies nicht beachten. Implementieren wir dies also in JavaScript:



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 und z0 sind die Anfangskoordinaten des Spielers im Rechteckkoordinatensystem (keine Rotationen. x1, y1 und z1 sind die Koordinaten des Spielers nach Verschiebung ohne Kollision. Punkt 0, Punkt 0, Punkt 1 und Punkt 2 sind der Anfangsradiusvektor, Radiusvektor nach Verschiebung ohne Kollisionen und Radiusvektor mit Kollisionen. map [i] [3] und andere, wenn Sie sich erinnern, sind dies die Drehwinkel des Rechtecks. Beachten Sie, dass wir unter der Bedingung nicht 100 zur Größe des Rechtecks ​​hinzufügen, sondern 98. Dies ist eine Krücke, warum, denken Sie Starten Sie das Spiel und Sie sollten einige Kollisionen von ziemlich hoher Qualität sehen.



Wie Sie sehen können, finden alle diese Aktionen in der for-Schleife für alle Rechtecke statt. Bei einer großen Anzahl von ihnen wird eine solche Operation sehr teuer, da bereits 3 Aufrufe der Koordinatentransformationsfunktionen vorliegen, die auch viele mathematische Operationen ausführen. Wenn die Rechtecke sehr weit vom Spieler entfernt sind, macht es natürlich keinen Sinn, die Kollision zu zählen. Fügen wir diese Bedingung hinzu:




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


Wir haben uns also mit Kollisionen befasst. Wir können leicht auf geneigten Oberflächen klettern, und das Auftreten von Fehlern ist nur auf langsamen Systemen möglich, wenn dies natürlich möglich ist. Tatsächlich endete der gesamte technische Hauptteil dort. Wir müssen nur private Dinge wie Schwerkraft, Dinge, Menüs, Sounds und schöne Grafiken hinzufügen. Aber das ist einfach genug und hat nichts mit dem Motor zu tun, den wir gerade hergestellt haben. Deshalb werde ich im nächsten Teil darüber sprechen . Überprüfen Sie nun, was Sie mit meinem Code erhalten haben:



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