Advertisement
  1. Game Development
  2. 3D

Crea un shooter espacial con PlayCanvas: Parte 2

Scroll to top
Read Time: 15 min
This post is part of a series called Create a Space Shooter with PlayCanvas.
Create a Space Shooter With PlayCanvas: Part 1

Spanish (Español) translation by Andrea Jiménez (you can also view the original English article)

Esta es la segunda parte de nuestra búsqueda para crear un shooter espacial en 3D. En la primera parte analizamos cómo configurar un juego básico de PlayCanvas, con física y colisión, nuestros propios modelos y una cámara.

Como referencia, esta es una demostración en vivo de nuestro resultado final nuevamente.

En esta parte, nos centraremos en la creación dinámica de entidades con scripts (para generar balas y asteroides), así como en cómo agregar cosas como un contador de FPS y texto en el juego. Si ya seguiste la parte anterior y estás satisfecho con lo que tienes, puedes comenzar a construir a partir de eso y omitir la siguiente sección de configuración mínima. De lo contrario, si necesitas reiniciar desde cero:

Configuración mínima

  1. Inicia un nuevo proyecto.
  2. Elimina todos los objetos de la escena excepto Cámara, Caja y Luz.
  3. Coloca tanto la luz como la cámara dentro del objeto de la caja en el panel de jerarquía.
  4. Pon la cámara en posición (0,1.5,2) y rotación (-20,0,0).
  5. Asegúrate de que el objeto de luz esté colocado en una posición que se vea bien (lo puse encima de la caja).
  6. Coloca un componente de cuerpo rígido en la caja. Configura tu tipo en dinámico. Y configura tu amortiguación en 0.95 (tanto lineal como angular).
  7. Conecta un componente de colisión a la caja.
  8. Configura la gravedad en 0 (desde la configuración de la escena).
  9. Coloca una esfera en (0,0,0) solo para marcar esta posición en el espacio.
  10. Crea y adjunta este script al cuadro y llámalo Fly.js:
1
var Fly = pc.createScript('fly');
2
3
Fly.attributes.add('speed', { type: 'number', default:50 });
4
5
// initialize code called once per entity

6
Fly.prototype.initialize = function() {
7
    
8
};
9
10
// update code called every frame

11
Fly.prototype.update = function(dt) {
12
     // Press Z to thrust 

13
    if(this.app.keyboard.isPressed(pc.KEY_Z)) {
14
        //Move in the direction its facing

15
        var force = this.entity.forward.clone().scale(this.speed);
16
        this.entity.rigidbody.applyForce(force);
17
    }
18
    
19
    // Rotate up/down/left/right    

20
    if(this.app.keyboard.isPressed(pc.KEY_UP)){
21
        var force_up = this.entity.right.clone().scale(1);
22
        this.entity.rigidbody.applyTorque(force_up);    
23
    }
24
    if(this.app.keyboard.isPressed(pc.KEY_DOWN)){
25
        var force_down = this.entity.right.clone().scale(-1);
26
        this.entity.rigidbody.applyTorque(force_down);    
27
    }
28
    if(this.app.keyboard.isPressed(pc.KEY_RIGHT)){
29
        // Rotate to the right

30
        var force_right = this.entity.up.clone().scale(-1);
31
        this.entity.rigidbody.applyTorque(force_right);    
32
    }
33
    if(this.app.keyboard.isPressed(pc.KEY_LEFT)){
34
        var force_left = this.entity.up.clone().scale(1);
35
        this.entity.rigidbody.applyTorque(force_left);    
36
    }
37
};
38
39
// swap method called for script hot-reloading

40
// inherit your script state here

41
Fly.prototype.swap = function(old) { };
42
43
// to learn more about script anatomy, please read:

44
// http://developer.playcanvas.com/en/user-manual/scripting/

Prueba que todo funcionó. ¡Deberías poder volar con Z para empujar y las teclas de flecha para girar!

8. Generación de asteroides

