En esta segunda parte, voy a implementar los controles del jugador permitiendo que se pueda mover a izquierda o derecha y también disparar. Además, haré que los enemigos sean destruidos cuando son alcanzados por un disparo o cuando chocan con el propio jugador.
Corrigiendo bugs…
En la primera parte, el código tenía un pequeño bug que seguramente has detectado. Los enemigos colisionaban entre ellos y se paraban en medio de la pantalla en vez de seguir bajando. Esto ocurría porque al añadir el componente 2d de Quintus, éste gestiona las colisiones de esa manera, pero es posible desactivar ese comportamiento para programar tú mismo el comportamiento al colisionar. Para eso, debes de añadir la propiedad “skipCollide: true” en el objeto Enemy. De esta manera, las colisiones seguirán siendo detectadas pero no tratadas por Quintus.
Por otra parte, en algunas ocasiones, los enemigos estaban casi fuera del canvas por la parte derecha. Esto es debido a que mi código no era muy acertado para generar su posición sobre el eje X. Lo he solucionado utilizando la siguiente fórmula:
1 |
Math.floor(Math.random() * (maxX - minX + 1)) + minX |
maxX es el valor de X máximo (el ancho del canvas menos 30px). minX es el valor de X mínimo (30px).
El objeto «Bullet»
Voy a crear un nuevo objeto «Bullet» que extenderá de «MovingSprite» igual que los dos primeros que he creado en la parte 1, “Player” y “Enemy”. Este objeto representará una bala disparada por el objeto “Player” cada vez que éste pulse la tecla “FIRE”. Añadiré el componente 2d a este objeto para poder controlar las colisiones y el movimiento.
Debes prestar especial atención a la propiedad “sensor: true” que añado al objeto “Bullet”. Esta propiedad permite que las colisiones sean detectadas pero que no se ejecute ninguna acción de las que Quintus tiene definidas en su módulo 2d. Prueba a desactivarlo si quieres y verás que si mantienes pulsada la tecla “FIRE” (tecla espacio en el teclado) las balas colisionan y no avanzan sino que se van desplazando hacia la izquierda una a una. Esto se soluciona con esa simple propiedad “sensor: true”.
Sobreescribo el método «step» para poder destruir las balas que salen del canvas. Si no lo hiciera, cada vez que una bala sobrepasa la parte superior del canvas seguiría avanzando indefinidamente. Por lo tanto, la destruyo en cuanto sale del campo de visión del jugador.
El módulo Input
Para implementar los controles, voy a utilizar el módulo Input de Quintus. Este módulo me permite escuchar los controles que el jugador vaya pulsando. En mi juego, quiero que el jugador sólo se mueva hacia los lados y no hacia arriba o abajo. Quintus incluye dos componentes “platformerControls” y “stepControls” pero ninguno me será de ayuda para lo que necesito. Por lo tanto voy a sobreescribir el método “step” y definir yo mismo la respuesta asociada a la pulsación de la tecla “LEFT” y la tecla “RIGHT”. Además, para disparar, añadiré un método que escuchará el evento “fire” que será lanzado por Quintus cuando el usuario pulse la tecla “FIRE”. Este método se llamará “shoot” y se encargará de crear nuevas balas.
El objeto «Player»
El objeto “Player” tiene ahora 2 métodos más “step” y “shoot”. El método “step” me permite definir el movimiento del jugador asociado a las teclas que pulsa cómo acabo de explicar. El método “shoot” me permite crear balas nuevas cada vez que se pulsa la tecla “FIRE”.
El código completo con comentarios
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>¿Cómo hacer un juego Shoo'Em Up con Quintus? - Parte 2</title> <style> body { padding: 0; margin: 0; background: #fff; } canvas { background: #000; } </style> </head> <body> <!-- Cargando quintus y todos sus módulos --> <script src="js/quintus.js"></script> <script src="js/quintus_2d.js"></script> <script src="js/quintus_anim.js"></script> <script src="js/quintus_audio.js"></script> <script src="js/quintus_input.js"></script> <script src="js/quintus_scenes.js"></script> <script src="js/quintus_sprites.js"></script> <script src="js/quintus_tmx.js"></script> <script src="js/quintus_touch.js"></script> <script> window.addEventListener("load", function() { var Q = Quintus({ development: true }) /** * En la parte 2, voy a utilizar el módulo Input para controles. */ .include("Sprites, Scenes, 2D, Input") .setup({ width: 320, height: 480 }) /** * Con esta simple línea, los controles están ahora activados. */ .controls(); Q.gravityY = 0; var SPRITE_PLAYER = 1; var SPRITE_BULLET = 2; var SPRITE_ENEMY = 3; Q.MovingSprite.extend("Player", { init: function(p) { this._super(p, { sheet: "player", sprite: "player", type: SPRITE_PLAYER, collisionMask: SPRITE_ENEMY, /** * "speed" es la velocidad del jugador cuando se mueve. */ speed: 300 }); /** * Cuando el jugador pulsa el botón "FIRE", Quintus lanza el evento "fire" y * el método "shoot" es llamado. */ Q.input.on("fire", this, "shoot"); }, /** * Sobreescribo el método "step" para definir qué hacer cuando los botones "LEFT" o "RIGHT" * son pulsados. Incremento o decremento "vx" con "speed" y actualizo "x". */ step: function(dt) { var p = this.p; /** * Comprobando si "LEFT" está siendo pulsado y si el ala izquierda del jugador está dentro del canvas. */ if (Q.inputs['left'] && (p.x - p.w/2) > 0) { p.vx = -p.speed; /** * Comprobando si "RIGHT" está siendo pulsado y si el ala derecha del jugador está dentro del canvas. */ } else if (Q.inputs['right'] && (p.x + p.w/2) < Q.width) { p.vx = p.speed; } else { p.vx = 0; } p.x += p.vx * dt; }, /** * Este método va a crear un nuevo objeto "Bullet". * La posición del nuevo "Bullet" tiene que ser el mismo x que el jugador * y un poco por encima del objeto "Player". * vy es la velocidad de "Bullet" sobre el eje Y. */ shoot: function() { var p = this.p; this.stage.insert(new Q.Bullet({ x: p.x, y: p.y - p.w/2, vy: -200 })) } }); Q.MovingSprite.extend("Enemy", { init: function(p) { this._super(p, { sheet: "enemy", sprite: "enemy", type: SPRITE_ENEMY, collisionMask: SPRITE_BULLET | SPRITE_PLAYER, /** * Esta propiedad desactiva el impacto implementado por Quintus. * Mis objetos "Enemy" no se paran ahora cuando colisionan unos con otros. */ skipCollide: true }); this.add("2d"); /** * Escucho el evento "hit" para poder gestionar las colisiones. */ this.on("hit"); }, hit: function(col) { /** * Si "Enemy" colisiona con el jugador, "Enemy" muere. */ if (col.obj.isA("Player")) { this.destroy(); } /** * Si "Enemy", colisiona con "Bullet", los dos mueren. */ else if (col.obj.isA("Bullet")) { this.destroy(); col.obj.destroy(); } } }); /** * Voy a crear un nuevo objeto "Bullet" extendiendo "MovingSprite". * Las propiedades por defecto son: * - Utilizar el sprite "bullet". * - El tipo es SPRITE_BULLET. * - Colisiona con SPRITE_ENEMY. * - "sensor: true" porque quiero definir mi propio comportamiento de colisiones. */ Q.MovingSprite.extend("Bullet", { init: function(p) { this._super(p, { sheet: "bullet", sprite: "bullet", type: SPRITE_BULLET, collisionMask: SPRITE_ENEMY, sensor: true }); /** * Añadiendo el componente 2d para activar la detección de colisiones y el movimiento. */ this.add("2d"); }, /** * Sobreescribo el método "step" para destruir las balas. * Cada bala fuera del canvas debe de ser destruida. Si no las destruyera, * la bala seguiría moviéndose y podría matar enemigos fuera del canvas * donde el jugador no viera nada. */ step: function(dt) { if (this.p.y < 0) { this.destroy(); } } }) Q.scene("level1", function(stage) { var player = stage.insert(new Q.Player({ x: Q.width/2, y: Q.height - 20 })); var num_enemies = Math.floor(Math.random() * 10 + 30); var enemies = new Array(num_enemies); /** * Algunos enemigos aparecían fuera del canvas. He cambiado entonces * este código para generar enemigos en el eje X entre 30px y 290px (Q.width-30px). */ var minX = 30; var maxX = Q.width - 30; for (var i=0; i <= num_enemies; i++) { enemies.push(stage.insert(new Q.Enemy({ x: Math.floor(Math.random() * (maxX - minX + 1)) + minX, y: -(Math.random() * 50) - (100*i), vy: Math.random() * 75 + 100 }))); } }); Q.load("sprites.png, sprites.json", function() { Q.compileSheets("sprites.png", "sprites.json"); Q.stageScene("level1"); }); }); </script> </body> </html> |
Esto ya empieza a ser un poco más divertido. Puedo moverme de izquierda a derecha y disparar a los enemigos. Las naves enemigas mueren al chocar conmigo y no se amontonan ya una encima de otra… Con todo esto ya sé:
- Mover un objeto en función de los controles pulsados por alguien.
- Gestionar colisiones entre objetos.
Lo siguiente que voy a hacer es utilizar sonidos y “animaciones”. Cambiaré la imagen de los enemigos por otra imagen simulando una explosión y pondré sonidos a los disparos y una música de fondo para ambientarlo todo. Además, crearé una interfaz de usuario en la que iré contabilizando el número de naves que el jugador ha destruido y cuantas quedan por aparecer.
Este artículo forma parte de una serie:
- ¿Cómo hacer un juego Shoo’Em Up con Quintus? – Parte 1
- ¿Cómo hacer un juego Shoo’Em Up con Quintus? – Parte 2
- ¿Cómo hacer un juego Shoo’Em Up con Quintus? – Parte 3
Las imágenes utilizadas son de SpriteLib.