Creando un juego de corredor sin fin 3D simple usando Three.js
() translation by (you can also view the original English article)



La plataforma
web ha tenido un gran crecimiento en los últimos tiempos con la ayuda de
HTML5, WebGL y el aumento de la potencia de la generación actual de
dispositivos. Ahora los dispositivos móviles y los navegadores son capaces de ofrecer contenido de alto rendimiento tanto en 2D como en 3D. La familiaridad de JavaScript (JS) como lenguaje de scripting también ha sido un factor determinante después de la desaparición de la plataforma web Flash.
La mayoría de los desarrolladores web son conscientes de lo complicado que es el ecosistema de JS con todos los diversos marcos y estándares disponibles, que a veces pueden ser abrumadores para un nuevo desarrollador. Pero cuando se trata de 3D, las opciones son sencillas, gracias a Mr.Doob. Su Three.js es actualmente la mejor opción para crear contenido 3DGL de alto rendimiento. Otra alternativa poderosa es Babylon.js, que también podría usarse para crear juegos en 3D.
En este tutorial, aprenderá a crear un juego de web nativo simple estilo corredor sin fin utilizando el poderoso marco Three.js. Utilizará las teclas de flecha para controlar una bola de nieve rodando por la ladera de una montaña con el fin de esquivar los árboles en su camino. No hay arte involucrado, y todas las imágenes se crean en código.
1. Escena 3D básica
Envato Tuts + ya tiene algunos tutoriales que pueden ayudarlo a comenzar con Three.js. Aquí hay algunos de ellos para que comiences.
- Una guía de Novato para Three.js
- WebGL con Three.js: conceptos básicos
- Three.js para desarrollo de juegos
Primero crearemos una escena 3D básica, como se muestra aquí donde hay un cubo giratorio. Puede usar la función de arrastrar del mouse para orbitar alrededor del cubo.
Cualquier gráfico que se muestre en una pantalla bidimensional es de naturaleza prácticamente 2D, con algunos elementos importantes que proporcionan la ilusión 3D: la iluminación, el sombreado, las sombras y la magia de proyección 3D a 2D que ocurre a través de la cámara. En la escena anterior, habilitamos la iluminación efectiva usando estas líneas de código.
1 |
camera = new THREE.PerspectiveCamera( 60, sceneWidth / sceneHeight, 0.1, 1000 );//perspective camera |
2 |
renderer = new THREE.WebGLRenderer({alpha:true});//renderer with transparent backdrop |
3 |
renderer.shadowMap.enabled = true;//enable shadow |
4 |
renderer.shadowMap.type = THREE.PCFSoftShadowMap; |
5 |
|
6 |
//...
|
7 |
hero = new THREE.Mesh( heroGeometry, heroMaterial ); |
8 |
hero.castShadow=true; |
9 |
hero.receiveShadow=false; |
10 |
//...
|
11 |
ground.receiveShadow = true; |
12 |
ground.castShadow=false; |
13 |
//..
|
14 |
sun = new THREE.DirectionalLight( 0xffffff, 0.8); |
15 |
sun.position.set( 0,4,1 ); |
16 |
sun.castShadow = true; |
17 |
scene.add(sun); |
18 |
//Set up shadow properties for the sun light
|
19 |
sun.shadow.mapSize.width = 256; |
20 |
sun.shadow.mapSize.height = 256; |
21 |
sun.shadow.camera.near = 0.5; |
22 |
sun.shadow.camera.far = 50 ; |
El renderizador
necesita tener shadowMap
habilitado, la escena necesita tener una luz
con castShadow
habilitado, y todos los objetos 3D necesitan las
propiedades castShadow
y receiveShadow
establecidas apropiadamente. Para que se produzca el sombreado adecuado, también deberíamos usar MeshStandardMaterial
o un material más rico en funciones para nuestros objetos 3D. La cámara se controla con el ingenioso script OrbitControls. Recomiendo jugar
con la escena 3D básica agregando formas más primitivas o jugando con
la iluminación, etc., antes de continuar con el tutorial.
2. El concepto de Endless Runner
Hay muchos tipos de juegos de corredor sin fin, y el nuestro es un 'rodillo sin fin'. Crearemos un juego donde una bola de nieve rueda por una ladera interminable donde usamos las teclas de flecha para esquivar los árboles entrantes. Una cosa interesante es que este simple juego no involucrará ningún recurso de arte, ya que todos los componentes se crearán por código. Aquí está el juego completo para jugar.
3. Componentes del juego
Los principales componentes o elementos del juego son:
- la bola de nieve rodando
- los árboles al azar
- el campo de desplazamiento
- la niebla a distancia
- el efecto de colisión
Exploraremos cada uno de estos uno por uno en la siguiente sección.
La niebla
La niebla
es una propiedad de la escena 3D en Tres. Siempre es un truco útil para simular la profundidad o mostrar un horizonte. El color de la niebla es importante para que la ilusión funcione correctamente y depende del color de la escena y la iluminación. Como puede ver en el siguiente código, también configuramos el valor clearColor
del renderizador
para que se acerque al color de la niebla
.
1 |
scene = new THREE.Scene(); |
2 |
scene.fog = new THREE.FogExp2( 0xf0fff0, 0.14 ); |
3 |
camera = new THREE.PerspectiveCamera( 60, sceneWidth / sceneHeight, 0.1, 1000 );//perspective camera |
4 |
renderer = new THREE.WebGLRenderer({alpha:true});//renderer with transparent backdrop |
5 |
renderer.setClearColor(0xfffafa, 1); |
Para que coincida con el ambiente, también estamos utilizando valores de color similares a las luces utilizadas en la escena. Cada color ambiental es un tono diferente de blanco que se gelifica para crear el efecto necesario.
1 |
var hemisphereLight = new THREE.HemisphereLight(0xfffafa,0x000000, .9) |
2 |
scene.add(hemisphereLight); |
3 |
sun = new THREE.DirectionalLight( 0xcdc1c5, 0.9); |
4 |
sun.position.set( 12,6,-7 ); |
5 |
sun.castShadow = true; |
6 |
scene.add(sun); |
La bola de nieve
Nuestra bola de nieve es una forma primitiva DodecahedronGeometry
creada como se muestra a continuación.
1 |
var sphereGeometry = new THREE.DodecahedronGeometry( heroRadius, 1); |
2 |
var sphereMaterial = new THREE.MeshStandardMaterial( { color: 0xe5f2f2 ,shading:THREE.FlatShading} ) |
3 |
heroSphere = new THREE.Mesh( sphereGeometry, sphereMaterial ); |
Para todos los elementos 3D de este juego, estamos usando THREE.FlatShading
para obtener el aspecto deseado de baja poli.
La montaña de desplazamiento
El campo de desplazamiento llamado rollingGroundSphere
es una gran primitiva de SphereGeometry
, y lo rotamos en el eje x
para crear la ilusión de suelo en movimiento. La bola de nieve realmente no rueda sobre nada; solo estamos creando la ilusión manteniendo la esfera terrestre rodando mientras mantenemos la bola de nieve inmóvil.
Una esfera primitiva normal se verá muy suave y, por lo tanto, no proporcionará la robustez necesaria para la pendiente de la montaña. Así que hacemos algunas manipulaciones de vértices para cambiar la superficie lisa de la esfera en un terreno accidentado. Aquí está el código correspondiente seguido de una explicación.
1 |
var sides=40; |
2 |
var tiers=40; |
3 |
var sphereGeometry = new THREE.SphereGeometry( worldRadius, sides,tiers); |
4 |
var sphereMaterial = new THREE.MeshStandardMaterial( { color: 0xfffafa ,shading:THREE.FlatShading} ) |
5 |
var vertexIndex; |
6 |
var vertexVector= new THREE.Vector3(); |
7 |
var nextVertexVector= new THREE.Vector3(); |
8 |
var firstVertexVector= new THREE.Vector3(); |
9 |
var offset= new THREE.Vector3(); |
10 |
var currentTier=1; |
11 |
var lerpValue=0.5; |
12 |
var heightValue; |
13 |
var maxHeight=0.07; |
14 |
for(var j=1;j<tiers-2;j++){ |
15 |
currentTier=j; |
16 |
for(var i=0;i<sides;i++){ |
17 |
vertexIndex=(currentTier*sides)+1; |
18 |
vertexVector=sphereGeometry.vertices[i+vertexIndex].clone(); |
19 |
if(j%2!==0){ |
20 |
if(i===0){ |
21 |
firstVertexVector=vertexVector.clone(); |
22 |
}
|
23 |
nextVertexVector=sphereGeometry.vertices[i+vertexIndex+1].clone(); |
24 |
if(i==sides-1){ |
25 |
nextVertexVector=firstVertexVector; |
26 |
}
|
27 |
lerpValue=(Math.random()*(0.75-0.25))+0.25; |
28 |
vertexVector.lerp(nextVertexVector,lerpValue); |
29 |
}
|
30 |
heightValue=(Math.random()*maxHeight)-(maxHeight/2); |
31 |
offset=vertexVector.clone().normalize().multiplyScalar(heightValue); |
32 |
sphereGeometry.vertices[i+vertexIndex]=(vertexVector.add(offset)); |
33 |
}
|
34 |
}
|
35 |
rollingGroundSphere = new THREE.Mesh( sphereGeometry, sphereMaterial ); |
Estamos creando una esfera primitiva con 40 segmentos horizontales (lados
) y 40 segmentos verticales (niveles
). Se puede acceder a cada vértice de una geometría de tres a través de la propiedad de matriz vértices
. Recorrimos todos los niveles entre los vértices extremos superior e inferior para hacer nuestras manipulaciones de vértices. Cada nivel de la geometría de la esfera contiene exactamente el mismo número sides
de vértices, que forma un anillo cerrado alrededor de la esfera.
El primer paso es rotar cada anillo impar de vértices para romper la uniformidad de los contornos de la superficie. Movemos cada vértice en el anillo por una fracción aleatoria entre 0.25 y 0.75 de la distancia al siguiente vértice. Como resultado de esto, los vértices verticales de la esfera ya no están alineados en línea recta, y obtenemos un bonito contorno en zigzag.
Como segundo
paso, proporcionamos a cada vértice un ajuste de altura aleatorio
alineado con el normal en el vértice, independientemente del nivel al
que pertenece. Esto da como resultado una superficie desigual y rugosa. Espero que las matemáticas vectoriales utilizadas aquí sean sencillas una vez que consideras que el centro de la esfera se considera el origen (0,0)
.
Los árboles
Los árboles aparecen fuera de nuestra pista rodante para agregar profundidad al mundo y dentro como obstáculos. Crear el árbol es un poco más complicado que el terreno accidentado, pero sigue la misma lógica. Utilizamos una
primitiva ConeGeometry
para crear la parte verde superior del árbol y un
CylinderGeometry
para crear la parte inferior del tronco.
Para la parte superior, recorremos cada nivel de los vértices y expandimos el anillo de vértices seguido de la reducción del siguiente anillo. El siguiente código muestra el método blowUpTree
utilizado para expandir el anillo alternativo de vértices hacia afuera y el método tightenTree
utilizado para reducir el siguiente anillo de vértices.
1 |
function createTree(){ |
2 |
var sides=8; |
3 |
var tiers=6; |
4 |
var scalarMultiplier=(Math.random()*(0.25-0.1))+0.05; |
5 |
var midPointVector= new THREE.Vector3(); |
6 |
var vertexVector= new THREE.Vector3(); |
7 |
var treeGeometry = new THREE.ConeGeometry( 0.5, 1, sides, tiers); |
8 |
var treeMaterial = new THREE.MeshStandardMaterial( { color: 0x33ff33,shading:THREE.FlatShading } ); |
9 |
var offset; |
10 |
midPointVector=treeGeometry.vertices[0].clone(); |
11 |
var currentTier=0; |
12 |
var vertexIndex; |
13 |
blowUpTree(treeGeometry.vertices,sides,0,scalarMultiplier); |
14 |
tightenTree(treeGeometry.vertices,sides,1); |
15 |
blowUpTree(treeGeometry.vertices,sides,2,scalarMultiplier*1.1,true); |
16 |
tightenTree(treeGeometry.vertices,sides,3); |
17 |
blowUpTree(treeGeometry.vertices,sides,4,scalarMultiplier*1.2); |
18 |
tightenTree(treeGeometry.vertices,sides,5); |
19 |
var treeTop = new THREE.Mesh( treeGeometry, treeMaterial ); |
20 |
treeTop.castShadow=true; |
21 |
treeTop.receiveShadow=false; |
22 |
treeTop.position.y=0.9; |
23 |
treeTop.rotation.y=(Math.random()*(Math.PI)); |
24 |
var treeTrunkGeometry = new THREE.CylinderGeometry( 0.1, 0.1,0.5); |
25 |
var trunkMaterial = new THREE.MeshStandardMaterial( { color: 0x886633,shading:THREE.FlatShading } ); |
26 |
var treeTrunk = new THREE.Mesh( treeTrunkGeometry, trunkMaterial ); |
27 |
treeTrunk.position.y=0.25; |
28 |
var tree =new THREE.Object3D(); |
29 |
tree.add(treeTrunk); |
30 |
tree.add(treeTop); |
31 |
return tree; |
32 |
}
|
33 |
function blowUpTree(vertices,sides,currentTier,scalarMultiplier,odd){ |
34 |
var vertexIndex; |
35 |
var vertexVector= new THREE.Vector3(); |
36 |
var midPointVector=vertices[0].clone(); |
37 |
var offset; |
38 |
for(var i=0;i<sides;i++){ |
39 |
vertexIndex=(currentTier*sides)+1; |
40 |
vertexVector=vertices[i+vertexIndex].clone(); |
41 |
midPointVector.y=vertexVector.y; |
42 |
offset=vertexVector.sub(midPointVector); |
43 |
if(odd){ |
44 |
if(i%2===0){ |
45 |
offset.normalize().multiplyScalar(scalarMultiplier/6); |
46 |
vertices[i+vertexIndex].add(offset); |
47 |
}else{ |
48 |
offset.normalize().multiplyScalar(scalarMultiplier); |
49 |
vertices[i+vertexIndex].add(offset); |
50 |
vertices[i+vertexIndex].y=vertices[i+vertexIndex+sides].y+0.05; |
51 |
}
|
52 |
}else{ |
53 |
if(i%2!==0){ |
54 |
offset.normalize().multiplyScalar(scalarMultiplier/6); |
55 |
vertices[i+vertexIndex].add(offset); |
56 |
}else{ |
57 |
offset.normalize().multiplyScalar(scalarMultiplier); |
58 |
vertices[i+vertexIndex].add(offset); |
59 |
vertices[i+vertexIndex].y=vertices[i+vertexIndex+sides].y+0.05; |
60 |
}
|
61 |
}
|
62 |
}
|
63 |
}
|
64 |
function tightenTree(vertices,sides,currentTier){ |
65 |
var vertexIndex; |
66 |
var vertexVector= new THREE.Vector3(); |
67 |
var midPointVector=vertices[0].clone(); |
68 |
var offset; |
69 |
for(var i=0;i<sides;i++){ |
70 |
vertexIndex=(currentTier*sides)+1; |
71 |
vertexVector=vertices[i+vertexIndex].clone(); |
72 |
midPointVector.y=vertexVector.y; |
73 |
offset=vertexVector.sub(midPointVector); |
74 |
offset.normalize().multiplyScalar(0.06); |
75 |
vertices[i+vertexIndex].sub(offset); |
76 |
}
|
77 |
}
|
El método blowUpTree
empuja cada vértice alternativo en un anillo de vértices mientras mantiene los otros vértices en el anillo a una menor altura. Esto crea las ramas puntiagudas en el árbol. Si usamos los vértices impares en un nivel, entonces usamos los vértices pares en el siguiente nivel para que la uniformidad se rompa. Una vez que se forma el árbol completo, le damos una rotación aleatoria en el eje Y para que se vea ligeramente diferente.
El efecto de explosión
El efecto de explosión de píxeles en bloque no es el más elegante que podríamos usar, pero ciertamente funciona bien. Este efecto de partícula particular es en realidad una geometría 3D que se manipula para que parezca un efecto utilizando la clase THREE.Points
.
1 |
function addExplosion(){ |
2 |
particleGeometry = new THREE.Geometry(); |
3 |
for (var i = 0; i < particleCount; i ++ ) { |
4 |
var vertex = new THREE.Vector3(); |
5 |
particleGeometry.vertices.push( vertex ); |
6 |
}
|
7 |
var pMaterial = new THREE.ParticleBasicMaterial({ |
8 |
color: 0xfffafa, |
9 |
size: 0.2 |
10 |
});
|
11 |
particles = new THREE.Points( particleGeometry, pMaterial ); |
12 |
scene.add( particles ); |
13 |
particles.visible=false; |
14 |
}
|
15 |
function explode(){ |
16 |
particles.position.y=2; |
17 |
particles.position.z=4.8; |
18 |
particles.position.x=heroSphere.position.x; |
19 |
for (var i = 0; i < particleCount; i ++ ) { |
20 |
var vertex = new THREE.Vector3(); |
21 |
vertex.x = -0.2+Math.random() * 0.4; |
22 |
vertex.y = -0.2+Math.random() * 0.4 ; |
23 |
vertex.z = -0.2+Math.random() * 0.4; |
24 |
particleGeometry.vertices[i]=vertex; |
25 |
}
|
26 |
explosionPower=1.07; |
27 |
particles.visible=true; |
28 |
}
|
29 |
function doExplosionLogic(){//called in update |
30 |
if(!particles.visible)return; |
31 |
for (var i = 0; i < particleCount; i ++ ) { |
32 |
particleGeometry.vertices[i].multiplyScalar(explosionPower); |
33 |
}
|
34 |
if(explosionPower>1.005){ |
35 |
explosionPower-=0.001; |
36 |
}else{ |
37 |
particles.visible=false; |
38 |
}
|
39 |
particleGeometry.verticesNeedUpdate = true; |
40 |
}
|
El método addExplosion
agrega 20 vértices a la matriz de vértices
de particleGeometry
. El método de explosión
se invoca cuando necesitamos que se ejecute el efecto, que coloca aleatoriamente cada vértice de la geometría. Se llama a doExplosionLogic
en el método de actualización
si el objeto de partícula es visible, donde movemos cada vértice hacia afuera. Cada vértice en un objeto de puntos
se representa como un bloque cuadrado.
4. El juego
Ahora que sabemos cómo crear cada uno de los elementos necesarios para el juego, entremos en el juego. Los principales elementos del juego son:
- el bucle del juego
- la ubicación de los árboles
- la interacción del usuario
- la detección de colisión
Analicemos esos en detalle.
El bucle del juego
Toda la mecánica básica del juego ocurre en el ciclo del juego, que en nuestro caso es el método de actualización
. Lo llamamos por primera vez desde el método init
, que recibe un llamado en la carga de la ventana. Después de esto,
se engancha en el bucle de procesamiento de documentos utilizando el
método requestAnimationFrame
para que se llame repetidamente.
1 |
function update(){ |
2 |
rollingGroundSphere.rotation.x += rollingSpeed; |
3 |
heroSphere.rotation.x -= heroRollingSpeed; |
4 |
if(heroSphere.position.y<=heroBaseY){ |
5 |
jumping=false; |
6 |
bounceValue=(Math.random()*0.04)+0.005; |
7 |
}
|
8 |
heroSphere.position.y+=bounceValue; |
9 |
heroSphere.position.x=THREE.Math.lerp(heroSphere.position.x,currentLane, 2*clock.getDelta());//clock.getElapsedTime()); |
10 |
bounceValue-=gravity; |
11 |
if(clock.getElapsedTime()>treeReleaseInterval){ |
12 |
clock.start(); |
13 |
addPathTree(); |
14 |
if(!hasCollided){ |
15 |
score+=2*treeReleaseInterval; |
16 |
scoreText.innerHTML=score.toString(); |
17 |
}
|
18 |
}
|
19 |
doTreeLogic(); |
20 |
doExplosionLogic(); |
21 |
render(); |
22 |
requestAnimationFrame(update);//request next update |
23 |
}
|
24 |
function render(){ |
25 |
renderer.render(scene, camera);//draw |
26 |
}
|
En la actualización
, llamamos al método de renderizado
, que usa el renderizador
para dibujar la escena. Llamamos al método doTreeLogic
, que comprueba la colisión y también elimina los árboles una vez que se han perdido de vista.
La bola de nieve y las esferas de tierra giran mientras también agregamos una lógica de rebote al azar a la bola de nieve. Los árboles nuevos se colocan en la ruta llamando a addPathTree
después de que haya transcurrido un tiempo predefinido. El tiempo se rastrea utilizando un objeto THREE.Clock
. También actualizamos el puntaje
a menos que haya ocurrido una colisión.
Colocación de los árboles
Un conjunto de árboles se coloca fuera de la pista rodante para crear el mundo utilizando el método addWorldTrees
. Todos los árboles se agregan como elementos secundarios de rollingGroundSphere
para que también se muevan cuando rotamos la esfera.
1 |
function addWorldTrees(){ |
2 |
var numTrees=36; |
3 |
var gap=6.28/36; |
4 |
for(var i=0;i<numTrees;i++){ |
5 |
addTree(false,i*gap, true); |
6 |
addTree(false,i*gap, false); |
7 |
}
|
8 |
}
|
9 |
function addTree(inPath, row, isLeft){ |
10 |
var newTree; |
11 |
if(inPath){ |
12 |
if(treesPool.length===0)return; |
13 |
newTree=treesPool.pop(); |
14 |
newTree.visible=true; |
15 |
//console.log("add tree");
|
16 |
treesInPath.push(newTree); |
17 |
sphericalHelper.set( worldRadius-0.3, pathAngleValues[row], -rollingGroundSphere.rotation.x+4 ); |
18 |
}else{ |
19 |
newTree=createTree(); |
20 |
var forestAreaAngle=0;//[1.52,1.57,1.62]; |
21 |
if(isLeft){ |
22 |
forestAreaAngle=1.68+Math.random()*0.1; |
23 |
}else{ |
24 |
forestAreaAngle=1.46-Math.random()*0.1; |
25 |
}
|
26 |
sphericalHelper.set( worldRadius-0.3, forestAreaAngle, row ); |
27 |
}
|
28 |
newTree.position.setFromSpherical( sphericalHelper ); |
29 |
var rollingGroundVector=rollingGroundSphere.position.clone().normalize(); |
30 |
var treeVector=newTree.position.clone().normalize(); |
31 |
newTree.quaternion.setFromUnitVectors(treeVector,rollingGroundVector); |
32 |
newTree.rotation.x+=(Math.random()*(2*Math.PI/10))+-Math.PI/10; |
33 |
|
34 |
rollingGroundSphere.add(newTree); |
35 |
}
|
Para plantar árboles del mundo, llamamos al método addTree
al pasar valores alrededor de la circunferencia de nuestra esfera terrestre. La utilidad esféricaHelper
nos ayuda a encontrar la posición en la superficie de una esfera.
Para plantar árboles en el camino, haremos uso de un grupo de árboles que se crean al comenzar usando el método createTreesPool
. También tenemos valores de ángulos predefinidos para cada ruta en la esfera almacenada en la matriz pathAngleValues
.
1 |
pathAngleValues=[1.52,1.57,1.62]; |
2 |
//..
|
3 |
function createTreesPool(){ |
4 |
var maxTreesInPool=10; |
5 |
var newTree; |
6 |
for(var i=0; i<maxTreesInPool;i++){ |
7 |
newTree=createTree(); |
8 |
treesPool.push(newTree); |
9 |
}
|
10 |
}
|
11 |
function addPathTree(){ |
12 |
var options=[0,1,2]; |
13 |
var lane= Math.floor(Math.random()*3); |
14 |
addTree(true,lane); |
15 |
options.splice(lane,1); |
16 |
if(Math.random()>0.5){ |
17 |
lane= Math.floor(Math.random()*2); |
18 |
addTree(true,options[lane]); |
19 |
}
|
20 |
}
|
El método
addPathTree
se llama a partir de la actualización cuando ha transcurrido
el tiempo suficiente después de plantar el último árbol. A su vez llama al método addTree
mostrado anteriormente con un conjunto diferente de parámetros donde el árbol se coloca en la ruta seleccionada. El método doTreeLogic
devolverá el árbol al grupo una vez que se salga de la vista.
La interacción del usuario
Estamos agregando un oyente al documento para buscar eventos de teclado relevantes. El método handleKeyDown
establece el valor del currentLane
si se presionan las teclas de flecha derecha o izquierda o establece el valor de bounceValue
si se presiona la flecha hacia arriba.
1 |
document.onkeydown = handleKeyDown; |
2 |
//..
|
3 |
function handleKeyDown(keyEvent){ |
4 |
if(jumping)return; |
5 |
var validMove=true; |
6 |
if ( keyEvent.keyCode === 37) {//left |
7 |
if(currentLane==middleLane){ |
8 |
currentLane=leftLane; |
9 |
}else if(currentLane==rightLane){ |
10 |
currentLane=middleLane; |
11 |
}else{ |
12 |
validMove=false; |
13 |
}
|
14 |
} else if ( keyEvent.keyCode === 39) {//right |
15 |
if(currentLane==middleLane){ |
16 |
currentLane=rightLane; |
17 |
}else if(currentLane==leftLane){ |
18 |
currentLane=middleLane; |
19 |
}else{ |
20 |
validMove=false; |
21 |
}
|
22 |
}else{ |
23 |
if ( keyEvent.keyCode === 38){//up, jump |
24 |
bounceValue=0.1; |
25 |
jumping=true; |
26 |
}
|
27 |
validMove=false; |
28 |
}
|
29 |
if(validMove){ |
30 |
jumping=true; |
31 |
bounceValue=0.06; |
32 |
}
|
33 |
}
|
En la
actualización
, la posición x
de nuestra bola de nieve se incrementa
lentamente para alcanzar la currentLane
allí al cambiar de carril.
Detección de colisiones
No hay física real involucrada en este juego en particular, aunque podríamos usar varios marcos físicos para nuestro propósito de detección de colisión. Pero como usted bien sabe, un motor de física agrega mucho rendimiento a nuestro juego, y siempre debemos tratar de ver si podemos evitarlo.
En nuestro caso, calculamos la distancia entre nuestra bola de nieve y cada árbol para provocar una colisión si están muy cerca. Esto sucede en el método doTreeLogic
, que se llama desde la actualización
.
1 |
function doTreeLogic(){ |
2 |
var oneTree; |
3 |
var treePos = new THREE.Vector3(); |
4 |
treesInPath.forEach( function ( element, index ) { |
5 |
oneTree=treesInPath[ index ]; |
6 |
treePos.setFromMatrixPosition( oneTree.matrixWorld ); |
7 |
if(treePos.distanceTo(heroSphere.position)<=0.6){ |
8 |
console.log("hit"); |
9 |
hasCollided=true; |
10 |
explode(); |
11 |
}
|
12 |
});
|
13 |
//..
|
14 |
}
|
Como habrás notado, todos los árboles actualmente presentes en nuestra ruta se almacenan en la matriz treesInPath
. El método doTreeLogic también elimina los árboles de la pantalla y en el grupo una vez que salen de nuestra vista utilizando el código que se muestra a continuación.
1 |
var treesToRemove=[]; |
2 |
treesInPath.forEach( function ( element, index ) { |
3 |
oneTree=treesInPath[ index ]; |
4 |
treePos.setFromMatrixPosition( oneTree.matrixWorld ); |
5 |
if(treePos.z>6 &&oneTree.visible){//gone out of our view zone |
6 |
treesToRemove.push(oneTree); |
7 |
}
|
8 |
});
|
9 |
var fromWhere; |
10 |
treesToRemove.forEach( function ( element, index ) { |
11 |
oneTree=treesToRemove[ index ]; |
12 |
fromWhere=treesInPath.indexOf(oneTree); |
13 |
treesInPath.splice(fromWhere,1); |
14 |
treesPool.push(oneTree); |
15 |
oneTree.visible=false; |
16 |
console.log("remove tree"); |
17 |
});
|
Conclusión
Crear un juego en 3D es un proceso complicado si no está usando una herramienta visual como Unity. Puede parecer intimidante o abrumador, pero déjame asegurarte que una vez que lo domines, te sentirás mucho más poderoso y creativo. Me gustaría que exploraras aún más utilizando los diversos marcos físicos o sistemas de partículas o los ejemplos oficiales.