La creación dinámica de objetos es crucial para casi cualquier tipo de juego. En la demostración que creé, estoy generando dos tipos de asteroides. Los del primer tipo simplemente flotan y actúan como obstáculos pasivos. Reaspan cuando se alejan demasiado para crear un campo de asteroides consistentemente denso alrededor del jugador. El segundo tipo surge desde más lejos y se mueve hacia el jugador (para crear una sensación de peligro incluso si el jugador no se está moviendo).

Necesitamos tres cosas para generar nuestros asteroides:

  1. Una entidad AsteroidModel desde la cual clonar todos los demás asteroides.
  2. Un script AsteroidSpawner adjunto al objeto root que actuará como nuestra fábrica/clonador.
  3. Un script Asteroid para definir el comportamiento de cada asteroide.

Crear un modelo de Asteroide

Crea una nueva entidad a partir de un modelo de tu elección. Esto podría ser algo de la tienda PlayCanvas, o algo de BlendSwap, o simplemente una forma básica. (Si estás utilizando tus propios modelos, es una buena práctica abrirlo primero en Blender para verificar el número de caras utilizadas y optimizarlo si es necesario).

Dale una forma de colisión adecuada y un componente de cuerpo rígido (asegúrate de que sea dinámico). Una vez que estés satisfecho con él, desmarca la casilla Habilitado:

Where to toggle the Enabled propertyWhere to toggle the Enabled propertyWhere to toggle the Enabled property

Cuando desactivas un objeto como este, es equivalente a eliminarlo del mundo en lo que respecta al jugador. Esto es útil para eliminar objetos temporalmente, o en nuestro caso, para mantener un objeto con todas sus propiedades pero no hacer que aparezca en el juego.

Creación del Script de generación de asteroides

Crea un nuevo script denominado AsteroidSpawner.js y adjúntalo al objeto Root de la jerarquía. (Ten en cuenta que Root es solo un objeto normal que puede tener cualquier componente conectado a él, al igual que la cámara).

Ahora abre el script que acabas de crear.

La forma general de clonar una entidad y agregarla al mundo a través de un script se ve así:

1
// Create the clone

2
var newEntity = oldEntity.clone();
3
// Add it to the root object

4
this.app.root.addChild(newEntity);
5
// Give it a new name, otherwise it also  gets oldEntity's name

6
newEntity.name = "ClonedEntity"; 
7
// Enable it, assuming oldEntity is set to disabled 

8
newEntity.enabled = true;

Así es como clonarías un objeto si ya tuvieras un objeto "oldEntity". Esto deja una pregunta sin respuesta: ¿Cómo accedemos al AsteroidModel que creamos?

Hay dos maneras de hacer esto. La forma más flexible es crear un atributo de script que contenga qué entidad clonar, de modo que puedas intercambiar modelos fácilmente sin tocar el script. (Así es exactamente como hicimos el script de camera lookAt en el paso 7).

La otra forma es usar la función findByName. Puedes llamar a este método en cualquier entidad para encontrar cualquiera de sus hijos. Así que podemos llamarlo en el objeto root:

1
var oldEntity = this.app.root.findByName("AsteroidModel");

Y esto completará nuestro código desde arriba. El script completo de AsteroidSpawner ahora se ve así:

1
var AsteroidSpawner = pc.createScript('asteroidSpawner');
2
3
// initialize code called once per entity

4
AsteroidSpawner.prototype.initialize = function() {
5
    var oldEntity = this.app.root.findByName("AsteroidModel");
6
    // Create the clone

7
    var newEntity = oldEntity.clone();
8
    // Add it to the root object

9
    this.app.root.addChild(newEntity);
10
    // Give it a new name, otherwise it also  gets oldEntity's name

11
    newEntity.name = "ClonedEntity"; 
12
    // Enable it, assuming oldEntity is set to disabled 

13
    newEntity.enabled = true;
14
    
15
    // Set its position 

16
    newEntity.rigidbody.teleport(new pc.Vec3(0,0,1));
17
};
18
19
// update code called every frame

20
AsteroidSpawner.prototype.update = function(dt) {
21
    
22
};

Prueba que esto funcionó lanzando y buscando ver si tu modelo de asteroide existe.

