1. Code
  2. Game Development

Crea un sencillo juego de disparos espaciales en HTML5 con EaselJS

El año pasado, te mostré cómo crear un juego de disparos con Flash y AS3. Con el aumento de la popularidad de HTML5 (y sus capacidades), vamos a ver cómo hacer lo mismo con HTML5, JavaScript y EaselJS.
Scroll to top
This post is part of a series called Shoot-'Em-Up.
Build a Stage3D Shoot-'Em-Up: Sprite Test

Spanish (Español) translation by Esther (you can also view the original English article)

El año pasado, te mostré cómo crear un juego de disparos con Flash y AS3. Con el aumento de la popularidad de HTML5 (y sus capacidades), vamos a ver cómo hacer lo mismo con HTML5, JavaScript y EaselJS.


Avance del resultado final

Veamos el resultado final para el que vamos a trabajar:

Haz clic para jugar a la demo.


Paso 1: Breve resumen

Utilizando sprites prefabricados codificaremos un entretenido juego de Space Shooter en HTML5 utilizando la librería EaselJS.

El jugador podrá controlar una nave espacial y disparar a múltiples enemigos mientras viaja por el espacio.


Paso 2: Interfaz

Se utilizará una interfaz simple y futurista, esto implica mapas de bits y más. He utilizado una gran librería de sprites en la demo de este tutorial, estos son parte de los gratuitos gráficos Sinistar Clone.

Los recursos de interfaz necesarios para este tutorial se encuentran en la descarga adjunta.


Paso 3: Obtener EaselJS

La biblioteca EaselJS será utilizada para construir nuestro juego, asegúrate de leer el tutorial Getting Started si eres nuevo en esta biblioteca.

Puedes descargar EaselJS desde su página web oficial.


Paso 4: Estructura HTML

Preparemos nuestro documento HTML, es una estructura HTML simple para comenzar a escribir nuestra aplicación. Guarda esto como Shooter.html.

1
 
2
<!DOCTYPE html> 
3
<html> 
4
	<head> 
5
		<title>Shooter</title> 
6
	</head> 
7
	<body> 
8
	</body> 
9
</html>

Paso 5: Ocultar la luz móvil

Añadamos también un poco de CSS, esta línea eliminará el resaltado por defecto cuando se pulse sobre un elemento utilizando un navegador móvil; sin esto, la experiencia móvil disminuiría drásticamente.

1
 
2
<!DOCTYPE html> 
3
<html> 
4
	<head> 
5
		<title>Shooter</title> 
6
		 
