Creación de juegos 3D basados ​​en navegador desde cero en html puro, css y js. Parte 1/2

¡La tecnología informática moderna te permite crear juegos de computadora geniales! Y ahora, los juegos con gráficos en 3D son bastante populares, ya que al jugarlos, te sumerges en un mundo ficticio y pierdes toda conexión con la realidad. El desarrollo de las tecnologías de Internet y de los navegadores hizo posible ejecutar acertijos y juegos de disparos en su Chrome, Mozilla favorito o algo más (guardemos silencio sobre el Explorer) en línea, sin descargar. Entonces, aquí te diré cómo crear un simple juego de navegador tridimensional.



La elección del género, la trama y el estilo del juego es una tarea bastante interesante, y el éxito del juego puede depender de la solución de estos problemas. Además, la elección de la tecnología sobre la base de la cual se creará el producto también trae sus propios matices. Mi objetivo es mostrar los fundamentos básicos de este divertido proceso, por lo que haré un laberinto tridimensional con un diseño simple. Además, lo haré en código puro sin usar bibliotecas y motores, como three.js (aunque es mejor hacer grandes proyectos en él) para mostrar cómo puedes crear un motor para tus necesidades. Un juego completamente escrito por uno mismo puede ser original y, por tanto, interesante. En general, ambos enfoques tienen sus pros y sus contras.



Supongo que si está leyendo este artículo, entonces está interesado en el tema de la creación de juegos para Google Chrome, lo que significa que comprende cómo funciona el paquete html-css-javaScript, por lo que no me extenderé en lo básico, pero comenzaré a desarrollarlo de inmediato. En html5 y css3, que son compatibles con todos los navegadores modernos (el Explorador no cuenta), es posible organizar bloques en un espacio tridimensional. También hay un elemento en el que se pueden dibujar líneas y primitivas gráficas. La mayoría de los buscadores utilizan <canvas> porque se pueden hacer más cosas con él y el rendimiento es mejor. Pero para cosas simples, es perfectamente posible usar métodos transform-3d, que requerirán menos código.



1. Herramientas de desarrollo



Solo uso 2 navegadores para comprobar sitios y juegos: Chrome y Mozilla. Todos los demás navegadores (excepto el propio Explorer) se basan en el primer motor, por lo que no veo el sentido de usarlos, porque los resultados son exactamente los mismos que en Chrome. Notepad ++ es suficiente para escribir código.



2. ¿Cómo se implementa el espacio 3D en html?



Veamos el sistema de coordenadas del bloque:







por defecto, el bloque hijo tiene coordenadas (izquierda y superior) 0 píxeles en xy 0 píxeles en y. Desplazamiento (traslación), también 0 píxeles en los tres ejes. Demostremos esto con un ejemplo, para el cual crearemos una nueva carpeta. En él, crearemos los archivos index.html, style.css y script.js. Abramos index.html y escribamos lo siguiente allí:



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


En el archivo style.css, establezcamos los estilos para los elementos "contenedor" y "mundo".



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


Salvemos. Al abrir index.html con Chrome, obtenemos:







Intentemos aplicar translate3d al elemento "world":



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






Como comprenderá, pasé al modo de pantalla completa. Ahora establezcamos el desplazamiento Z:

transform: translate3d (200px, 100px, -1000px);



Si vuelve a abrir el archivo html en el navegador, no verá ningún cambio. Para ver los cambios, debe establecer la perspectiva del objeto "contenedor":



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


Como resultado: la







Plaza se ha alejado de nosotros. ¿Cómo funciona la perspectiva en html? Echemos un vistazo a la imagen:







d es la distancia del usuario al objeto y z es su coordenada. Z negativo (en html es translateZ) significa que hemos alejado el objeto, y positivo, viceversa. El valor de perspectiva determina el valor de d. Si no se establece la propiedad de perspectiva, se supone que el valor d es infinito y, en este caso, el objeto no cambia visualmente para el usuario con un cambio en z. En nuestro caso, establecemos d = 600px. De forma predeterminada, el punto de vista en perspectiva está en el centro del elemento, sin embargo, se puede cambiar estableciendo la propiedad perspectiva-origen:.