Nota: Utilicé newEntity.rigidbody.teleport en lugar de newEntity.setPosition. Si una entidad tiene un cuerpo rígido, entonces el cuerpo rígido anulará la posición y la rotación de la entidad, así que recuerda configurar estas propiedades en el cuerpo rígido y no en la entidad en sí.

Antes de continuar, intenta hacer que aparezcan diez o más asteroides alrededor del jugador, ya sea al azar o de alguna manera sistemática (¿tal vez incluso en un círculo?). Sería útil poner todo tu código de generación en una función para que se vea así:

1
AsteroidSpawner.prototype.initialize = function() {
2
    this.spawn(0,0,0);
3
    this.spawn(1,0,0);
4
    this.spawn(1,1,0);
5
    // etc... 

6
};
7
8
AsteroidSpawner.prototype.spawn = function(x,y,z){
9
    // Spawning code here.. 

10
}

Creación del script Asteroid

Deberías sentirte cómodo agregando nuevos scripts a estas alturas. Crea un nuevo script (llamado Asteroid.js) y adjúntalo al AsteroidModel. Dado que todos nuestros asteroides engendrados son clones, todos tendrán el mismo script adjunto a ellos.

Si estamos creando muchos asteroides, sería una buena idea asegurarnos de que se destruyan cuando ya no los necesitemos o cuando estén lo suficientemente lejos. Esta es una forma en la que podrías hacer esto:

1
Asteroid.prototype.update = function(dt) {
2
    // Get the player

3
    var player = this.app.root.findByName("Ship"); // Replace "Ship" with whatever your player's name is 

4
    // Clone the asteroid's position 

5
    var distance = this.entity.getPosition().clone();
6
    // Subtract the player's position from this asteroid's position

7
    distance.sub(player.getPosition());
8
    // Get the length of this vector

9
    if(distance.length() > 10){ //Some arbitrary threshold 

10
        this.entity.destroy();
11
    }
12
};

Consejo de depuración: Si quieres imprimir algo, siempre puedes usar la consola del navegador como si se tratara de cualquier aplicación JavaScript normal. Así que podrías hacer algo como console.log(distance.toString()); para imprimir el vector de distancia, y aparecerá en la consola.

Antes de continuar, comprueba que el asteroide desaparezca cuando te alejes de él.

9. Generación de balas

Generar balas será aproximadamente la misma idea que generar asteroides, con un nuevo concepto: queremos detectar cuándo la bala golpea algo y eliminarlo. Para crear nuestro sistema de balas, necesitamos:

  1. Un modelo de bala para clonar.
  2. Un script de Shoot.js para generar balas al presionar X.
  3. Un script Bullet.js para definir el comportamiento de cada bala.

Creación de un modelo de bala

Puedes usar cualquier forma para tu bala. Usé una cápsula solo para tener una idea de hacia qué dirección se dirigía la bala. Al igual que antes, crea tu entidad, disminuye la escala y dale un cuerpo rígido dinámico y una caja de colisión adecuada. Dale el nombre de "Bullet" para que sea fácil de encontrar.

Una vez que hayas terminado, asegúrate de deshabilitarla (con la casilla de verificación Habilitada).

Creación de un Script shoot

Crea un nuevo script y adjúntalo a tu nave de jugadores. Esta vez usaremos un atributo para obtener una referencia a nuestra entidad de bala:

1
Shoot.attributes.add('bullet', { type: 'entity' });

Vuelve al editor y presiona "analizar" para que aparezca el nuevo atributo y selecciona la entidad de bala que creaste.

Ahora en la función de actualización, queremos:

  1. Clonarla y añadirla al mundo.
  2. Aplicar una fuerza en la dirección en la que mira el jugador.
  3. Colocarla frente al jugador.

Ya conoces todos estos conceptos. Has visto cómo clonar asteroides, cómo aplicar una fuerza en una dirección para hacer que la nave se mueva y cómo colocar las cosas. Dejaré la implementación de esta parte como un desafío. (Pero si te quedas atascado, siempre puedes ver cómo implementé mi propio script Shoot.js en mi proyecto).