7
		<style>*{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> 
8
	 
9
	</head> 
10
	<body> 
11
	</body> 
12
</html>

Paso 6: Bibliotecas JavaScript

El siguiente código añade las librerías JavaScript necesarias para que nuestra aplicación funcione.

1
 
2
<!DOCTYPE html> 
3
<html> 
4
	<head> 
5
		<title>Shooter</title> 
6
		 
7
		<style>*{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> 
8
 
9
		<script src="easel.js"></script> 
10
		<script src="Tween.js"></script> 
11
		<script src="sound.js"></script> 
12
		<script src="Main.js"></script> 
13
	</head> 
14
	<body> 
15
	</body> 
16
</html>

Main.js es el archivo que usaremos para almacenar todas nuestras funciones para el juego. Créalo ahora y guárdalo en la misma carpeta que Shooter.html. También necesitarás descargar las librerías EaselJS de la lista.


Paso 7: Llamar a la función Main

En las siguientes líneas llamamos a nuestra función principal; esta es la función que iniciará nuestra aplicación, será creada más adelante en nuestro código JavaScript.

1
 
2
<!DOCTYPE html> 
3
<html> 
4
	<head> 
5
		<title>Shooter</title> 
6
		 
7
		<style>*{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> 
8
 
9
		<script src="easel.js"></script> 
10
		<script src="Tween.js"></script> 
11
		<script src="sound.js"></script> 
12
		<script src="Main.js"></script> 
13
 
14
	</head> 
15
	<body onload="Main();"> 
16
	</body> 
17
</html>

Paso 8: Elemento del lienzo

En esta línea se añade el Canvas. Le asignamos un ID para referenciarlo posteriormente y también establecemos su anchura y altura.

1
 
2
<!DOCTYPE html> 
3
<html> 
4
	<head> 
5
		<title>Shooter</title> 
6
		 
7
		<style>*{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> 
8
 
9
		<script src="easel.js"></script> 
10
		<script src="Tween.js"></script> 
11
		<script src="sound.js"></script> 
12
		<script src="Main.js"></script> 
13
 
14
	</head> 
15
	<body onload="Main();"> 
16
		<canvas id="Shooter" width="320" height="480"></canvas> 
17
	</body> 
18
</html>

Paso 9: Iniciar el JavaScript

¡Empecemos a crear nuestro juego!

Abre tu editor de JavaScript preferido (cualquier editor de texto funcionará, pero no tendrás resaltado de sintaxis) y prepárate para escribir tu impresionante juego. Abre el archivo Main.js que creaste antes.


Paso 10: Definir el lienzo

Empezaremos por definir todas las variables gráficas y lógicas.

Las siguientes variables representan el elemento canvas HTML y el stage que estará vinculado a él. (La variable stage se comportará de forma similar al stage en un proyecto AS3 Flash).

1
 
2
/* Define Canvas */ 
3
 
4
var canvas; 
5
var stage;

Paso 11: Antecedentes

Las siguientes variables almacenan las imágenes de fondo. Se utilizan dos imágenes en mosaico para crear un fondo de desplazamiento infinito.

1
 
2
/* Background */ 
3
 
4
var bgImg = new Image(); 
5
var bg; 
6
var bg2Img = new Image(); 
7
var bg2;

Paso 12: Enviar

Esta es la nave que se utilizará como personaje del jugador o héroe.

1
 
2
/* Ship */ 
3
 
4
var sImg = new Image(); 
5
var ship;

Paso 13: Enemigo

Habrá varios enemigos en el escenario; usarán esto como gráfico de origen.

1
 
2
/* Enemy */ 
3
 
4
var eImg = new Image();

Paso 14: Jefe

Un jefe estará presente en el juego, más grande y con más salud que los otros enemigos. Estas variables se utilizan para instanciarlo.

1
 
2
/* Boss */ 
3
 
4
var bImg = new Image(); 
5
var boss;

Paso 15: Vidas

El icono "vida". Se dan tres vidas al principio, y se pierde una al ser golpeado por un enemigo.

1
 
2
/* Lives */ 
3
 
4
var lImg = new Image();

Paso 16: Balas

Esta es tu arma: dispara balas a los enemigos para matarlos. Esta variable almacena la imagen de origen.

1
 
2
/* Bullets */ 
3
 
4
var bltImg = new Image();

Paso 17: Gráficos de alerta

En el juego se utilizan dos alertas, una para cuando se gana y otra para cuando se pierde. Veremos cómo determinar una victoria o una pérdida más adelante en este tutorial.

1
 
2
/* Alert */ 
3
 
4
var winImg = new Image(); 
5
var loseImg = new Image(); 
6
var win; 
7
var lose;

Paso 16: Variables

Estas son las variables que usaremos, lee los comentarios en el código para saber más sobre ellas. Algunos de sus nombres se explican por sí mismos y por eso no tienen comentarios.

1
 
2
var lives = new Container(); //stores the lives gfx 

3
var bullets = new Container(); //stores the bullets gfx 

4
var enemies = new Container(); //stores the enemies gfx 

5
var bossHealth = 20; 
6
var score; 
7
var gfxLoaded = 0; //used as a preloader, counts the already loaded items 

8
var centerX = 160; 
9
var centerY = 240; 
10
var tkr = new Object(); //used as a Ticker listener 

11
var timerSource; //references a setInterval method

Paso 17: Sonidos

Utilizaremos efectos de sonido para mejorar la sensación del juego. Puedes encontrar los sonidos utilizados en este ejemplo en Soungle.com utilizando las palabras clave espacio, explosión y láser.


Paso 18: Función Main

La función Main() será la primera en ejecutarse cuando se cargue la página web, porque se hace referencia a ella en el atributo onload del documento HTML (mira el paso 7).

Llama a las funciones necesarias para iniciar el juego. Crearemos esas funciones en los próximos pasos, todo desde el paso 19 al 23 debe ir dentro de esta función.

1
 
2
function Main() 
3
{ 
4
	//code... 

5
}

Paso 19: Enlazar el lienzo

Este código obtiene el ID del lienzo HTML y lo vincula a la clase EaselJS Stage. Esto hará que la variable stage se comporte como la clase stage en AS3.

1
 
2
/* Link Canvas */ 
3
	 
4
canvas = document.getElementById('Shooter'); 
5
stage = new Stage(canvas);

Paso 20: Habilitar los eventos del ratón

Los eventos del ratón están deshabilitados por defecto en EaselJS para mejorar el rendimiento; como los necesitamos en el juego añadimos la siguiente línea.

1
 
2
stage.mouseEventsEnabled = true;

Paso 21: Cargar sonidos

Usaremos SoundJS para añadir sonidos a nuestro juego. El método addBatch de SoundJS utiliza un array de tres parámetros para cada llamada:

  • name: El nombre de la instancia que quieres que tenga el sonido, esto se usará para reproducir el sonido más tarde.
  • src: La URL del archivo de sonido.
  • instances: El número de instancias que se pueden reproducir al mismo tiempo.
1
 
2
/* Sound */ 
3
 
4
SoundJS.addBatch([ 
5
		{name:'boss', src:'boss.mp3', instances:1}, 
6
		{name:'explo', src:'explo.mp3', instances:10}, 
7
		{name:'shot', src:'shot.mp3', instances:10}]);

Paso 22: Cargar los gráficos

Este código se utiliza para precargar los gráficos con la ayuda de una función que escribiremos más adelante. Apunta cada objeto Imagen que creamos antes al archivo PNG fuente en nuestra carpeta de documentos. Se da un nombre para detectar qué imagen se carga y, por último, se llama a la función que maneja las imágenes cargadas.

1
 
2
/* Load GFX */ 
3
 
4
bgImg.src = 'bg.png'; 
5
bgImg.name = 'bg'; 
6
bgImg.onload = loadGfx; 
7
 	 
8
bg2Img.src = 'bg2.png'; 
9
bg2Img.name = 'bg2'; 
10
bg2Img.onload = loadGfx; 
11
 	 
12
sImg.src = 'ship.png'; 
13
sImg.name = 'ship'; 
14
sImg.onload = loadGfx; 
15
 
16
eImg.src = 'enemy1.png'; 
17
eImg.name = 'enemy'; 
18
eImg.onload = loadGfx; 
19
 
20
bImg.src = 'boss.png'; 
21
bImg.name = 'boss'; 
22
bImg.onload = loadGfx; 
23
 
24
lImg.src = 'live.png'; 
25
lImg.name = 'live'; 
26
lImg.onload = loadGfx; 
27
 
28
bltImg.src = 'bullet.png'; 
29
bltImg.name = 'bullet'; 
30
bltImg.onload = loadGfx; 
31
 
32
winImg.src = 'win.png'; 
33
winImg.name = 'win'; 
34
winImg.onload = loadGfx; 
35
 
36
loseImg.src = 'lose.png'; 
37
loseImg.name = 'lose'; 
38
loseImg.onload = loadGfx;

Paso 23: Establecer el marcador

La clase Ticker proporciona un "tick" centralizado, emitido a un intervalo establecido. Podemos utilizar su función tick() para ejecutar cierto código con una frecuencia regular.

El siguiente código establece la velocidad de fotogramas a 30 y define el escenario como el oyente de los ticks.

La clase TweenJS escuchará este tick para realizar las animaciones.

1
 
2
/* Ticker */ 
3
 
4
Ticker.setFPS(30); 
5
Ticker.addListener(stage);

Paso 24: Función de precarga

Cada vez que se cargue un gráfico se ejecutará esta función. Asignará cada imagen a un objeto bitmap y comprobará que todos los elementos están cargados antes de proceder a llamar a addGameView.

1
 
2
function loadGfx(e) 
3
{ 
4
	if(e.target.name = 'bg'){bg = new Bitmap(bgImg);} 
5
	if(e.target.name = 'bg2'){bg2 = new Bitmap(bg2Img);} 
6
	if(e.target.name = 'ship'){ship = new Bitmap(sImg);} 
7
	 
8
	gfxLoaded++; 
9
	 
10
	if(gfxLoaded == 9) 
11
	{ 
12
		addGameView(); 
13
	} 
14
}

Paso 25: Añadir vista de juego

Cuando todos los gráficos están cargados se llama a la función addGameView. Esta función añadirá la nave, el contador de vidas, la puntuación y los fondos al escenario.

1
 
2
function addGameView() 
3
{ 
4
	ship.x = centerX - 18.5; 
5
	ship.y = 480 + 34; 
6
	 
7
	/* Add Lives */ 
8
	 
9
	for(var i = 0; i < 3; i++) 
10
	{ 
11
		var l = new Bitmap(lImg); 
12
		 
13
		l.x = 248 + (25 * i); 
14
		l.y = 463; 
15
		 
16
		lives.addChild(l); 
17
		stage.update(); 
18
	} 
19
	 
20
	/* Score Text */ 
21
	 
22
	score = new Text('0', 'bold 14px Courier New', '#FFFFFF'); 
23
	score.maxWidth = 1000;	//fix for Chrome 17 

24
	score.x = 2; 
25
	score.y = 476; 
26
	 
27
	/* Second Background */ 
28
	 
29
	bg2.y = -480; 
30
	 
31
	/* Add gfx to stage and Tween Ship */ 
32
	 
33
	stage.addChild(bg, bg2, ship, enemies, bullets, lives, score); 
34
	Tween.get(ship).to({y:425}, 1000).call(startGame); 
35
}

Paso 26: Mover la nave

La nave del jugador será controlada por el ratón, y usamos esta función para manejar eso:

1
 
2
function moveShip(e) 
3
{ 
4
	ship.x = e.stageX - 18.5; 
5
}

e.stageX se refiere a la coordenada x del ratón, y esta función es llamada cada vez que el ratón se mueve.


Paso 27: Disparar

Nuestra nave podrá disparar balas para destruir y protegerse de los enemigos. Esta función se ejecutará cada vez que el usuario haga clic en el escenario y colocará una bala delante de la nave que posteriormente será movida por la función update(). También reproduce un sonido de disparo.

1
 
2
function shoot() 
3
{ 
4
	var b = new Bitmap(bltImg); 
5
	 
6
	b.x = ship.x + 13; 
7
	b.y = ship.y - 20; 
8
	 
9
	bullets.addChild(b); 
10
	stage.update(); 
11
	 
12
	SoundJS.play('shot'); 
13
}

Paso 28: Añadir la función de enemigo

No sería un shooter sin algo a lo que disparar. Aquí se utiliza un setInterval() para crear un enemigo cada 1000 milisegundos (puedes cambiar ese valor en el siguiente paso) que luego es movido por la función update().

1
 
2
function addEnemy() 
3
{ 
4
	var e = new Bitmap(eImg); 
5
	 
6
	e.x = Math.floor(Math.random() * (320 - 50)) 
7
	e.y = -50 
8
	 
9
	enemies.addChild(e); 
10
	stage.update(); 
11
}

Paso 29: Iniciar el juego

Estas líneas añadirán las escuchas necesarias al escenario y al temporizador; esto incluye eventos del ratón, eventos temporizados (a través de setInterval) y eventos del Ticker que actualizarán el juego cada fotograma.

1
 
2
function startGame() 
3
{ 
4
	stage.onMouseMove = moveShip; 
5
	bg.onPress = shoot; 
6
	bg2.onPress = shoot; 
7
	 
8
	Ticker.addListener(tkr, false); 
9
	tkr.tick = update; 
10
	 
11
	timerSource = setInterval('addEnemy()', 1000); 
12
}

Paso 30: Mover el fondo

El fondo se mueve en cada fotograma para simular el viaje espacial; cuando el sprite del fondo inferior alcanza el límite del escenario se mueve de nuevo a la parte superior, creando un bucle infinito.

1
 
2
function update() 
3
{ 
4
	/* Move Background */ 
5
	 
6
	bg.y += 5; 
7
	bg2.y += 5; 
8
	 
9
	if(bg.y >= 480) 
10
	{ 
11
		bg.y = -480; 
12
	} 
13
	else if(bg2.y >= 480) 
14
	{ 
15
		bg2.y = -480; 
16
	}

Paso 31: Mover las balas

Las siguientes líneas de código comprueban si hay balas en el escenario; si es así, las balas se mueven hacia arriba.

1
 
2
/* Move Bullets */ 
3
	 
4
	for(var i = 0; i < bullets.children.length; i++) 
5
	{ 
6
		bullets.children[i].y -= 10; 
7
	}

Paso 32: Eliminar las balas fuera de escena

Agreguemos algunas líneas para detectar la posición de la bala, y usemos esto para destruir una bala cuando ya no sea visible.

1
 
2
/* Move Bullets */ 
3
	 
4
	for(var i = 0; i < bullets.children.length; i++) 
5
	{ 
6
		bullets.children[i].y -= 10; 
7
		 
8
		/* Remove Offstage Bullets */ 
9
		 
10
		if(bullets.children[i].y < - 20) 
11
		{ 
12
			bullets.removeChildAt(i); 
13
		} 
14
	}

Paso 33: Mostrar al jefe

Añadiremos un gran jefe malo al juego. Cuando el usuario alcance una determinada puntuación, el jefe aparecerá:

1
 
2
/* Show Boss */ 
3
	 
4
	if(parseInt(score.text) >= 500 && boss == null) 
5
	{ 
6
		boss = new Bitmap(bImg); 
7
				 
8
		SoundJS.play('boss'); 
9
				 
10
		boss.x = centerX - 90; 
11
		boss.y = -183; 
12
				 
13
		stage.addChild(boss); 
14
		Tween.get(boss).to({y:40}, 2000)   //tween the boss onto the play area 

15
	}

Paso 34: Mover enemigos

Los enemigos, al igual que las balas, también se mueven cada fotograma. Este código encuentra todos los enemigos en el escenario usando el contenedor enemies, y los mueve cada 5px hacia abajo.

1
 
2
/* Move Enemies */ 
3
	 
4
	for(var j = 0; j < enemies.children.length; j++) 
5
	{ 
6
		enemies.children[j].y += 5;

Paso 35: Eliminar los enemigos fuera del escenario

También comprobamos las posiciones de los enemigos para destruirlos cuando ya no sean visibles.

1
 
2
/* Move Enemies */ 
3
	 
4
	for(var j = 0; j < enemies.children.length; j++) 
5
	{ 
6
		enemies.children[j].y += 5; 
7
		 
8
		/* Remove Offstage Enemies */ 
9
		 
10
		if(enemies.children[j].y > 480 + 50) 
11
		{ 
12
			enemies.removeChildAt(j); 
13
		}

Paso 36: Colisión bala-enemigo

Se comprueba si las balas del contenedor colisionan con los enemigos; cuando esto ocurre, ambos se retiran del escenario, se reproduce un sonido y se actualiza la puntuación.

1
 
2
for(var k = 0; k < bullets.children.length; k++) 
3
{ 
4
	/* Bullet - Enemy Collision */ 
5
	 
6
	if(bullets.children[k].x >= enemies.children[j].x && bullets.children[k].x + 11 < enemies.children[j].x + 49 && bullets.children[k].y < enemies.children[j].y + 40) 
7
	{ 
8
		bullets.removeChildAt(k); 
9
		enemies.removeChildAt(j); 
10
		stage.update(); 
11
		SoundJS.play('explo'); 
12
		score.text = parseFloat(score.text + 50); 
13
	}

Paso 37: Colisión Bala - Jefe

El siguiente código maneja las colisiones del jefe, utiliza el mismo método usado en el bucle de colisión bala-enemigo. Aquí usamos la variable bossHealth para determinar cuando el jefe es derrotado.

1
 
2
/* Bullet - Boss Collision */ 
3
			 
4
	if(boss != null && bullets.children[k].x >= boss.x && bullets.children[k].x + 11 < boss.x + 183 && bullets.children[k].y < boss.y + 162) 
5
	{ 
6
		bullets.removeChildAt(k); 
7
		bossHealth--; 
8
		stage.update(); 
9
		SoundJS.play('explo'); 
10
		score.text = parseInt(score.text + 50); 
11
	} 
12
}

Paso 38: Colisión barco-enemigo

Aquí comprobamos si un enemigo colisiona con la nave del jugador; si lo hace, se reproduce un sonido, se elimina una vida y se anima la nave.

1
 
2
/* Ship - Enemy Collision */ 
3
		 
4
	if(enemies.hitTest(ship.x, ship.y) || enemies.hitTest(ship.x + 37, ship.y)) 
5
	{ 
6
		enemies.removeChildAt(j); 
7
		lives.removeChildAt(lives.length); 
8
		ship.y = 480 + 34; 
9
		Tween.get(ship).to({y:425}, 500) 
10
		SoundJS.play('explo'); 
11
	} 
12
}

Paso 39: Comprobar si se gana o se pierde

El jugador gana cuando el jefe pierde toda su salud y pierde si pierde todas sus propias vidas. Las siguientes líneas detectan esas situaciones y llaman a una función de alerta utilizando el parámetro correcto.

1
 
2
/* Check for win */ 
3
	 
4
	if(boss != null && bossHealth <= 0) 
5
	{ 
6
		alert('win'); 
7
	} 
8
	 
9
	/* Check for lose */ 
10
	 
11
	if(lives.children.length <= 0) 
12
	{ 
13
		alert('lose'); 
14
	} 
15
}

Paso 40: Alerta

La Alerta muestra al jugador información sobre el estado del juego; se muestra cuando se alcanza un evento del juego. Elimina los oyentes del juego y muestra el mensaje apropiado.

1
 
2
function alert(e) 
3
{ 
4
	/* Remove Listeners */ 
5
		 
6
	stage.onMouseMove = null; 
7
	bg.onPress = null; 
8
	bg2.onPress = null; 
9
	 
10
	Ticker.removeListener(tkr); 
11
	tkr = null; 
12
	 
13
	timerSource = null; 
14
	 
15
	/* Display Correct Message */ 
16
	 
17
	if(e == 'win') 
18
	{ 
19
		win = new Bitmap(winImg); 
20
		win.x = centerX - 64; 
21
		win.y = centerY - 23; 
22
		stage.addChild(win); 
23
		stage.removeChild(enemies, boss); 
24
	} 
25
	else 
26
	{ 
27
		lose = new Bitmap(loseImg); 
28
		lose.x = centerX - 64; 
29
		lose.y = centerY - 23; 
30
		stage.addChild(lose); 
31
		stage.removeChild(enemies, ship); 
32
	} 
33
	 
34
	bg.onPress = function(){window.location.reload();}; 
35
	bg2.onPress = function(){window.location.reload();}; 
36
	stage.update(); 
37
}

Paso 41: Prueba

¡Guarda tu trabajo (si no lo has hecho) y abre el archivo HTML en el navegador para ver tu juego HTML5 en funcionamiento!


Conclusión

Ya has aprendido a crear un juego de Space Shooter con todas sus características básicas, intenta ampliarlo utilizando lo que ya sabes. Un buen comienzo sería hacer que los enemigos o el jefe devuelvan los disparos al jugador.

Espero que te haya gustado este tutorial, ¡gracias por leer!