Ahora rotaremos "mundo" alrededor de algún eje. Hay 2 formas de rotar en CSS. La primera es la rotación alrededor de los ejes x, y y z. Para hacer esto, use las propiedades de transformación rotateX (), rotateY () y rotateZ (). El segundo es la rotación alrededor de un eje dado usando la propiedad rotate3d (). Usaremos el primer método, ya que es más adecuado para nuestras tareas. ¡Tenga en cuenta que los ejes de rotación salen del centro del rectángulo!







El punto en el que ocurren las transformaciones se puede cambiar estableciendo la propiedad translate-origin:. Entonces, establezcamos la rotación de "mundo" a lo largo del eje x:



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


Obtenemos:







Desplazamiento notable en sentido antihorario. Si agregamos rotateY (), obtendremos el desplazamiento a lo largo del eje Y. Es importante tener en cuenta que cuando se gira el bloque, los ejes de rotación también giran. También puede experimentar con diferentes valores de rotación.

Ahora, dentro del bloque "mundo", creemos otro bloque, para esto agregamos una etiqueta al archivo 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>


Agregue estilos a este bloque en style.css:



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


Obtenemos:







Es decir, los elementos dentro del bloque "mundo" se transformarán como parte de este bloque. Intentemos rotar “cuadrado1” a lo largo del eje y añadiéndole un estilo de rotación:

transform: rotateY (30deg);



Al final:







"¿Dónde está la rotación?" - ¿usted pregunta? De hecho, así es como se ve la proyección del bloque “cuadrado1” sobre el plano formado por el elemento “mundo”. Pero no necesitamos una proyección, sino una rotación real. Para hacer que todos los elementos dentro del "mundo" sean volumétricos, necesita aplicarle la propiedad transform-style: preserve-3d. Después de sustituir la propiedad dentro de la lista de estilos "mundial", verifique los cambios:







¡Excelente! La mitad del bloque "cuadrado" está oculta detrás del bloque azul. Para mostrarlo completamente, elimine el color del bloque "mundo", es decir, elimine la línea de color de fondo: # C0FFFF; Si agregamos más rectángulos dentro del bloque "mundo", entonces podemos crear un mundo 3D. Ahora eliminemos el desplazamiento "mundial" eliminando la línea de propiedad de transformación en los estilos de este elemento.



3. Crea movimiento en un mundo tridimensional



Para que el usuario pueda moverse por este mundo, debe definir controladores para las pulsaciones de teclas y los movimientos del mouse. Los controles serán estándar, que está presente en la mayoría de los shooters 3D. Con las teclas W, S, A, D avanzaremos, retrocederemos, izquierda, derecha, con la barra espaciadora saltaremos (es decir, subiremos), y con el mouse cambiaremos la dirección de nuestra mirada. Para hacer esto, abramos un archivo script.js aún vacío. Primero, agreguemos las siguientes variables allí:



//   ?

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


Inicialmente no se presionó ninguna tecla. Si presionamos una tecla, el valor de una determinada variable cambiará a 1. Si la soltamos, volverá a ser 0. Hacemos esto agregando controladores para presionar y soltar teclas:



//   

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


El número 32 es un código de espacio. Como puede ver, hay una variable onGround que indica si estamos en el suelo. Por ahora, permitamos el movimiento ascendente agregando la variable onGround después de presionar ... variables:



//    ?

var onGround = true;


Entonces, hemos agregado un algoritmo de empujar y tirar. Ahora necesitamos agregar el movimiento en sí. Lo que, de hecho, estamos moviendo. Imaginemos que tenemos un objeto que estamos moviendo. Llamémoslo "peón". Como es habitual para los desarrolladores normales, crearemos una clase "Player" separada para él. Las clases en javaScript se crean, por extraño que parezca, usando funciones:



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


Peguemos este código en script.js al principio del archivo. Al final del archivo, creemos un objeto de este tipo:



//   

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