Estos son algunos consejos que podrían ahorrarte un poco de dolor de cabeza:

  1. Usa keyboard.wasPressed en lugar de keyboard.isPressed. Al detectar cuándo se presiona la tecla X para disparar, la primera es una forma conveniente de hacer que dispare solo cuando presionas en lugar de disparar mientras se mantenga presionado el botón.
  2. Usa rotateLocal en lugar de configurar una rotación absoluta. Para asegurarte de que la bala siempre se genere en paralelo al barco, fue un fastidio calcular los ángulos correctamente. Una forma mucho más fácil es simplemente configurar la rotación de la bala en la rotación de la nave, y luego girar la bala en su espacio local en 90 grados en el eje X.

Creación del script de comportamiento de la bala

En este punto, tus balas deberían estar generando, golpeando los asteroides y simplemente rebotando en el espacio vacío. La cantidad de balas puede volverse abrumadora rápidamente, y saber cómo detectar colisiones es útil para todo tipo de cosas. (Por ejemplo, es posible que hayas notado que puedes crear objetos que solo tienen un componente de colisión pero no un cuerpo rígido. Estos actuarían como desencadenantes pero no reaccionarían físicamente).

Crea un nuevo script llamado Bullet y adjúntalo al modelo Bullet que se clona. PlayCanvas tiene tres tipos de eventos de contacto. Escucharemos el choque, que se dispara cuando los objetos se separan (de lo contrario, la bala se destruiría antes de que el asteroide tenga la oportunidad de reaccionar).

Para escuchar un evento de contacto, escribe esto en tu función de inicio:

1
this.entity.collision.on('collisionend', this.onCollisionEnd, this);

Y luego crea el oyente en sí:

1
Bullet.prototype.onCollisionEnd = function(result) {
2
    // Destroy the bullet if it hits an asteroid 

3
    if(result.name == "Asteroid") {
4
        this.entity.destroy();
5
    }
6
};

Aquí es donde el nombre que le diste a tus asteroides cuando los generaste se vuelve relevante. Queremos que la bala solo se destruya cuando choque con un asteroide. result es la entidad con la que terminó de chocar.

Alternativamente, puedes eliminar esa cheque y simplemente hacer que se destruya en caso de colisión con cualquier cosa.

Es cierto que no hay otros objetos en el mundo con los que chocar, pero tuve algunos problemas desde el principio con el jugador que activó la colisión de la bala durante un fotograma y desapareció antes de que pudiera lanzarse. Si tienes necesidades de colisión más complicadas, PlayCanvas admite grupos de colisión y máscaras, pero no está muy bien documentado en el momento de escribir este artículo.

10. Agregar un medidor de FPS

Básicamente terminamos con el juego en sí en este punto. Por supuesto, hay un montón de pequeños detalles que agregué a la demostración final, pero no hay nada que no puedas hacer con lo que aprendiste hasta ahora.

Quería mostrarte cómo crear un medidor de FPS (a pesar de que PlayCanvas ya tiene un generador de perfiles; puedes pasar el cursor sobre el botón de reproducción y marcar la casilla del generador de perfiles) porque es un buen ejemplo de agregar un elemento DOM que está fuera del motor de PlayCanvas.

Usaremos esta biblioteca astuta de FPSMeter. Lo primero que debes hacer es dirigirte al sitio web de la biblioteca y descargar la versión de producción minificada.

Vuelve a tu editor de PlayCanvas, crea un nuevo script y copia el código fpsMeter.min.js. Adjunta este script al objeto raíz.

Ya que se cargó la biblioteca, crea un nuevo script que se inicializará y usará la biblioteca. Llámala meter.js, y del ejemplo de uso en el sitio web de la biblioteca, tenemos:

1
var Meter  = pc.createScript('meter');
2
3
Meter.prototype.initialize = function(){
4
    this.meter = new FPSMeter(document.body, {
5
        graph: 1,
6
        heat: 1
7
    });
8
};
9
10
11
Meter.prototype.update = function(dt){
12
     this.meter.tick();
13
};

