Sesion Laboratorio 10 Practica 3 3 - jesusgpa/2024-2025-CSAAI GitHub Wiki
- Tiempo: 2h
- Fecha: Jueves, 3 de Abril de 2025
-
Objetivos de la sesión:
- Añadir la destrucción de ladrillos
- Introducción
- Los ladrillos
- Ejemplo 1: Creación y dibujo de los ladrillos
- ¡A practicar!
- Resumen de tareas a realizar
- Conclusiones
- Autores
- Licencia
- Enlaces
Es el momento de añadir los ladrillos y completar nuestro juego.
En esta animación puedes ver una versión de este juego, con todos sus ladrillos y cómo se van destruyendo.
Para implementar los ladrillos debemos hacer las siguentes cosas:
- Definir su estructura y sus valores iniciales
- Crear la estructura inicial, inicializada correctamente
- Función para dibujar los ladrillos en función de su estado
- Función para detectar la colisiones entre la bola y los ladrillos
Los ladrillos son objetos que tienen las siguientes propiedades:
- Posición x,y: Dónde están colocados en la pantalla
- Dimensiones: w,h: Tamaño del ladrillo (anchura y altura)
- Relleno: Los ladrillos no están pegados unos a otros, sino que tienen espacio vacío alrededor (como la propiedad padding de los elementos html)
- Visible: Los ladrillos tiene dos estados: Visible y no visible. Así es como controlamos que se destruyan. Los ladrillos visibles están activos (comprobamos la física, se dibuja, etc). Los ladrillos NO visibles están inactivos
Además, estos ladrillos los disponemos en una organización rectangular de F filas y C columnas.
En el objeto constante LADRILLO colocamos todas estas propiedades y constantes.
Se podrían definir como constantes separadas, pero es mucho mejor agruparlas para que el código nos quede más legible.
Este es un ejemplo.
En tu juego tendrás que seleccionar los valores adecuados (esta estructura es de prueba para los ejemplos).
const LADRILLO = {
F: 2, //-- Filas
C: 3, //-- Columnas
w: 30, //-- Anchura
h: 20, //-- Altura
padding: 10, //-- Espacio alrededor del ladrillo
visible: true //-- Estado del ladrillo: activo o no
}
La estructura para almacenar ladrillos es un Array bidimensional, formado por las F filas y C columnas.
La construimos mediante bucles for anidados, que recorran todas las columnas y para cada columna las F filas.
A cada ladrillo habrá que asignarle sus valores constantes (definidos en LADRILLO) y sus posiciones x,y en la pantalla, que habrá que calcularlas en función de su fila y su columna.
Esta sería una forma de crearla:
//-- Creación de los ladrillos. La estructura se almacena
//-- en el objeto ladrillos, que inicialmente está vacío
const ladrillos = [];
//-- Recorrer todas las filas. La variable i toma valores de 0 hasta F-1 (número de filas)
for (let i = 0; i < LADRILLO.F; i++) {
ladrillo[i] = []; //-- Inicializar la fila. Las filas son a su vez Arrays que inicialmente están vacíos
//-- Recorrer las C columnas de la fila i. La variable j toma valores de 0 hasta C-1 (numero de columnas)
for (let j = 0; j < LADRILLO.C; j++) {
//-- Calcular valores para el ladrillo de la fila i y la columna j
//-- Algunos valores son constates. Otros depeden de i y j
ladrillos[i][j] = {
x: (LADRILLO.w + LADRILLO.padding) * j,
y: (LADRILLO.h + LADRILLO.padding) * i,
w: LADRILLO.w,
h: LADRILLO.h,
padding: LADRILLO.padding,
visible: LADRILLO.visible
};
}
}
Para dibujar los ladrillos habrá que recorrer esta estructura y si el ladrillo situado en (i,j) es visible, entonces se dibujará.
//-- Dibujar ladrillos
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
//-- Si el ladrillo es visible se pinta
if (ladrillos[i][j].visible) {
ctx.beginPath();
ctx.rect(ladrillos[i][j].x, ladrillos[i][j].y, LADRILLO.w, LADRILLO.h);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
}
}
Para detectar si la bola ha colisionado con algún ladrillo habrá que construir una función que por cada nueva posición de la bola copruebe si ha contactado con algún ladrillo.
Si es así, el ladrillo tendrá que pasar al estado *no visible.
La comprobación de la colisión sólo se realiza para los ladrillos visibles.
El esquema de esta función es el siguiente:
* Recorrer todas las filas y columnas de ladrillos
* Si el ladrillo (i,j) es visible:
* Si la bola está en contacto con el ladrillo (i,j):
* Hacer que el ladrillo (i,j) pase a no visible
* Cambiar la velocidad de la bola según el choque (rebote)
En este ejemplo se muestra cómo dibujar una formación de F filas de ladrillos situados en C columnas.
Inicialmente F=2 y C=3.
Para comprobar que funciona la propiedad de visibilidad, una vez creada la estructura de ladrillos el ladrillo situado en la fila 0 y columna 1 se hace invisible:
ladrillos[0][1].visible = false;
Al dibujar a formación este ladrillo NO aparecerá.
- Fichero Ej-01.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="Ej-01.js" defer></script>
<title>Ejemplo 1</title>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
- Fichero Ej-01js:
const canvas = document.getElementById("canvas");
canvas.width = 300;
canvas.height = 100;
const ctx = canvas.getContext("2d");
//-- Constantes de los ladrillos
const LADRILLO = {
F: 2, // Filas
C: 3, // Columnas
w: 30,
h: 20,
origen_x: 0,
origen_y: 0,
padding: 5,
visible: true
};
//-- Estructura de los ladrillos
const ladrillos = [];
for (let i = 0; i < LADRILLO.F; i++) {
ladrillos[i] = [];
for (let j = 0; j < LADRILLO.C; j++) {
ladrillos[i][j] = {
x: (LADRILLO.w + LADRILLO.padding) * j,
y: (LADRILLO.h + LADRILLO.padding) * i,
w: LADRILLO.w,
h: LADRILLO.h,
padding: LADRILLO.padding,
visible: LADRILLO.visible
};
}
}
ladrillos[0][1].visible = false;
//-- Dibujar ladrillos
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
//-- Si el ladrillo es visible se pinta
if (ladrillos[i][j].visible) {
ctx.beginPath();
ctx.rect(ladrillos[i][j].x, ladrillos[i][j].y, LADRILLO.w, LADRILLO.h);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
}
}
Esto es lo que obtenemos:
Vemos que el ladrillo (0,1) no aparece.
Jugando con las propiedades de los ladrillos, podemos cambiar su tamaño. Y también podemos cambiar el número de filas y columnas que queremos mostrar, y su posición inicial respecto del canvas.
- breakout.hmtl
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="breakout-01.js" defer></script>
<title>BreackOut</title>
<link rel="stylesheet" href="breakout.css">
</head>
<body>
<h1>BreakOut</h1>
<canvas id="canvas"></canvas>
</body>
</html>
- breakout.css
body {
text-align: center;
background-color: black;
color: white;
font-family: Arial, Helvetica, sans-serif;
}
canvas {
background-color: #111;
border: 2px solid white;
display: block;
margin: auto;
}
- breakout-01.js
const canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
const ctx = canvas.getContext("2d");
//-- Constantes de los ladrillos
const LADRILLO = {
F: 3, // Filas
C: 8, // Columnas
w: 60,
h: 20,
origen_x: 50,
origen_y: 50,
padding: 5,
visible: true
};
//-- Estructura de los ladrillos
const ladrillos = [];
//-- Crear los ladrillos
for (let i = 0; i < LADRILLO.F; i++) {
ladrillos[i] = [];
for (let j = 0; j < LADRILLO.C; j++) {
ladrillos[i][j] = {
x: LADRILLO.origen_x + ((LADRILLO.w + LADRILLO.padding) * j),
y: LADRILLO.origen_y + ((LADRILLO.h + LADRILLO.padding) * i),
w: LADRILLO.w,
h: LADRILLO.h,
padding: LADRILLO.padding,
visible: LADRILLO.visible
};
}
}
//ladrillos[0][1].visible = false;
//-- Dibujar ladrillos
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
//-- Si el ladrillo es visible se pinta
if (ladrillos[i][j].visible) {
ctx.beginPath();
ctx.rect(ladrillos[i][j].x, ladrillos[i][j].y, LADRILLO.w, LADRILLO.h);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
}
}
Y para hacer que todos los ladrillos se muevan a la vez ¿Cómo lo hacemos?.
- Añadir velocidad y dirección:
Se incluye brickSpeed y brickDirection para mover los ladrillos horizontalmente.
- Detectar colisiones con bordes:
Antes de mover los ladrillos, se comprueba si alguno va a salirse del canvas.
Si ocurre, se cambia la dirección multiplicando brickDirection por -1.
- Actualizar posiciones:
En cada frame, se mueve la posición x de todos los ladrillos.
- Animación continua:
Se encapsula todo en una función update() que usa requestAnimationFrame para actualizar constantemente.
- Separación en funciones:
drawBricks() para pintar los ladrillos.
draw() para limpiar y redibujar todo.
moveBricks() para manejar el movimiento.
- breakout-03.js
const canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
const ctx = canvas.getContext("2d");
//-- Constantes de los ladrillos
const LADRILLO = {
F: 3, // Filas
C: 8, // Columnas
w: 60,
h: 20,
origen_x: 50,
origen_y: 50,
padding: 5,
visible: true,
brickSpeed: 2
};
//-- Dirección de los ladrillos
let brickDirection = 1; // 1 = derecha, -1 = izquierda
//-- Estructura de los ladrillos
const ladrillos = [];
//-- Crear los ladrillos
for (let i = 0; i < LADRILLO.F; i++) {
ladrillos[i] = [];
for (let j = 0; j < LADRILLO.C; j++) {
ladrillos[i][j] = {
x: LADRILLO.origen_x + ((LADRILLO.w + LADRILLO.padding) * j),
y: LADRILLO.origen_y + ((LADRILLO.h + LADRILLO.padding) * i),
w: LADRILLO.w,
h: LADRILLO.h,
padding: LADRILLO.padding,
visible: LADRILLO.visible
};
}
}
//-- Dibujar ladrillos
function drawBricks() {
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
const brick = ladrillos[i][j];
if (brick.visible) {
ctx.beginPath();
ctx.rect(brick.x, brick.y, brick.w, brick.h);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
}
}
}
//-- Mover ladrillos
function moveBricks() {
let changeDirection = false;
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
const brick = ladrillos[i][j];
if (brick.visible) {
const nextX = brick.x + LADRILLO.brickSpeed * brickDirection;
if (nextX < 0 || nextX + brick.w > canvas.width) {
changeDirection = true;
break;
}
}
}
if (changeDirection) break;
}
if (changeDirection) {
brickDirection *= -1;
}
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
ladrillos[i][j].x += LADRILLO.brickSpeed * brickDirection;
}
}
}
//-- Dibujar todo
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
}
//-- Bucle principal
function update() {
moveBricks();
draw();
requestAnimationFrame(update);
}
update();
En esta versión añadimos una pelota en movimiento que permite probar la detección de colisiones con los ladrillos. El objetivo es demostrar cómo se puede detectar el impacto de un objeto con otros elementos del juego que también están en movimiento.
-
La pelota rebota en los cuatro bordes del canvas.
-
Al colisionar con un ladrillo visible, este desaparece (se marca como no visible).
-
El rebote es simple: la pelota cambia su dirección vertical (dy *= -1).
-
Se mantiene el movimiento horizontal conjunto de todos los ladrillos.
Este ejemplo sirve como base para introducir disparos, proyectiles o físicas simples en el futuro desarrollo del juego.
- breakout-04.js
const canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
const ctx = canvas.getContext("2d");
//-- Constantes de los ladrillos
const LADRILLO = {
F: 3, // Filas
C: 8, // Columnas
w: 60,
h: 20,
origen_x: 50,
origen_y: 50,
padding: 5,
visible: true,
brickSpeed: 2
};
//-- Dirección de los ladrillos
let brickDirection = 1; // 1 = derecha, -1 = izquierda
//-- Estructura de los ladrillos
const ladrillos = [];
//-- Crear los ladrillos
for (let i = 0; i < LADRILLO.F; i++) {
ladrillos[i] = [];
for (let j = 0; j < LADRILLO.C; j++) {
ladrillos[i][j] = {
x: LADRILLO.origen_x + ((LADRILLO.w + LADRILLO.padding) * j),
y: LADRILLO.origen_y + ((LADRILLO.h + LADRILLO.padding) * i),
w: LADRILLO.w,
h: LADRILLO.h,
padding: LADRILLO.padding,
visible: LADRILLO.visible
};
}
}
//-- Pelota
const ball = {
x: canvas.width / 2,
y: canvas.height - 50,
r: 8,
dx: 3,
dy: -3
};
function drawBall() {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
ctx.closePath();
}
function moveBall() {
ball.x += ball.dx;
ball.y += ball.dy;
// Rebotar en paredes
if (ball.x + ball.r > canvas.width || ball.x - ball.r < 0) {
ball.dx *= -1;
}
if (ball.y - ball.r < 0 || ball.y + ball.r > canvas.height) {
ball.dy *= -1;
}
// Colisión con ladrillos
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
const brick = ladrillos[i][j];
if (brick.visible) {
if (
ball.x > brick.x &&
ball.x < brick.x + brick.w &&
ball.y > brick.y &&
ball.y < brick.y + brick.h
) {
brick.visible = false;
ball.dy *= -1;
}
}
}
}
}
//-- Dibujar ladrillos
function drawBricks() {
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
const brick = ladrillos[i][j];
if (brick.visible) {
ctx.beginPath();
ctx.rect(brick.x, brick.y, brick.w, brick.h);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
}
}
}
}
//-- Mover ladrillos
function moveBricks() {
let changeDirection = false;
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
const brick = ladrillos[i][j];
if (brick.visible) {
const nextX = brick.x + LADRILLO.brickSpeed * brickDirection;
if (nextX < 0 || nextX + brick.w > canvas.width) {
changeDirection = true;
break;
}
}
}
if (changeDirection) break;
}
if (changeDirection) {
brickDirection *= -1;
}
for (let i = 0; i < LADRILLO.F; i++) {
for (let j = 0; j < LADRILLO.C; j++) {
ladrillos[i][j].x += LADRILLO.brickSpeed * brickDirection;
}
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
}
function update() {
moveBricks();
moveBall();
draw();
requestAnimationFrame(update);
}
update();
Para implementar los ladrillos en tu juego se proponen los siguientes ejercicios.
Estudia el ejemplo 1 de esta sesión para entender cómo funciona.
Adáptalo para dibujar mayor cantidad de ladrillos y en una posición diferente (ya que estos ladrillos se dibuja a partir de la posición (0,0)).
Integralo en tu juego para que se dibujen (aunque no haya colisiones todavía).
En este momento tendrás una bola que rebota en las paredes y la raqueta, pero que pasa por encima de los ladrillos.
Deberás implementar la funcioń de detección de colisión con la bola con los ladrillos e integrarla en tu juego.
Para que la detección de colisiones sea más fácil, parte primero de una bola pequeña y realiza las comprobaciones sólo con las coordenadas de su centro.
Cuando ya te funcione, modifícalo para que la comprobación se haga tendiendo en cuenta el radio de la bola (aunque la bola sea redonda, supón para las colisiones que es un cuadrado).
Una vez que ya tengas la colisión hecha, ya casi tendrás el juego listo.
Sólo te faltarán los toques finales: incrementar el número de puntos al romper un ladrillo, detectar cuándo has terminado la partida (cuando ya no haya más ladrillos visibles), etc.
Repasa las especificaciones del práctica para implementar todo lo pedido.
Cuando ya tengas el juego funcionando según las especificacioens, es el momento de que hagas mejoras si te apetece y quieres subir nota.
¡La creatividad al poder!
- Haz los ejercicios propuestos en esta sesión. No olvides subir todas las pruebas que hagas al repo, en la carpeta P3.
Aunque sean pruebas temporales que luego no formen parte del juego, súbelas.
Son una prueba objetiva de tu trabajo y de tus "horas de vuelo".
Con lo que se ha indicado en esta sesión ya deberías ser capaz de terminar tu juego.
Jesús Parrado Alameda (jesusgpa)
- Creado a partir del contenido generado por el profesor Jose María Cañas
- Creado a partir del contenido generado por el profesor Juan Gonzalez-Gomez