Anotemos lo que significan estas variables. x, y, z son las coordenadas iniciales del jugador, rx, ry son los ángulos de su rotación con respecto a los ejes xey en grados. La última línea escrita significa que creamos un objeto "peón" de tipo "jugador" (estoy escribiendo un tipo específicamente, no una clase, ya que las clases en javascript significan algunas otras cosas) con coordenadas iniciales cero. Cuando movemos el objeto, la coordenada mundial no debe cambiar, pero la coordenada del "peón" debe cambiar. Esto es en términos de variables. Y desde el punto de vista del usuario, el jugador está en un solo lugar, pero el mundo se mueve. Por lo tanto, debe obligar al programa a cambiar las coordenadas del jugador, manejar estos cambios y, al final, mover el mundo. De hecho, esto es más fácil de lo que parece.



Entonces, después de cargar el documento en el navegador, ejecutaremos una función que redibuja el mundo. Escribamos una función de redibujo:



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


En los nuevos navegadores, world hará coincidir el elemento con id = "world", pero es más seguro asignarlo antes de la función update () usando la siguiente construcción:



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


Cambiaremos la posición del mundo cada 10 ms (100 actualizaciones por segundo), para lo cual iniciaremos un bucle infinito:



TimerGame = setInterval(update,10);


Empecemos el juego. ¡Hurra, ahora podemos movernos! Sin embargo, el mundo sale de los límites del elemento "contenedor". Para evitar que esto suceda, establezcamos una propiedad css en style.css. Agregue la línea overflow: hidden; y ver los cambios. El mundo ahora permanece dentro del contenedor.



Es posible que no siempre entiendas dónde necesitas escribir ciertas líneas de código, así que ahora te presentaré los archivos que, creo, deberías obtener:



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 tiene algo diferente, ¡asegúrese de corregirlo!



Aprendimos a mover el personaje, ¡pero todavía no sabemos cómo rotarlo! La rotación del personaje, por supuesto, se hará con el ratón. Para el mouse, agregaremos las variables de estado de movimiento del mouse a las variables de estado de las teclas presionar ...:



//       ?

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


Y después de los controladores de empuje y liberación, inserte el controlador de movimiento:



//   

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


Agregue una rotación a la función de actualización:



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


Tenga en cuenta que al mover el mouse a lo largo del eje y, el peón gira a lo largo del eje x y viceversa. Si miramos el resultado, nos horrorizaremos con lo que vimos. El punto es que si no hay compensación, MouseX y MouseY siguen siendo los mismos, y no iguales a cero. Esto significa que después de cada iteración de actualización, las compensaciones de Misha deben restablecerse a cero:



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


Aún mejor, nos deshicimos de la inercia rotacional, ¡pero la rotación sigue siendo extraña! Para tener una idea de lo que está pasando, agreguemos el div "peón" dentro del "contenedor":



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


Vamos a diseñarlo en style.css:



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


Comprobemos el resultado. ¡Ahora todo está bien! Lo único es que el cuadrado azul se queda al frente, pero por ahora dejemos eso. Para hacer el juego en primera persona, y no en tercera, necesitas acercar el mundo a nosotros mediante un valor de perspectiva. Hagámoslo en script.js en la función update ():



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


Ahora puedes hacer el juego desde la primera persona. Oculte el peón agregando una línea a style.css:



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


Excelente. Debo decir de inmediato que es extremadamente difícil navegar en un mundo con un cuadrado, así que crearemos un sitio. Agreguemos el bloque "cuadrado2" al "mundo":



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


Y en style.css, agregue estilos para él:



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


Ahora todo está claro. Bueno, no del todo. Cuando presionamos las teclas, nos movemos estrictamente a lo largo de los ejes X y Z. Y queremos hacer el movimiento en la dirección de la vista. Hagamos lo siguiente: al principio del archivo script.js, agregue 2 variables:



//  

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


Un grado es pi / 180 de radianes. Tendremos que aplicar senos y cosenos, que se calculan a partir de radianes. ¿Lo que debe hacerse? Eche un vistazo a la imagen:







cuando nuestra mirada se dirige en ángulo y queremos avanzar, ambas coordenadas cambiarán: X y Z.Si nos movemos hacia un lado, las funciones trigonométricas simplemente cambiarán de lugar y el signo frente al seno resultante cambiará. Cambiemos las ecuaciones de compensación en 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;