Agrega también el script del medidor al objeto raíz y ejecútalo. ¡Deberías ver el contador de FPS en la esquina superior izquierda de la pantalla!

11. Añadir texto

Finalmente, agreguemos un poco de texto en nuestro mundo. Este está un poco involucrado ya que hay varias formas de hacerlo. Si solo quieres agregar una interfaz de usuario estática, una forma de hacerlo es trabajar con el DOM directamente, superponiendo los elementos de la interfaz de usuario sobre el elemento de lienzo de PlayCanvas. Otro método es utilizar SVG. Esta publicación analiza algunas de estas diferentes formas.

Dado que todas estas son formas estándar de manejar el texto en la web, opté por ver cómo crear texto que exista dentro del espacio del mundo del juego. Así que piensa en ello como texto que iría en un letrero en el entorno o un objeto en el juego.

La forma en que hacemos esto es creando un material para cada pieza de texto que queremos renderizar. Luego creamos un lienzo invisible en el que renderizamos el texto usando el método familiar fillText de lienzo. Finalmente, renderizamos el lienzo sobre el material para que aparezca en el juego.

Ten en cuenta que este método se puede utilizar para algo más que texto. Puedes dibujar texturas dinámicamente o hacer cualquier cosa que un lienzo pueda hacer.

Crear el material de texto

Crea un nuevo material y llámalo algo así como "TextMaterial". Configura tu color difuso en negro ya que nuestro texto será blanco.

Crea una entidad plana y adjunta este material a ella.

Crea el script de texto

Puedes encontrar el script text.js completo en esta idea:

https://gist.github.com/OmarShehata/e016dc219da36726e65cedb4ab9084bd

Puedes ver cómo configura la textura para usar el lienzo como fuente, específicamente en la línea: this.texture.setSource(this.canvas);

Crea este script y adjúntalo a tu avión. Observa cómo crea dos atributos: texto y tamaño de fuente. De esta manera, puedes usar el mismo script para cualquier objeto de texto en tu juego.

Inicia la simulación y deberías ver el texto grande "Hola mundo" en algún lugar. Si no lo ves, asegúrate de que a) tenga una fuente de luz cerca y b) estés mirando hacia el lado correcto. El texto no se procesará si lo miras desde atrás. (También ayuda colocar un objeto físico cerca del avión solo para ubicarlo al principio).

12. Publicaciones

Una vez que hayas reunido tu impresionante prototipo, puedes hacer clic en el icono de PlayCanvas en la esquina superior izquierda de la pantalla y seleccionar "Publicación". ¡Aquí es donde puedes publicar nuevas compilaciones para alojarlas en PlayCanvas y compartirlas con el mundo!

Dialog box for publishing on PlayCanvasDialog box for publishing on PlayCanvasDialog box for publishing on PlayCanvas

Conclusión

Eso es todo por esta demostración. Hay mucho más para explorar en PlayCanvas, pero esperamos que esta descripción general te haga sentir lo suficientemente cómodo con los conceptos básicos para comenzar a construir tus propios juegos. Es un motor realmente agradable que creo que debería usar más gente. Mucho de lo que se ha creado con él han sido demostraciones técnicas en lugar de juegos completos, pero no hay razón para que no puedas crear y publicar algo increíble con él.

Una característica de la que realmente no hablé, pero que podría haber sido evidente, es que el editor de PlayCanvas te permite actualizar tu juego en tiempo real. Esto es cierto para el diseño, ya que puedes mover cosas en el editor y se actualizarán en la ventana de inicio si lo tienes abierto, así como para el código, con su recarga en caliente.

Finalmente, si bien el editor es realmente conveniente, cualquier cosa que puedas hacer con él se puede hacer con código puro. Entonces, si necesitas usar PlayCanvas con un presupuesto limitado, una buena referencia para usar es la carpeta de ejemplos en GitHub. (Un buen lugar para comenzar sería este simple ejemplo de un cubo giratorio).

Si algo es confuso, ¡por favor házmelo saber en los comentarios! O simplemente si construiste algo genial y quieres compartir, o descubriste una manera más fácil de hacer algo, ¡me encantaría verlo!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.