Esta es la segunda parte de una serie de artículos en la que explico cómo desarrollo el juego clon de Breakout. En esta segunda parte, explicaré cómo crear la pala, registrar los movimientos del usuario e implementar las colisiones entre pala-paredes y pala-bola.
La clase Paddle
En la primera parte, he creado el html base y la bola. De la misma forma que creé la bola, voy a crear un archivo con una clase Paddle. En esta clase pondré los métodos y propiedades que tendrá mi pala. Primero, empezaré por definir el constructor y el método «draw» para dibujar la pala.
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 |
// Constructor de la pala. function Paddle (x, y, width, height, color) { // Valores por defecto. var DEFAULTS = { x: 185, y: 425, width: 50, height: 10, color: '#ff0000', speed: 5 }; // Asignación de valores si existen. this.x = (x == undefined) ? DEFAULTS.x : x; this.y = (y == undefined) ? DEFAULTS.y : y; this.width = (width == undefined) ? DEFAULTS.width : width; this.height = (height == undefined) ? DEFAULTS.height : height; this.color = (color == undefined) ? DEFAULTS.color : color; // move_left y move_right se utilizarán para comprobar la dirección // en la que mover la pala. this.move_left = false; this.move_right = false; // Velocidad a la que se moverá la pala. this.speed = DEFAULTS.speed; this.vX = 0; // Iniciamos los eventos de teclado. this.initEvents(); console.log('¡Pala creada!'); }; Paddle.prototype = { constructor: Paddle, // Método initEvents que se encarga de iniciar la captura de eventos. initEvents: function () { // Controles del paddle. var KEYS = { left: 37, right: 39 }; var self = this; // Si el usuario pulsa una tecla. utils.addListener(window, 'keydown', function(e) { // Tecla flecha izquierda. if (e.keyCode == KEYS.left) { self.move_left = true; self.update(); } // Tecla flecha derecha. else if (e.keyCode == KEYS.right) { self.move_right = true; self.update(); } }); // Si el usuario suelta una tecla. utils.addListener(window, 'keyup', function(e) { // Tecla flecha izquierda. if (e.keyCode == KEYS.left) { self.move_left = false; self.update(); } // Tecla flecha derecha. else if (e.keyCode == KEYS.right) { self.move_right = false; self.update(); } }); }, // Método draw que se encarga de dibujar la pala. draw: function (context) { context.fillStyle = this.color; context.fillRect(this.x, this.y, this.width, this.height); }, // Método update que se encarga de actualizar la posición de la pala. update: function () { // Si el movimiento es hacia la izquierda y vX es positivo. if (this.move_left && this.vX >= 0) { // Cambio el signo de vX. this.vX = this.speed * -1; } // Si el movimiento es a la derecha y vX es negativo. else if (this.move_right && this.vX <= 0) { // Cambio el signo de vX. this.vX = this.speed; } // Si no hay que ir ni a izquierda ni a derecha. vX = 0. if (!this.move_left && !this.move_right) { this.vX = 0; } // Actualizamos la posición en el eje X. this.x += this.vX; }, // Método checkCollisions que se encarga de gestionar las colisiones entre bola y paredes. checkCollisions: function(canvas) { if (canvas === undefined) { return; }; // Comprobar si colisiona con pared derecha. if ((this.x + this.width) >= canvas.width) { this.x = canvas.width - this.width; } // Comprobar si colisiona con pared izquierda. else if (this.x <= 0) { this.x = 0; }; } }; |
En el constructor, hago una llamada al método «initEvents». Este método se encarga de inicializar la captura de eventos detectados en el teclado. Cuando el usuario pulsa una tecla y suelta una tecla se lanzará un evento. Asocié el cambio de estado de las propiedades «move_left» y «move_right» al pulsar las teclas izquierda y derecha. Además hago una llamada al método «update» para actualizar la dirección y la posición de la pala. Cuando el usuario suelta la tecla, vuelvo a actualizar las propiedades para detener el movimiento de la pala. Para este método, he creado un nuevo archivo llamado «utils.js» en el que voy a guardar funciones útiles que pueda necesitar a lo largo del desarrollo. Podéis echar un vistazo al capítulo 4, página 75 del libro Javascript Patterns si queréis entender qué significa ese código.
El método «draw» se encarga de dibujar la pala. El método «update» se encarga de actualizar la dirección en la que se moverá la pala según el valor contenido en las propiedades «move_left» y «move_right» además de la posición de la misma. El método «checkCollisions» se encarga de comprobar si la pala colisiona con las paredes laterales para evitar que salga del campo de visión del usuario.
Una vez que tengo lista la clase Paddle, necesito hacer algunos cambios en el archivo «game.js» y el html.
Cambios en game.js
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 |
(function() { // Capturo el canvas donde voy a dibujar. var canvas = document.getElementById('canvas'); // Capturo el contexto 2d. var context = canvas.getContext('2d'); // Creo una bola. var ball = new Ball(); // Creo una pala. var paddle = new Paddle(); // Inicio el bucle. gameloop(); // Bucle del juego. function gameloop() { window.requestAnimationFrame(gameloop); // Actualizar posiciones. updateAll(); // Comprobar colisiones. checkCollisions(); // Dibujar todo. drawAll(); } function updateAll() { // Actualizar posiciones... // Actualizar la posición de la bola. ball.update(); // Actualizar la posición de la pala. paddle.update(); } function checkCollisions() { // Comprobar colisiones... // Comprobar colisiones bola-paredes. ball.checkCollisions(canvas); // Comprobar colisiones pala-paredes. paddle.checkCollisions(canvas); } function drawAll() { // Dibujar todo... // Borro el canvas. context.clearRect(0, 0, canvas.width, canvas.height); // Dibujar bola. ball.draw(context); // Dibujar pala. paddle.draw(context); } }()); |
En la primera parte, había hablado de gestionar las acciones de los usuarios en el bucle del juego. Esto no es necesario hacerlo a parte ya que lo gestiono directamente desde el método update de la pala. Creo la pala y hago las llamadas a los métodos «draw», «update» y «checkCollisions».
En el html, sólo debo incluir los dos nuevos scripts de javascript que he creado: utils.js y paddle.js.
Colisión entre la pala y la bola
Esto es lo último que me queda para terminar esta segunda parte. Este código debe estar en la clase bola ya que es la que va a sufrir un cambio de dirección. La pala no se va a ver afectada por chocar con la bola. Por lo tanto, voy a implementar esto en la clase bola en el método «checkCollisions». Voy a pasar como argumento la pala para poder calcular la colisión.
Para calcular la colisión con la pala, debo realizar las siguientes comprobaciones:
- El punto más bajo de la bola debe de estar a la misma altura que la pala. Es decir la coordenada y del punto más bajo de la bola, debe de ser mayor o igual que la coordenada y de la pala y menor o igual que la coordenada y sumado al alto de la pala. Si no fuera así, la bola rebotaría a cualquier altura, sólo estando en una coordenada x contenida en la pala.
- Para saber si la bola está tocando la pala, debemos comprobar si cualquier punto de la bola está contenido en el rango de coordenadas x de la pala de un extremo a otro.
Este es el código de «checkCollisions» de la clase Ball:
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 |
// Método checkCollisions que se encarga de comprobar las colisiones. checkCollisions: function(canvas, paddle) { if (canvas !== undefined) { // Colisión con la pared derecha o izquierda. if ( ((this.x + this.radius) >= canvas.width) || ((this.x - this.radius) <= 0) ) { if ((this.x - this.radius) <= 0) { this.x = this.radius; } else { this.x = canvas.width - this.radius; } this.angle = 180 - this.angle; this.update(); } // Colisión con la pared inferior o superior. else if ( ((this.y + this.radius) >= canvas.height) || ((this.y - this.radius) <= 0) ) { if ((this.y - this.radius) <= 0) { this.y = this.radius; } else { this.y = canvas.height - this.radius; } this.angle = 360 - this.angle; this.update(); } } if (paddle !== undefined) { // Colisión con la pala. if ( ((this.y + this.radius) >= paddle.y) && ((this.y + this.radius) <= (paddle.y + paddle.height)) ) { if ( (((this.x + this.radius) >= paddle.x) && ((this.x + this.radius) <= (paddle.x + paddle.width))) || (((this.x - this.radius) >= paddle.x) && ((this.x - this.radius) <= (paddle.x + paddle.width))) ) { this.y = paddle.y - this.radius; this.angle = 360 - this.angle; this.update(); } } } } |
Para que esto funcione, debo pasar como argumento la pala en la llamada al método «checkCollisions» desde game.js.
1 2 3 4 5 6 7 |
function checkCollisions() { // Comprobar colisiones... // Comprobar colisiones bola-paredes y bola-pala. ball.checkCollisions(canvas, paddle); // Comprobar colisiones pala-paredes. paddle.checkCollisions(canvas); } |
¡Listo! Ahora nuestra pala hace que la bola rebote.
Aquí os dejo la demostración del juego en esta segunda parte: VER DEMO
Algunas de las fuentes que he utilizado:
Este artículo forma parte de una serie:
- Introducción: Another Breakout game…
- Diario Breakout – Parte 1
- Diario Breakout – Parte 2
- Diario Breakout – Parte 3
- Diario Breakout – Parte 4
- Diario Breakout – Parte 5 y ¿última?
- Diario Breakout – Integrar Clay.io
He subido Breakout a GitHub.