5
Juegos

Bubble Shooter Game

Juego arcade de disparos donde apuntas y lanzas burbujas para alcanzar la mayor puntuación posible.

Bubble Shooter Game

Descripción

Bubble Shooter Game es un dinámico juego arcade de disparos que desafía a los jugadores a apuntar y lanzar proyectiles para golpear burbujas objetivo y lograr la mayor puntuación. El juego presenta elementos que se mueven constantemente por la pantalla, algunos siendo objetivos y otros obstáculos. Incluye un sistema de puntuación que rastrea aciertos y fallos, ofrece recompensas por rachas de golpes y culmina en una condición de 'Game Over'. Con efectos de sonido inmersivos y una interfaz de usuario clara que muestra las estadísticas del juego, este proyecto ofrece una experiencia de juego atractiva y de ritmo rápido.

Características clave

  • Mecánica de Disparo Interactiva:

    Los jugadores hacen clic para 'disparar' proyectiles que dejan una marca visual en el lugar del impacto.

  • Objetivos Dinámicos:

    Las 'burbujas' (representadas por cuadrados con imágenes) se mueven constantemente por la pantalla, con algunas designadas como objetivos a disparar y otras como obstáculos.

  • Sistema de Puntuación:

    Lleva un registro de los disparos totales, aciertos (hits) y fallos (misses).

  • Recompensa por Aciertos Consecutivos:

    Los jugadores reciben tiros adicionales por una racha de 5 aciertos.

  • Manejo de Juego (Game Over):

    El juego termina al quedarse sin disparos o al alcanzar un límite de fallos.

  • Efectos de Sonido:

    Incluye sonidos para los disparos y explosiones al impactar, mejorando la inmersión.

  • Controles de Audio:

    Botón para silenciar/activar los sonidos del juego.

  • Interfaz de Usuario:

    Muestra el tiempo transcurrido, los disparos restantes, los aciertos y los fallos en un tablero de puntuación.

  • Guía de Juego:

    Un botón 'How to Play' que muestra/oculta una ventana con instrucciones.

