Volvemos a nuestro juego, el clon marca blanca de R-Type que estamos montando con el canvas de html. Aquí tienes el post anterior por si quieres ponerte en situación:
También te dejo el repo por aquí, ya actualizado con las incorporaciones de esta segunda parte:
https://github.com/entorno5/canvas-game/
Antes de empezar con el tema de los enemigos, hablaremos del movimiento de la nave. En la anterior versión teníamos un movimento rudimentario. La hemos mejorado para el movimiento diagonal, usando variables para cada dirección:
let LEFT = false;
let RIGHT = false;
let UP = false;
let DOWN = false;
document.onkeydown = function(e) {
if (e.key === "o") LEFT = true;
if (e.key === "p") RIGHT = true;
if (e.key === "q") UP = true;
if (e.key === "a") DOWN = true;
if (e.key === " ") dispararBala();
}
document.onkeyup = function(e) {
if (e.key === "o") LEFT = false;
if (e.key === "p") RIGHT = false;
if (e.key === "q") UP = false;
if (e.key === "a") DOWN = false;
}
En esas variables, almacenamos si se está pulsando o no la tecla, disparando en el caso de que detecte la barra espaciadora. He cambiado las teclas de cursor por las míticas QAOP (recuerdo especial de mi Amstrad CPC 6128) porque, por algún motivo que desconozco, la combinación de flecha arriba e izquierda daba problemas.
Nos cargamos el listener de manejarTeclado, pues ya no nos hace falta, y a la hora de mover la nave, añadimos una función move() en el update, que sería esta:
function move() {
if(LEFT) {
nave.x -= VELOCIDAD_NAVE;
}
if(RIGHT) {
nave.x += VELOCIDAD_NAVE;
}
if(UP) {
nave.y -= VELOCIDAD_NAVE;
}
if(DOWN) {
nave.y += VELOCIDAD_NAVE;
}
}
Y así podemos gestionar el movimiento diagonal sin problema. Por cierto, en el update verás que hemos añadido una condición gameStarted: es para evitar que “pasen cosas” hasta que le demos al botón de start.
Vamos con los enemigos, que es la gracia de todo esto. ¿Qué necesitamos que hagan?:
- Que aparezcan (con su sprite correspondiente)
- Que se muevan hacia nosotros (en línea recta en este caso y lo haremos a diferentes velocidades)
- Que puedan ser destruidos por nuestras balas (que choquen con nuestra nave lo dejamos para la tercera entrega, donde gestionaremos las vidas).
Como serán varios enemigos los que podrán aparecer en pantalla, primero defino sus características:
let enemigos = [];
const VELOCIDAD_ENEMIGO_MIN = 1;
const VELOCIDAD_ENEMIGO_MAX = 3;
const MAX_ENEMIGOS_EN_PANTALLA = 3;
Siéntete libre de cambiar a tu gusto los valores. De hecho, podríamos crear niveles de dificultad que variaran esos valores según el nivel elegido, pero eso ya te dejo que lo hagas tú si te apetece.
Defino el sprite del enemigo al igual que hicimos con la nave:
const imagenEnemigo = new Image();
imagenEnemigo.src = 'img/enemigo.png';
Y ahora viene la mandanga, las funciones que llevan la lógica de los enemigos, llamando a las funciones moverEnemigos y dibujarEnemigos en nuestro draw:
function generarEnemigo() {
const nuevoEnemigo = {
x: canvas.width,
y: Math.random() * (canvas.height - nave.size),
size: 30,
speed: Math.random() * (VELOCIDAD_ENEMIGO_MAX - VELOCIDAD_ENEMIGO_MIN) + VELOCIDAD_ENEMIGO_MIN,
};
enemigos.push(nuevoEnemigo);
}
function dibujarEnemigos() {
for (const enemigo of enemigos) {
ctx.drawImage(imagenEnemigo, enemigo.x, enemigo.y, enemigo.size, enemigo.size);
}
}
function moverEnemigos() {
for (const enemigo of enemigos) {
enemigo.x -= enemigo.speed;
}
enemigos = enemigos.filter(enemigo => enemigo.x > -enemigo.size);
if (enemigos.length < MAX_ENEMIGOS_EN_PANTALLA && Math.random() < 0.02) {
generarEnemigo();
}
}
Básicamente hacemos lo siguiente:
- Movemos los enemigos, recorriendo el array de estos, cada uno con su velocidad (generada en generarEnemigo). Dejamos de tener en cuenta los que se salen de la pantalla, y comprobamos si “cabe” alguno más. Si es así, lo generamos.
- En la generación nada nuevo, le damos sus propiedades y lo añadimos al array de enemigos.
- Y dibujar, más de lo mismo: igual que con nuestra nave, pero recorriendo el array.
¿Qué nos falta? Las colisiones, comprobar si algún enemigo es tocado por alguna de nuetras balas. Para ello usamos esta función:
function colision(obj1, obj2) {
return (
obj1.x < obj2.x + obj2.size &&
obj1.x + obj1.size > obj2.x &&
obj1.y < obj2.y + obj2.size &&
obj1.y + obj1.size > obj2.y
);
}
Le pasamos dos objetos para ver si coinciden en el espacio en función de sus coordenadas y tamaño. ¿Cómo la usamos? En el draw:
for (let i = 0; i < balas.length; i++) {
for (let j = 0; j < enemigos.length; j++) {
if (colision(balas[i], enemigos[j])) {
// Eliminar la bala y el enemigo
balas.splice(i, 1);
enemigos.splice(j, 1);
// Sumar 20 puntos a la puntuación
puntuacion += 20;
// Salir del bucle interno, ya que una bala no puede golpear a múltiples enemigos
break;
}
}
}
Lo que hacemos es recorrer las balas y, para cada bala, recorrer los enemigos. Si hay colisión, eliminamos bala y enemigo correspondiente, sumando 20 puntos (también podría ser una variable).
¡Y listo! Ya puedes matar marcianitos a discreción. Sí, eres inmortal, pero eso lo arreglaremos en la tercera entrega de nuestro juego 😉
¡Hasta la semana que viene!