¡Revise todos los archivos con cuidado! Si algo sale mal para ti, ¡definitivamente habrá errores que te romperán la cabeza!



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


Casi descubrimos el movimiento. Pero hubo un inconveniente: el cursor del mouse solo puede moverse dentro de la pantalla. En los juegos de disparos tridimensionales, puede girar el mouse todo el tiempo que desee. Hagámoslo también: cuando hagamos clic en la pantalla del juego (en "contenedor"), el cursor desaparecerá y podremos rotar el ratón sin restricciones en el tamaño de la pantalla. Activamos la captura del mouse al hacer clic en la pantalla, para lo cual colocamos un manejador para hacer clic con el mouse en “contenedor” frente a los manejadores de pulsaciones de teclas:



//     container

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

//    

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


Ahora es un asunto completamente diferente. Sin embargo, generalmente es mejor hacer que la rotación solo ocurra cuando se captura el cursor. Introduzcamos una nueva variable después de la prensa ...



//    ?

var lock = false;


Agreguemos un controlador para cambiar el estado de la captura del cursor (capturado o no) antes del controlador de captura del cursor (perdón por la tautología):



//     

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


Y en update () agregue la condición de rotación "peón":



//   ,  

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


Y la captura del propio mouse al hacer clic en el contenedor está permitida solo cuando el cursor aún no ha sido capturado:



//    

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


Nos hemos ocupado completamente del movimiento. Pasemos a generar el mundo



4. Cargando el mapa



En nuestro caso, el mundo se representa más convenientemente como un conjunto de rectángulos con diferentes ubicaciones, rotaciones, tamaños y colores. También se pueden utilizar texturas en lugar de color. De hecho, todos los mundos 3D modernos en los juegos son una colección de triángulos y rectángulos llamados polígonos. En juegos geniales, su número puede llegar a decenas de miles en un solo cuadro. Tendremos alrededor de un centenar de ellos, ya que el propio navegador tiene un rendimiento gráfico bajo. En los párrafos anteriores, insertamos bloques "div" dentro del "mundo". Pero si hay muchos de esos bloques (cientos), insertar cada uno de ellos en un contenedor es muy tedioso. Y puede haber muchos niveles. Así que dejemos que javaScript inserte estos rectángulos, no nosotros. Crearemos una matriz especial para ello.



Abramos index.html y eliminemos todos los bloques internos del bloque "mundo":



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


Como puede ver, ahora no hay nada en el "mundo". En style.css, elimine los estilos para # square1 y # square2 (elimine # square1 y # square2 de este archivo por completo) y, en su lugar, cree estilos para la clase .square, que serán comunes a todos los rectángulos. Y estableceremos solo una propiedad para ello:




.square{
	position:absolute;
}


Ahora creemos una matriz de rectángulos (por ejemplo, la empujaremos entre el constructor del reproductor y las variables de prensa ... en 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"]
]


Era posible hacer esto en forma de constructor, pero por ahora lo manejaremos con un arreglo puramente, ya que es más fácil iniciar el ciclo para ordenar rectángulos a través de arreglos, y no a través de constructores. Explicaré lo que significan los números. La matriz de mapas contiene matrices unidimensionales de 9 variables: [,,,,,,,,]. Creo que entiendes que los primeros tres números son las coordenadas del centro del rectángulo, los segundos tres números son los ángulos de rotación en grados (relativos al mismo centro), luego dos números son sus dimensiones y el último número es el fondo. Además, el fondo puede ser un color sólido, un degradado o una fotografía. Este último es muy conveniente para usar como texturas.



Hemos escrito la matriz, ahora escribiremos una función que transformará esta matriz en los rectángulos reales:



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


Permítanme explicar lo que está sucediendo: estamos creando una nueva variable que apunta al elemento que acabamos de crear. Le asignamos un id y una clase css (esto es lo que significa la palabra clase en el lenguaje javaScript), establecemos el ancho con altura, fondo y transformación. Es de destacar que en la transformación, además de las coordenadas del centro del rectángulo, especificamos un desplazamiento de 600 y 400 y la mitad de las dimensiones para que el centro del rectángulo esté exactamente en el punto con las coordenadas deseadas. Iniciemos el generador de mundos frente al temporizador:



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