Aspectos destacados del código

  • Generación Dinámica de Elementos del Juego:

    La función CreateSquares crea y añade un número determinado de div elementos al DOM (containerAnimation), que representan los objetivos y obstáculos en movimiento. Esto permite que el juego genere una cantidad controlada de elementos visuales.

  • function CreateSquares(numberSquares) {
        let html = "<div></div>";
        for (let index = 0; index < numberSquares; index++) {
            containerAnimation.insertAdjacentHTML("afterbegin", html);
        }
        AddSizeAndStylesToSquares();
    }
  • Estilos y Animaciones CSS para Movimiento:

    La función AddSizeAndStylesToSquares asigna tamaños, posiciones iniciales, imágenes de fondo y múltiples propiedades de animación CSS (animationName, animationDuration, animationDelay, animationIterationCount, animationTimingFunction) a cada cuadrado, creando su movimiento continuo desde abajo hacia arriba y reaparición.

  • function AddSizeAndStylesToSquares() {
        for (let index = 0; index < containerAnimation.childElementCount; index++) {
            let sizeSquare = GetRandomNumber(3, 7);
            let leftSquare = GetRandomNumber(10, 85);
            // ... otras variables aleatorias ...
            containerAnimation.children[index].style.width = `${sizeSquare}vw`;
            containerAnimation.children[index].style.height = `${sizeSquare}vw`;
            containerAnimation.children[index].style.animationName = `animateSquares${animationnumber}`;
            // ... otras propiedades de estilo y animación ...
        }
    }
  • Manejo de Clics y Generación de Marcas de Disparo:

    Un addEventListener global para mousedown detecta los clics del usuario en la pantalla. Si es un disparo válido, crea dinámicamente un nuevo elemento div (bulletMark) que representa la marca del disparo, lo posiciona según las coordenadas del clic y le aplica estilos aleatorios antes de eliminarlo después de un tiempo.

  • const bullet = document.addEventListener('mousedown', function (e) {
        if (e.clientY > 115 && totalShots && !gameOver) {
            const newChild = document.createElement("div");
            newChild.className = "bulletMark";
            // ... lógica de posicionamiento, estilos aleatorios y remoción ...
            totalShots--;
            UpdateStats();
        }
        if (e.clientY > 115) {
            soundPainballShoot.currentTime = 0;
            soundPainballShoot.play();
        }
        if (!totalShots) {
            GameOver();
        }
    });
  • Lógica de Aciertos, Fallos y Puntuación:

    La función ComponentChange se activa cuando se hace clic en un cuadrado. Determina si el cuadrado clicado es el objetivo correcto comparando sus imágenes de fondo. Si es un acierto, incrementa totalhits y llama a ParticlesExplosion. Si es un fallo (o un no-objetivo), incrementa totalmisses y verifica la condición de 'Game Over'. También gestiona la transparencia y desaparición del cuadrado impactado.

  • function ComponentChange(e) {
        if (!gameOver) {
            // ... posicionamiento de la explosión ...
            if (e.target.style.backgroundImage.replace(/^url\(|\)$|"/g, '').replace('.', '') == targetImg.src.substring(targetImg.src.indexOf('/images')).replace(' ', '') && e.target.style.backgroundColor != "transparent") {
                totalhits++;
                ParticlesExplosion();
                CountHits(true); // Recompensa por aciertos consecutivos
            } else {
                totalmisses++;
                if (totalmisses == maxMisses) GameOver();
                ParticlesExplosion();
                CountHits(false);
            }
            // ... lógica de ocultamiento del cuadrado impactado ...
        }
    }
  • Detección de Colisiones para Fallos por Paso:

    La función checkCollision utiliza requestAnimationFrame para un bucle de juego continuo. Compara las posiciones de los cuadrados móviles con la del marcador (scoreboard). Si un cuadrado objetivo (targetImg.src) colisiona con el marcador sin haber sido disparado, se registra como un fallo (totalmisses), simulando que el enemigo ha 'escapado'.

  • function checkCollision() {
        for (let i = 0; i < containerAnimation.childElementCount; i++) {
            const childElement = containerAnimation.children[i];
            if (elementStates[i].hasCollided) { continue; }
            const childElementPosition = childElement.getBoundingClientRect();
            const scoreboardPosition = scoreboard.getBoundingClientRect();
            if (childElementPosition.left < scoreboardPosition.right &&
                childElementPosition.right > scoreboardPosition.left &&
                childElementPosition.top + childElementPosition.height < scoreboardPosition.bottom &&
                childElementPosition.bottom > scoreboardPosition.top) {
                if (containerAnimation.children[i].style.backgroundImage.replace(/^url\(|\)$|"/g, '').replace('.', '') === targetImg.src.substring(targetImg.src.indexOf('/images')).replace(' ', '')) {
                    totalmisses++;
                    if (totalmisses == maxMisses) GameOver();
                    UpdateStats();
                }
                elementStates[i].hasCollided = true;
            }
        }
        requestAnimationFrame(checkCollision);
    }
    requestAnimationFrame(checkCollision);
  • Gestión del Estado del Juego y 'Game Over':

    La función GameOver se activa cuando se cumplen las condiciones de fin de juego (munición agotada o demasiados fallos). Detiene las animaciones de todos los cuadrados, los oculta de la pantalla con un efecto visual y muestra la tarjeta de 'Game Over' al jugador.

  • async function GameOver() {
        if (!gameOver) {
            soundExplosion.currentTime = 0;
            for (let index = 0; index < containerAnimation.childElementCount; index++) {
                containerAnimation.children[index].style.animationPlayState = "paused";
            }
            for (let index = 0; index < containerAnimation.childElementCount; index++) {
                containerAnimation.children[index].style.top = "1000px"; // Mueve los elementos fuera de vista
                containerAnimation.children[index].style.boxShadow = "none";
                containerAnimation.children[index].style.backgroundColor = "transparent";
                soundExplosion.play();
                await sleep(50);
            }
        }
        gameOverCard.style.transform = "translateX(0%)"; // Muestra la tarjeta de Game Over
        gameOver = true;
    }
  • Control de Audio:

    El botón de silencio (mutedButton) permite al usuario activar o desactivar los efectos de sonido del juego (soundPainballShoot, soundExplosion). La lógica cambia la propiedad muted de los elementos de audio y actualiza visualmente el estado del botón.

  • mutedButton.children[0].addEventListener('mousedown', function () {
        const styleElement = getComputedStyle(mutedButton.children[0], null);
        let translateElement = styleElement.getPropertyValue("transform");
        if (translateElement == "none" || translateElement == "matrix(1, 0, 0, 1, 0, 0)") {
            mutedButton.children[0].style.transform = "translateX(35px)";
            // ... actualizar estilos para 'muteado' ...
            soundPainballShoot.muted = true;
            soundExplosion.muted = true;
        } else {
            mutedButton.children[0].style.transform = "translateX(0px)";
            // ... actualizar estilos para 'no muteado' ...
            soundPainballShoot.muted = false;
            soundExplosion.muted = false;
        }
    })
  • Lógica de Reaparición de Objetivos y Aleatoriedad:

    Un evento animationiteration en los cuadrados permite que, al finalizar cada ciclo de animación (cuando desaparecen por la parte superior de la pantalla), reaparezcan desde abajo con nuevas posiciones, tamaños y, crucialmente, una nueva imagen de fondo (pueden ser objetivos o no) de forma aleatoria, manteniendo el juego dinámico.

  • for (let index = 0; index < containerAnimation.children.length; index++) {
        containerAnimation.children[index].addEventListener("animationiteration", function () {
            let leftSquare = GetRandomNumber(10, 80);
            let backgroundSquare = GetRandomNumber(1, 41);
            let sizeSquare = GetRandomNumber(3, 7);
            // ... resetear opacidad, posición, tamaño y estilos ...
            if (index > 5 && index < 13) { // Asignar ciertos índices como objetivos
                containerAnimation.children[index].style.backgroundImage = `url('./images/comp_${imageTargetID}.svg')`;
            } else if (index > 25 && index < 33) {
                containerAnimation.children[index].style.backgroundImage = `url('./images/comp_${imageTargetID}.svg')`;
            } else { // El resto son objetivos aleatorios
                containerAnimation.children[index].style.backgroundImage = `url('./images/comp_${backgroundSquare}.svg')`;
            }
            containerAnimation.children[index].style.scale = 1;
            elementStates[index].hasCollided = false;
        });
    }

Desafíos técnicos

  • Gestión de Animaciones DOM a Gran Escala:

    Mantener animaciones CSS fluidas y sincronizadas para múltiples elementos moviéndose concurrentemente por la pantalla, lo que exige una consideración cuidadosa del rendimiento del navegador para evitar lag.

  • Detección de Colisiones Bidireccional:

    Implementar una lógica precisa para detectar cuándo los clics del usuario impactan un objetivo (hit detection) y, por otro lado, cuándo los objetivos no impactados 'escapan' al alcanzar un área específica de la pantalla (miss detection).

  • Sincronización de Eventos de Juego Complejos:

    Coordinar los disparos, impactos, explosiones, sonidos, actualizaciones del marcador y la lógica de reaparición de objetivos de manera que todos los componentes del juego se sientan cohesivos y responsivos.

  • Manejo Eficiente de Recursos Multimedia:

    Cargar y reproducir efectos de sonido instantáneamente (soundExplosion, soundPainballShoot) al ocurrir los eventos del juego sin introducción de latencia o fallos.

  • Lógica de Flujo de Juego y Estado:

    Implementar un sistema robusto para gestionar el estado del juego (activo, pausado, game over), las condiciones de victoria/derrota, y las recompensas por racha de aciertos de manera lógica y sin errores.

  • Generación Aleatoria Controlada:

    Asegurar que los objetivos aparezcan de forma aleatoria pero con una probabilidad adecuada para mantener el juego desafiante y justo, y que los elementos no objetivo actúen como distracciones efectivas.

Aprendizajes obtenidos

  • Diseño de Bucle de Juego en el Navegador:

    Aprendí a estructurar la lógica de un juego arcade web, utilizando setInterval para el temporizador principal y requestAnimationFrame para la gestión de animaciones y colisiones, creando un bucle de juego eficiente.

  • Optimización del Rendimiento en Animaciones DOM:

    Gane experiencia en cómo manipular un gran número de elementos DOM y sus estilos CSS para crear animaciones complejas y fluidas sin sacrificar el rendimiento, lo cual es crítico en juegos.

  • Implementación de Sistemas de Puntuación:

    Comprendí cómo diseñar y mantener un sistema de puntuación que no solo registre aciertos y fallos, sino que también incorpore mecánicas de recompensa (disparos adicionales por racha) para mejorar la jugabilidad.

  • Manejo Avanzado de Eventos y Estados:

    Reforcé mis habilidades en la programación reactiva a eventos, manejando múltiples tipos de interacciones del usuario y cambios de estado del juego para actualizar la interfaz y la lógica de forma dinámica.

  • Integración Efectiva de Audio:

    Adquirí práctica en cómo añadir y controlar efectos de sonido en una aplicación web, mejorando la inmersión del usuario y la retroalimentación del juego.

  • Desarrollo de Experiencias de Usuario Intuitivas:

    Entendí la importancia de proveer instrucciones claras (buttonHowToPlay) y retroalimentación visual y auditiva para una experiencia de usuario atractiva y fácil de entender.

Posibles mejoras / Ideas a implementar

  • Múltiples Niveles de Dificultad:

    Implementar niveles que aumenten la velocidad de los objetivos, reduzcan la munición inicial o modifiquen el límite de fallos.

  • Diferentes Tipos de Objetivos:

    Introducir objetivos con diferentes valores de puntuación, tamaños o patrones de movimiento.

  • Poderes (Power-ups):

    Añadir elementos especiales que, al ser disparados, otorguen ventajas temporales (ej., cámara lenta, disparos infinitos, bonificación de puntos).

  • Pantallas de Inicio y Fin de Juego:

    Desarrollar pantallas más elaboradas para el inicio del juego (con opciones de configuración) y el final (mostrando la puntuación final y opción para reiniciar).

  • Almacenamiento de Puntuaciones Altas:

    Implementar localStorage para guardar las mejores puntuaciones de los jugadores localmente.

  • Optimización Adicional de Rendimiento:

    Investigar el uso de Web Workers o Canvas para el renderizado de un número mucho mayor de elementos, si el juego escalara en complejidad.

  • Controles Adaptados a Dispositivos Táctiles:

    Optimizar la experiencia de juego para dispositivos móviles con eventos táctiles.

Información

Juegos

2023-04-04

JavaScript

Etiquetas

gamecanvasbubble shootercolor matcharcadeanimationevent handlingscoringsoundUI/UXfrontend