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

La technologie informatique moderne vous permet de créer des jeux informatiques sympas! Et maintenant, les jeux avec des graphismes 3D sont assez populaires, car en y jouant, vous plongez dans un monde fictif et perdez tout lien avec la réalité. Le développement d'Internet et des technologies de navigation a permis d'exécuter des puzzles et des jeux de tir dans votre Chrome, Mozilla ou autre chose préféré (gardons le silence sur l'explorateur) en ligne, sans téléchargement. Donc, ici, je vais vous dire comment créer un simple jeu par navigateur en trois dimensions.



Le choix du genre, de l'intrigue et du style du jeu est une tâche assez intéressante, et le succès du jeu peut dépendre de la solution de ces problèmes. De plus, le choix de la technologie sur la base de laquelle le produit sera créé apporte également ses propres nuances. Mon objectif est de montrer les bases de ce processus amusant, je vais donc créer un labyrinthe en 3 dimensions avec un design simple. De plus, je le ferai en code pur sans utiliser de bibliothèques et de moteurs, tels que three.js (bien qu'il soit préférable de faire de gros projets dessus) pour montrer comment vous pouvez créer un moteur pour vos besoins. Un jeu complètement auto-écrit peut être original et donc intéressant. En général, les deux approches ont leurs avantages et leurs inconvénients.



Je suppose que si vous lisez cet article, le sujet de la création de jeux pour Google Chrome vous intéresse, ce qui signifie que vous comprenez comment le bundle html-css-javaScript fonctionne, donc je ne m'attarderai pas sur les bases, mais je commencerai immédiatement à développer. En html5 et css3, qui sont pris en charge par tous les navigateurs modernes (l'explorateur ne compte pas), il est possible d'organiser des blocs dans un espace en 3 dimensions. Il existe également un élément dans lequel vous pouvez dessiner des lignes et des primitives graphiques. La plupart des moteurs de navigateur utilisent <canvas> parce que plus de choses peuvent être faites dessus et que les performances sont meilleures. Mais pour des choses simples, il est parfaitement possible d'utiliser des méthodes transform-3d, qui prendront moins de code.



1. Outils de développement



Je n'utilise que 2 navigateurs pour consulter les sites et les jeux: Chrome et Mozilla. Tous les autres navigateurs (à l'exception de l'Explorateur lui-même) sont construits sur le premier moteur, donc je ne vois pas l'intérêt de les utiliser, car les résultats sont exactement les mêmes que dans Chrome. Notepad ++ suffit pour écrire du code.



2. Comment l'espace 3D est-il implémenté en html?



Regardons le système de coordonnées du bloc:







Par défaut, le bloc enfant a des coordonnées (gauche et haut) 0 pixels en x et 0 pixels en y. Décalage (translation), également 0 pixels sur les trois axes. Montrons cela avec un exemple, pour lequel nous allons créer un nouveau dossier. Dans celui-ci, nous allons créer les fichiers index.html, style.css et script.js. Ouvrons index.html et écrivons ce qui suit:



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


Dans le fichier style.css, définissons les styles des éléments «container» et «world».



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


Sauvons. En ouvrant index.html avec Chrome, on obtient:







Essayons d'appliquer translate3d à l'élément "world":



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






Comme vous l'avez compris, je suis passé en mode plein écran.

Définissons maintenant le décalage Z: transform: translate3d (200px, 100px, -1000px);



Si vous ouvrez à nouveau le fichier html dans le navigateur, vous ne verrez aucun changement. Pour voir les modifications, vous devez définir la perspective de l'objet "conteneur":



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


Résultat: la







place s'est éloignée de nous. Comment fonctionne la perspective en HTML? Jetons un œil à l'image:







d est la distance de l'utilisateur à l'objet, et z est sa coordonnée. Un z négatif (en html, c'est translateZ) signifie que nous avons éloigné l'objet, et un z positif est le contraire. La valeur de perspective détermine la valeur de d. Si la propriété de perspective n'est pas définie, la valeur de d est supposée être l'infini et, dans ce cas, l'objet ne change pas visuellement pour l'utilisateur avec un changement de z. Dans notre cas, nous définissons d = 600px. Par défaut, le point de regard en perspective est au centre de l'élément, mais il peut être modifié en définissant la propriété perspective-origin:.



Maintenant, tournons le "monde" autour d'un axe. Il existe 2 façons de faire une rotation en CSS. Le premier est la rotation autour des axes x, y et z. Pour ce faire, utilisez les propriétés de transformation rotateX (), rotateY () et rotateZ (). La seconde est la rotation autour d'un axe donné à l'aide de la propriété rotate3d (). Nous utiliserons la première méthode, car elle est plus adaptée à nos tâches. Notez que les axes de rotation sortent du centre du rectangle!







Le point auquel les transformations se produisent peut être modifié en définissant la propriété translate-origin:. Alors, définissons la rotation du "monde" le long de l'axe des x:



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


Nous obtenons: Un







décalage notable dans le sens antihoraire. Si nous ajoutons rotateY (), nous obtiendrons un décalage le long de l'axe Y. Il est important de noter que lorsque le bloc est tourné, les axes de rotation tournent également. Vous pouvez également tester différentes valeurs de rotation.

Maintenant, à l'intérieur du bloc "world", nous allons créer un autre bloc, pour cela nous ajoutons une balise au fichier 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>


Ajoutez des styles à ce bloc dans style.css:



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


Nous obtenons:







c'est-à-dire que les éléments à l'intérieur du bloc «monde» seront transformés dans le cadre de ce bloc. Essayons de faire pivoter «square1» le long de l'axe y en y ajoutant un style de rotation:

transform: rotateY (30deg);



À la fin:







"Où est la rotation?" - tu demandes? En fait, c'est exactement à quoi ressemble la projection du bloc «carré1» sur le plan formé par l'élément «monde». Mais nous n'avons pas besoin d'une projection, mais d'une vraie rotation. Pour rendre tous les éléments à l'intérieur du «monde» en trois dimensions, vous devez lui appliquer la propriété transform-style: preserve-3d. Après avoir remplacé la propriété dans la liste des styles "univers", vérifiez les modifications:







Excellent! La moitié du bloc «carré» est cachée derrière le bloc bleu. Pour le montrer complètement, supprimons la couleur du bloc "monde", à savoir, supprimons la ligne de couleur d'arrière-plan: # C0FFFF; Si nous ajoutons plus de rectangles à l'intérieur du bloc «monde», nous pouvons créer un monde 3D. Supprimons maintenant le décalage «monde» en supprimant la ligne de propriété de transformation dans les styles de cet élément.



3. Créez du mouvement dans un monde en trois dimensions



Pour que l'utilisateur puisse se déplacer dans ce monde, vous devez définir des gestionnaires pour les frappes et les mouvements de la souris. Les commandes seront standard, ce qui est présent dans la plupart des tireurs 3D. Avec les touches W, S, A, D, nous allons avancer, reculer, gauche, droite, avec la barre d'espace, nous allons sauter (en d'autres termes, déplacer vers le haut), et avec la souris nous changerons la direction de la vue. Pour ce faire, ouvrez le fichier script.js, qui est toujours vide. Tout d'abord, ajoutons les variables suivantes ici:



//   ?

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


Aucune touche n'a été pressée au départ. Si nous appuyons sur une touche, la valeur d'une certaine variable passera à 1. Si nous la relâchons, elle deviendra 0. Nous l'implémenterons en ajoutant des gestionnaires pour appuyer et relâcher les touches:



//   

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


Le numéro 32 est un code d'espace. Comme vous pouvez le voir, il existe une variable onGround qui indique si nous sommes sur le terrain. Pour l'instant, autorisons le mouvement ascendant en ajoutant la variable onGround après les variables press ...:



//    ?

var onGround = true;


Nous avons donc ajouté un algorithme push and pull. Nous devons maintenant ajouter le mouvement lui-même. Que, en fait, nous bougeons. Imaginons que nous ayons un objet que nous déplaçons. Appelons cela «pion». Comme d'habitude pour les développeurs normaux, nous allons créer une classe «Player» distincte pour cela. Les classes en javaScript sont créées, assez curieusement, à l'aide des fonctions:



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


Collons ce code dans script.js au tout début du fichier. À la fin du fichier, créons un objet de ce type:



//   

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


Écrivons ce que signifient ces variables. x, y, z sont les coordonnées initiales du joueur, rx, ry sont les angles de sa rotation par rapport aux axes x et y en degrés. La dernière ligne écrite signifie que nous créons un objet "pion" de type "player" (j'écris un type spécifiquement, pas une classe, puisque les classes en javascript signifient quelques autres choses) avec des coordonnées de départ nulles. Lorsque nous déplaçons l'objet, la coordonnée mondiale ne doit pas changer, mais la coordonnée «pion» doit changer. C'est en termes de variables. Et du point de vue de l'utilisateur, le joueur est au même endroit, mais le monde bouge. Ainsi, vous devez forcer le programme à changer les coordonnées du joueur, gérer ces changements et, à la fin, déplacer le monde. En fait, c'est plus facile qu'il n'y paraît.



Ainsi, après avoir chargé le document dans le navigateur, nous exécuterons une fonction qui redessine le monde. Écrivons une fonction de redraw:



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


Dans les nouveaux navigateurs, world correspondra à l'élément avec id = "world", mais il est plus sûr de l'attribuer avant la fonction update () en utilisant la construction suivante:



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


Nous changerons la position du monde toutes les 10 ms (100 mises à jour par seconde), pour lesquelles nous commencerons une boucle infinie:



TimerGame = setInterval(update,10);


Commençons le jeu. Hourra, maintenant nous pouvons bouger! Cependant, le monde sort des limites de l'élément "container". Pour éviter que cela ne se produise, définissons une propriété css dans style.css. Ajoutez le dépassement de ligne: caché; et voyez les changements. Le monde reste maintenant dans le conteneur.



Il est possible que vous ne compreniez pas toujours où vous devez écrire certaines lignes de code, alors maintenant je vais vous présenter les fichiers que, je crois, vous devriez obtenir:



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


Si vous avez quelque chose de différent, assurez-vous de le corriger!



Nous avons appris à déplacer le personnage, mais nous ne savons pas encore comment le faire pivoter! La rotation du personnage, bien sûr, se fera avec la souris. Pour la souris, aux variables d'état des touches press ..., on ajoute les variables d'état du mouvement de la souris:



//       ?

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


Et après les gestionnaires push-release, insérez le gestionnaire de mouvement:



//   

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


Ajoutez une rotation à la fonction de mise à jour:



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


Notez que déplacer la souris le long de l'axe y fait pivoter le pion le long de l'axe x et vice versa. Si nous regardons le résultat, nous serons horrifiés par ce que nous avons vu. Le fait est que s'il n'y a pas de décalage, alors MouseX et MouseY restent les mêmes et ne sont pas égaux à zéro. Cela signifie qu'après chaque itération de mise à jour, les offsets de misha doivent être remis à zéro:



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


Mieux encore, on s'est débarrassé de l'inertie de rotation, mais la rotation est encore étrange! Pour comprendre ce qui se passe, ajoutons le div "pion" dans le "conteneur":



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


Stylisons-le dans style.css:



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


Vérifions le résultat. Maintenant, tout est lisse! La seule chose est que le carré bleu reste devant, mais pour l'instant laissons ça. Pour faire le jeu à la première personne, et non à la troisième, il faut rapprocher le monde de nous par une valeur de perspective. Faisons-le dans script.js dans la fonction update ():



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


Vous pouvez maintenant créer le jeu à la première personne. Cachez le pion en ajoutant une ligne à style.css:



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


Excellent. Je dois dire tout de suite qu'il est extrêmement difficile de naviguer dans un monde avec un seul carré, nous allons donc créer un site. Ajoutons le bloc "square2" au "monde":



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


Et dans style.css, ajoutez des styles pour cela:



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


Maintenant, tout est clair. Enfin, pas tout à fait. Lorsque nous appuyons sur les touches, nous nous déplaçons strictement le long des axes X et Z. Et nous voulons faire le mouvement dans le sens de la vue. Faisons ce qui suit: au tout début du fichier script.js, ajoutez 2 variables:



//  

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


Un degré équivaut à pi / 180 de radian. Nous devrons appliquer des sinus et des cosinus, qui sont calculés à partir des radians. Qu'est-ce qui devrait être fait? Jetez un œil à l'image:







lorsque notre regard est orienté vers un angle et que nous voulons avancer, les deux coordonnées changeront: X et Z.Si nous nous déplaçons sur le côté, les fonctions trigonométriques changeront simplement de place et le signe devant le sinus résultant changera. Modifions les équations de décalage dans 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;


Examinez attentivement tous les fichiers! Si quelque chose ne va pas avec vous, il y aura certainement des erreurs qui vous casseront la tête!



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


Nous avons presque compris le mouvement. Mais il y avait un inconvénient: le curseur de la souris ne peut se déplacer que dans l'écran. Dans les tireurs en trois dimensions, vous pouvez faire pivoter la souris aussi longtemps et aussi loin que vous le souhaitez. Faisons aussi: lorsque nous cliquons sur l'écran de jeu (sur «conteneur»), le curseur disparaîtra, et nous pourrons faire pivoter la souris sans restrictions sur la taille de l'écran. On active la capture de la souris en cliquant sur l'écran, pour laquelle on place un handler pour cliquer la souris sur «container» devant les handlers de frappe:



//     container

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

//    

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


Maintenant, c'est une question complètement différente. Cependant, il est généralement préférable d'effectuer la rotation uniquement lorsque le curseur est capturé. Introduisons une nouvelle variable après la presse ...



//    ?

var lock = false;


Ajoutons un gestionnaire pour changer l'état de la capture du curseur (capturé ou non) avant le gestionnaire de capture du curseur (désolé pour la tautologie):



//     

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


Et dans update (), ajoutez la condition de rotation «pion»:



//   ,  

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


Et la capture de la souris elle-même en cliquant sur le conteneur n'est autorisée que lorsque le curseur n'a pas encore été capturé:



//    

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


Nous avons complètement traité le mouvement. Passons à la génération du monde



4. Chargement de la carte



Le monde dans notre cas est le plus commodément représenté sous la forme d'un ensemble de rectangles avec différents emplacements, rotation, tailles et couleurs. Les textures peuvent également être utilisées à la place de la couleur. En fait, tous les mondes 3D modernes dans les jeux sont une collection de triangles et de rectangles appelés polygones. Dans les jeux sympas, leur nombre peut atteindre des dizaines de milliers en une seule image. Nous en aurons une centaine, car le navigateur lui-même a de faibles performances graphiques. Dans les paragraphes précédents, nous avons inséré des blocs «div» à l'intérieur du «monde». Mais s'il y a beaucoup de tels blocs (des centaines), alors insérer chacun d'eux dans le conteneur est très fastidieux. Et il peut y avoir plusieurs niveaux. Alors laissez javaScript insérer ces rectangles, pas nous. Nous allons créer un tableau spécial pour cela.



Ouvrons index.html et supprimons tous les blocs internes du bloc "world":



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


Comme vous pouvez le voir, il n'y a plus rien dans le «monde» maintenant. Dans style.css, supprimez les styles pour # square1 et # square2 (supprimez complètement # square1 et # square2 de ce fichier), et créez à la place des styles pour la classe .square, qui sera commune à tous les rectangles. Et nous ne définirons qu'une seule propriété pour cela:




.square{
	position:absolute;
}


Créons maintenant un tableau de rectangles (par exemple, nous allons le pousser entre le constructeur du player et les variables press ... dans 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"]
]


Il était possible de le faire sous la forme d'un constructeur, mais pour l'instant nous allons gérer avec un tableau purement, car il est plus facile de démarrer le cycle de disposition des rectangles à travers des tableaux, et non à travers des constructeurs. Je vais vous expliquer ce que signifient les chiffres. Le tableau de carte contient des tableaux unidimensionnels de 9 variables: [,,,,,,,,]. Je pense que vous comprenez que les trois premiers chiffres sont les coordonnées du centre du rectangle, les trois seconds sont les angles de rotation en degrés (par rapport au même centre), puis deux chiffres sont ses dimensions et le dernier chiffre est l'arrière-plan. De plus, le fond peut être une couleur unie, un dégradé ou une photographie. Ce dernier est très pratique à utiliser comme textures.



Nous avons écrit le tableau, maintenant nous allons écrire une fonction qui transformera ce tableau en rectangles réels:



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


Laissez-moi vous expliquer ce qui se passe: nous créons une nouvelle variable qui pointe vers l'élément que nous venons de créer. Nous lui attribuons un identifiant et une classe css (c'est ce que nous entendons par le mot classe en javaScript), définissons la largeur avec la hauteur, l'arrière-plan et la transformation. Il est à noter que dans la transformation, en plus des coordonnées du centre du rectangle, nous spécifions un décalage de 600 et 400 et la moitié des dimensions afin que le centre du rectangle soit exactement au point avec les coordonnées souhaitées. Commençons le générateur de monde devant le minuteur:



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


Nous voyons maintenant une zone avec des murs roses et un sol gris. Comme vous pouvez le voir, la création d'une carte n'est pas techniquement difficile à mettre en œuvre. En conséquence, votre code dans trois fichiers devrait ressembler à ceci:



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


Si tout va bien, passez à l'élément suivant.



5. Collisions entre les joueurs et les objets du monde



Nous avons créé une technique de mouvement, un générateur du monde à partir d'un tableau. Nous pouvons nous déplacer dans un monde qui peut être beau. Cependant, notre joueur n'interagit pas encore avec lui. Pour que cette interaction se produise, nous devons vérifier si le joueur entre en collision avec un rectangle ou non? Autrement dit, nous vérifierons les collisions. Tout d'abord, insérons une fonction vide:



function collision(){
	
}


Et nous l'appellerons dans update ():



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


Comment cela peut-il arriver? Imaginons que le joueur soit une balle de rayon r. Et il se déplace vers le rectangle:







évidemment, si la distance de la balle au plan du rectangle est supérieure à r, alors la collision ne se produit certainement pas. Pour connaître cette distance, vous pouvez traduire les coordonnées du joueur dans le système de coordonnées rectangle. Écrivons la fonction de transfert du système mondial vers le système rectangle:



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


Et la fonction inverse:



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


Insérons ces fonctions après la fonction update (). Je n'expliquerai pas comment cela fonctionne, car je n'ai pas envie de donner un cours de géométrie analytique. Je dirai qu'il existe de telles formules pour la translation des coordonnées lors de la rotation et nous venons de les utiliser. Du point de vue du rectangle, notre joueur est positionné comme ceci:







Dans ce cas, la condition de collision devient la suivante: si, après avoir déplacé la balle de la valeur v (v est un vecteur), la coordonnée z est comprise entre –r et r, et les coordonnées x et y se trouvent à l'intérieur du rectangle ou en sont séparées par une quantité non supérieure à r, une collision est déclarée. Dans ce cas, la coordonnée z du joueur après le décalage sera r ou - r (selon de quel côté le joueur vient). En conséquence, le décalage du joueur est modifié. Nous appelons intentionnellement la collision avant de mettre à jour () les coordonnées du joueur pour changer le décalage dans le temps. Ainsi, la balle ne se croisera jamais avec le rectangle, comme cela se produit dans d'autres algorithmes de collision. Bien que physiquement le joueur ressemble plus à un cube, nous n'y prêterons pas attention. Alors, implémentons ceci en 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 et z0 sont les coordonnées initiales du joueur dans le système de coordonnées rectangle (sans rotations.x1, y1 et z1 sont les coordonnées du joueur après déplacement sans collision.point0, point0, point1 et point2 sont le vecteur rayon initial, vecteur rayon après déplacement sans les collisions et le rayon vecteur avec les collisions, respectivement. map [i] [3] et autres, si vous vous en souvenez, ce sont les angles de rotation du rectangle. Notez que dans la condition nous ajoutons non pas 100 à la taille du rectangle, mais 98. C'est une béquille, pourquoi, pensez Démarrez le jeu et vous devriez voir des collisions de très haute qualité.



Comme vous pouvez le voir, toutes ces actions ont lieu dans la boucle for pour tous les rectangles. Avec un grand nombre d'entre elles, une telle opération devient très coûteuse, puisqu'il y a déjà 3 appels aux fonctions de transformation de coordonnées, qui effectuent également de nombreuses opérations mathématiques. Évidemment, si les rectangles sont très éloignés du joueur, cela n'a aucun sens de compter la collision. Ajoutons cette condition:




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


Donc, nous avons traité des collisions. Nous pouvons facilement grimper sur des surfaces inclinées, et l'apparition de bugs n'est possible que sur des systèmes lents, si, bien sûr, cela est possible. En fait, toute la partie technique principale s'est terminée là. Nous devons juste ajouter des choses privées telles que la gravité, des choses, des menus, des sons, de beaux graphismes. Mais c'est assez facile à faire et cela n'a rien à voir avec le moteur que nous venons de fabriquer. Par conséquent, j'en parlerai dans la partie suivante . Maintenant, vérifiez ce que vous avez avec mon code:



index.html:



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


style.css



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


script.js:



//  

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

//  player

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

//  

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