Continuando con nuestra serie de creación de juegos en Phaser 3, en la parte 1 hicimos todo lo necesario para preparar el proyecto. Por lo que ahora podemos evolucionar y agregar enemigos patrullando y una puerta de salida. Esta es la estructura actualizada del proyecto:
mi-juego/ ├── index.html ├── js/ │ ├── enemigo.js │ └── main.js └── assets/ ├── greenie.png ├── enemigo.png └── puerta.png lang-css
Hicimos algunos arreglos, primero, ahora tenemos una carpeta "js", que es donde vamos a ordenar todo nuestro código, por lo que el archivo main.js del post pasado ahora se mueve a esta carpeta.
También agregamos el archivo enemigo.js, que se encargará de la lógica de los enemigos, y finalmente agregamos nuevas imágenes para el enemigo y la puerta que te comparto acá para que las puedas descargar:
También agregamos el archivo enemigo.js, que se encargará de la lógica de los enemigos, y finalmente agregamos nuevas imágenes para el enemigo y la puerta que te comparto acá para que las puedas descargar:
El siguiente paso es actualizar nuestro index.html en la sección <body> (todo lo demás queda igual):
<body> <script src="js/enemigo.js"></script> <script src="js/main.js"></script> </body> lang-html
El cambio principal aquí es la nueva carpeta y agregar el archivo para la clase enemigo.
Nota: En el tutorial de hoy agregué muchos más detalles y comentarios al código que buscan explicar mejor que hace cada cosa, por lo que no tengas miedo si ves que los archivos ahora son más grandes, solo quiero explicar mejor.
Clase Enemigo
El archivo enemigo.js contendrá lo siguiente:
Nota: En el tutorial de hoy agregué muchos más detalles y comentarios al código que buscan explicar mejor que hace cada cosa, por lo que no tengas miedo si ves que los archivos ahora son más grandes, solo quiero explicar mejor.
Clase Enemigo
El archivo enemigo.js contendrá lo siguiente:
/** * Clase Enemigo - Crea enemigos que patrullan automáticamente * * Esta clase extiende de Phaser.Physics.Arcade.Sprite para crear enemigos * que se mueven de forma automática y rebotan en los bordes de la pantalla */ class Enemigo extends Phaser.Physics.Arcade.Sprite { /** * Constructor del enemigo * scene - La escena donde se creará el enemigo * x - Posición inicial en el eje X * y - Posición inicial en el eje Y * dir - Dirección de movimiento: 'horizontal' o 'vertical' */ constructor(scene, x, y, dir = 'horizontal') { // Llamar al constructor padre con la imagen 'enemigo' super(scene, x, y, 'enemigo'); // Agregar este sprite a la escena (para que sea visible) scene.add.existing(this); // Agregar física a este sprite (para que pueda moverse y colisionar) scene.physics.add.existing(this); // Propiedades del enemigo this.velocidad = 100; // Velocidad de movimiento en píxeles por segundo this.direccion = dir; // Guardar la dirección para referencia futura /** * IMPORTANTE: Usamos delayedCall porque Phaser necesita un frame completo * para inicializar completamente el cuerpo físico (this.body) del sprite. * Sin este delay, las propiedades de física no se aplicarían correctamente. */ scene.time.delayedCall(0, () => { // Hacer que el enemigo rebote al tocar los bordes de la pantalla this.body.setCollideWorldBounds(true); // setBounce(1) = rebote perfecto (no pierde velocidad al rebotar) // setBounce(0.5) = rebote con pérdida de velocidad this.body.setBounce(1); // Establecer la velocidad inicial según la dirección if (dir === 'horizontal') { // Movimiento horizontal (izquierda-derecha) this.body.setVelocityX(this.velocidad); } else { // Movimiento vertical (arriba-abajo) this.body.setVelocityY(this.velocidad); } }); } } lang-javascript
Clase Principal
Al igual que la clase enemigo, main.js recibió un poco más de amor en este post, porque agregué comentarios explicando que hace cada línea:
/** * CONFIGURACIÓN DEL JUEGO * * Aquí definimos todas las configuraciones básicas de nuestro juego Phaser */ const config = { type: Phaser.AUTO, // Phaser elegirá automáticamente WebGL o Canvas width: 640, // Ancho del canvas en píxeles height: 480, // Alto del canvas en píxeles backgroundColor: '#1d1d1d', // Color de fondo oscuro physics: { default: 'arcade', // Usar el sistema de física Arcade (simple y rápido) arcade: { gravity: { y: 0 }, // Sin gravedad (juego vista desde arriba) debug: false // Cambiar a true para ver los cuerpos de colisión } }, scene: { preload, // Función para cargar recursos create, // Función para crear objetos del juego update // Función que se ejecuta cada frame } }; /** * VARIABLES GLOBALES * * Estas variables almacenan los objetos principales del juego * para poder acceder a ellos desde cualquier función */ let player; // El sprite del jugador let teclas; // Objeto que controla las teclas del teclado let puerta; // El sprite de la puerta (objetivo del juego) let enemigos; // Grupo que contiene todos los enemigos // Inicializar el juego con la configuración definida arriba const game = new Phaser.Game(config); /** * FUNCIÓN PRELOAD * * Se ejecuta una sola vez al inicio del juego. * Aquí cargamos todos los recursos (imágenes, sonidos, etc.) * que necesitaremos en el juego. */ function preload() { // Cargar las imágenes desde la carpeta assets this.load.image('jugador', 'assets/greenie.png'); // Sprite del jugador this.load.image('enemigo', 'assets/enemigo.png'); // Sprite de los enemigos this.load.image('puerta', 'assets/puerta.png'); // Sprite de la puerta } /** * FUNCIÓN CREATE * * Se ejecuta una sola vez después de preload. * Aquí creamos todos los objetos del juego, configuramos la física * y establecemos las colisiones entre objetos. */ function create() { // === CREAR EL JUGADOR === // Crear el jugador en la posición (50, 450) con la imagen 'jugador' player = this.physics.add.image(50, 450, 'jugador'); // Impedir que el jugador se salga de los límites de la pantalla player.body.setCollideWorldBounds(true); // === CONFIGURAR CONTROLES === // Crear controles para las flechas del teclado teclas = this.input.keyboard.createCursorKeys(); // Agregar también las teclas WASD como alternativa this.input.keyboard.addKeys('W,S,A,D'); // === CREAR ENEMIGOS === // Crear un grupo para almacenar todos los enemigos enemigos = this.physics.add.group(); // Definir las posiciones y direcciones de cada enemigo const posicionesEnemigos = [ { x: 100, y: 200, dir: 'horizontal' }, // Enemigo que se mueve izquierda-derecha { x: 300, y: 150, dir: 'vertical' }, // Enemigo que se mueve arriba-abajo { x: 200, y: 400, dir: 'horizontal' } // Otro enemigo horizontal ]; // Crear cada enemigo y añadirlo al grupo posicionesEnemigos.forEach(({ x, y, dir }) => { const enemigo = new Enemigo(this, x, y, dir); enemigos.add(enemigo); }); // === CREAR LA PUERTA (OBJETIVO) === // La puerta es un objeto estático (no se mueve) en la esquina superior derecha puerta = this.physics.add.staticImage(600, 50, 'puerta'); // === CONFIGURAR COLISIONES === // Si el jugador toca un enemigo -> función perder() this.physics.add.overlap(player, enemigos, perder, null, this); // Si el jugador toca la puerta -> función ganar() this.physics.add.overlap(player, puerta, ganar, null, this); } /** * FUNCIÓN UPDATE * * Se ejecuta continuamente, aproximadamente 60 veces por segundo. * Aquí manejamos el movimiento del jugador y actualizaciones del juego. */ function update() { const speed = 200; // Velocidad del jugador en píxeles por segundo // === RESETEAR MOVIMIENTO DEL JUGADOR === // Cada frame, primero paramos al jugador // (solo el jugador, no afectamos a los enemigos) player.body.setVelocity(0); // === CONTROLAR MOVIMIENTO HORIZONTAL === // Detectar si se presiona flecha izquierda o tecla A if (teclas.left.isDown || this.input.keyboard.keys[65].isDown) { player.body.setVelocityX(-speed); // Mover hacia la izquierda } // Detectar si se presiona flecha derecha o tecla D else if (teclas.right.isDown || this.input.keyboard.keys[68].isDown) { player.body.setVelocityX(speed); // Mover hacia la derecha } // === CONTROLAR MOVIMIENTO VERTICAL === // Detectar si se presiona flecha arriba o tecla W if (teclas.up.isDown || this.input.keyboard.keys[87].isDown) { player.body.setVelocityY(-speed); // Mover hacia arriba } // Detectar si se presiona flecha abajo o tecla S else if (teclas.down.isDown || this.input.keyboard.keys[83].isDown) { player.body.setVelocityY(speed); // Mover hacia abajo } // NOTA: Los enemigos se mueven automáticamente gracias a su clase, // por eso no necesitamos código adicional para ellos aquí. } /** * FUNCIÓN PERDER * * Se ejecuta cuando el jugador toca un enemigo. * Muestra un mensaje de derrota y reinicia el juego. */ function perder() { alert("¡Has perdido! Tocaste un enemigo."); this.scene.restart(); // Reiniciar la escena del juego } /** * FUNCIÓN GANAR * * Se ejecuta cuando el jugador llega a la puerta. * Muestra un mensaje de victoria y reinicia el juego. */ function ganar() { alert("¡Felicidades! Has llegado a la puerta."); this.scene.restart(); // Reiniciar la escena del juego } lang-javascript
Resultado esperado al correrlo
Para poner a prueba el juego, usamos el mismo método del primer tutorial, y el resultado que esperamos es el siguiente:

- Enemigos patrullan y rebotan en bordes
- Puerta está fija en pantalla
- Si tocas un enemigo → Pierdes
- Si llegas a la puerta → Ganas
💬 ¿Qué te pareció?
Si probaste este tutorial o hiciste tu propia versión, me encantaría verla. Déjame un comentario o comparte tu resultado conmigo. ¡Nos leemos!
0 comentario
Aún no hay comentarios. ¡Sé el primero en comentar!