Ahora vemos un área con paredes rosas y un piso gris. Como puede ver, la creación de un mapa no es técnicamente difícil de implementar. Como resultado, su código en tres archivos debería tener un aspecto similar a



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 todo está bien, pase al siguiente elemento.



5. Colisiones de jugadores con objetos del mundo



Hemos creado una técnica de movimiento, un generador del mundo a partir de una matriz. Podemos movernos por un mundo que puede ser hermoso. Sin embargo, nuestro jugador aún no interactúa con él. Para que ocurra esta interacción, debemos verificar si el jugador choca con algún rectángulo o no. Es decir, buscaremos colisiones. Primero, insertemos una función vacía:



function collision(){
	
}


Y lo llamaremos en update ():



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


¿Como sucedió esto? Imaginemos que el jugador es una pelota de radio r. Y se mueve hacia el rectángulo:







obviamente, si la distancia de la bola al plano del rectángulo es mayor que r, entonces la colisión definitivamente no ocurre. Para encontrar esta distancia, puede traducir las coordenadas del jugador al sistema de coordenadas del rectángulo. Escribamos la función de transferir del sistema mundial al sistema rectangular:



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


Y la función inversa:



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


Insertemos estas funciones después de la función update (). No explicaré cómo funciona, porque no tengo ganas de dar un curso de geometría analítica. Diré que existen tales fórmulas para traducir coordenadas durante la rotación, y las usamos. Desde el punto de vista del rectángulo, nuestro reproductor se posiciona así:







En este caso, la condición de colisión se convierte en la siguiente: si, después de que la bola se desplaza por el valor v (v es un vector), la coordenada z está entre –ryr, y las coordenadas xey están dentro del rectángulo o están separadas de él por una cantidad no mayor que r, entonces se declara una colisión. En este caso, la coordenada z del jugador después del cambio será r o - r (dependiendo de qué lado venga el jugador). En consecuencia, se cambia el desplazamiento del jugador. Específicamente llamamos a la colisión antes de actualizar () las coordenadas del jugador para cambiar el desplazamiento en el tiempo. Por lo tanto, la bola nunca se cruzará con el rectángulo, como sucede en otros algoritmos de colisión. Aunque físicamente el jugador será más probable que sea un cubo, no prestaremos atención a esto. Entonces, implementemos esto 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 y z0 son las coordenadas iniciales del jugador en el sistema de coordenadas del rectángulo (sin rotaciones. x1, y1 y z1 son las coordenadas del jugador después del desplazamiento sin colisión. punto0, punto0, punto1 y punto2 son el vector de radio inicial, vector de radio después del desplazamiento sin colisión). colisiones y vector de radio con colisiones, respectivamente. map [i] [3] y otros, si recuerda, estos son los ángulos de rotación del rectángulo. Tenga en cuenta que en la condición no agregamos 100 al tamaño del rectángulo, sino 98. Esto es una muleta, por qué, piense Inicie el juego y debería ver algunas colisiones de bastante alta calidad.



Como puede ver, todas estas acciones tienen lugar en el ciclo for para todos los rectángulos. Con una gran cantidad de ellos, dicha operación se vuelve muy costosa, ya que ya hay 3 llamadas a las funciones de transformación de coordenadas, que también realizan muchas operaciones matemáticas. Obviamente, si los rectángulos están muy lejos del jugador, entonces no tiene sentido contar la colisión. Agreguemos esta condición:




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


Entonces, nos hemos ocupado de las colisiones. Podemos trepar fácilmente en superficies inclinadas, y la aparición de errores solo es posible en sistemas lentos, si, por supuesto, es posible. De hecho, toda la parte técnica principal terminó allí. Solo tenemos que agregar cosas privadas como gravedad, cosas, menús, sonidos, hermosos gráficos. Pero esto es bastante fácil de hacer y no tiene nada que ver con el motor que acabamos de hacer. Por lo tanto, hablaré de esto en la siguiente parte . Ahora verifique lo que obtuvo con mi código:



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