1. Game Development

Crear un juego de Pong en HTML5 con EaselJS

En este tutorial, crearemos un clon del clásico juego Pong en HTML5, utilizando la librería EaselJS. El juego tendrá múltiples pantallas, efectos de sonido, y un oponente (muy simple) de IA.
Scroll to top
12 min read

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

En este tutorial, crearemos un clon del clásico juego Pong en HTML5, utilizando la librería EaselJS. El juego tendrá múltiples pantallas, efectos de sonido, y un oponente (muy simple) de IA.

Paso 1: Breve resumen

Utilizando gráficos prefabricados codificaremos un entretenido juego de Pong en HTML5 utilizando la librería EaselJS, que proporciona una interfaz similar a Flash para el lienzo de HTML5. Para una introducción a EaselJS, mira este artículo de Activetuts+.

El jugador podrá controlar una paleta usando el ratón y jugar contra el oponente controlado por el ordenador para conseguir puntos.


Paso 2: Interfaz

Se utilizará una interfaz sencilla con un estilo neofuturista; esto implica múltiples formas, botones, mapas de bits y más.

Los gráficos 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 de inicio si eres nuevo en esta biblioteca.

Puedes descargar la última versión de EaselJS desde su web oficial. Sin embargo, puede ser incompatible con el código aquí, por lo que sugiero utilizar la versión de la biblioteca que se incluye con la descarga de la fuente.


Paso 4: Estructura HTML

Vamos a preparar nuestro documento HTML. Empezaremos con lo más básico, un esquema básico:

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

Paso 5: Ocultar el resaltado del móvil

También debemos añadir un poco de CSS para eliminar el resaltado por defecto que se aplica cuando se toca un elemento en un navegador móvil. Sin esto, la experiencia móvil disminuiría drásticamente.

1
 
2
<!DOCTYPE html> 
3
<html> 
4
	<head> 
5
		<title>Pong</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>Pong</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>/

Además de EaselJS, también utilizaremos TweenJS (para manejar las transiciones de pantalla y el bucle del juego) y SoundJS (para los efectos de sonido).

Main.js es el archivo que usaremos para mantener nuestro propio código JS.


Paso 7: Llamar a la función principal

En las siguientes líneas llamamos a nuestra función Main(). Esta es la función que iniciará nuestra aplicación; será creada más adelante en el tutorial, dentro de Main.js.

1
 
2
<!DOCTYPE html> 
3
<html> 
4
	<head> 
5
		<title>Pong</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: Etiqueta del lienzo

En esta línea se crea un lienzo HTML5; le asignamos un ID para poder referenciarlo posteriormente y también establecemos su anchura y altura.

1
 
2
<!DOCTYPE html> 
3
<html> 
4
	<head> 
5
		<title>Pong</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="Pong" width="480" height="320"></canvas> 
17
	</body> 
18
</html>

Paso 9: Crear Main.js

¡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 el código. Recuerda guardar el archivo como Main.js en la carpeta de tu proyecto.


Paso 10: Definir el lienzo

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

Las siguientes variables representan el elemento HTML canvas y el stage que estará vinculado a él. La variable stage se comportará de forma similar a la stage AS3.

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

Paso 11: Antecedentes

Esta variable almacena la imagen de fondo del título.

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

Paso 12: Vista del título

Esta es la Vista de Título, la primera pantalla interactiva que aparece en nuestro juego. Estas variables almacenan sus componentes.

1
 
2
/* Title View */ 
3
 
4
var mainImg = new Image(); 
5
var main; 
6
var startBImg = new Image(); 
7
var startB; 
8
var creditsBImg = new Image(); 
9
var creditsB; 
10
 
11
var TitleView = new Container();

Paso 13: Créditos

Esta vista mostrará los créditos, el año y el copyright del juego, estas variables se utilizarán para almacenarlo.

1
 
2
/* Credits */ 
3
 
4
var creditsViewImg = new Image(); 
5
var credits;

Paso 14: Vista del juego

Las siguientes variables almacenan los gráficos individuales que aparecen en la Vista de Juego:

1
 
2
/* Game View */ 
3
 
4
var playerImg = new Image(); 
5
var player; 
6
var ballImg = new Image(); 
7
var ball; 
8
var cpuImg = new Image(); 
9
var cpu; 
10
var winImg = new Image(); 
11
var win; 
12
var loseImg = new Image(); 
13
var lose;

Paso 15: Puntuación

Los valores de puntuación serán manejados por las siguientes variables:

1
 
2
/* Score */ 
3
 
4
var playerScore; 
5
var cpuScore;

Paso 16: Variables

Estas son las variables que usaremos, lee los comentarios en el código para entender para qué sirven:

1
 
2
var xSpeed = 5; //Horizontal speed of the ball 

3
var ySpeed = 5; //Vertical speed of the ball 

4
var gfxLoaded = 0; //used as a preloader, counts the already loaded items 

5
var tkr = new Object; //used as an event listener to the Ticker

Paso 17: Crear efectos de sonido

Utilizaremos efectos de sonido para mejorar la sensación del juego. Los sonidos de este ejemplo fueron creados con la excelente herramienta gratuita as3sfxr y convertidos a MP3 con Audacity.

Todos los sonidos necesarios se encuentran en la descarga de la fuente. Si quieres crear los tuyos propios, necesitarás cuatro:

  • hit.mp3: se reproduce cuando la pelota golpea una pala
  • playerScore.mp3: se reproduce cuando el jugador marca
  • enemyScore.mp3: se reproduce cuando el enemigo anota
  • wall.mp3: se reproduce cuando el balón toca el límite superior o inferior

Paso 18: Función principal

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 (véase el paso 7).

Llamará a las funciones necesarias para iniciar el juego.

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. Añade esto a Main().

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

Paso 20: Habilitar los eventos del ratón

Los eventos del ratón están desactivados por defecto en EaselJS para mejorar el rendimiento. Como los necesitamos en el juego, añadimos la siguiente línea. Añade esto a Main().

1
 
2
stage.mouseEventsEnabled = true;

Paso 21: Cargar sonidos

Usaremos SoundJS para añadir sonidos a nuestro juego; escribe el siguiente código para importar los sonidos que usaremos. Añade esto a Main().

1
 
2
/* Sound */ 
3
 
4
SoundJS.addBatch([ 
5
	{name:'hit', src:'hit.mp3', instances:1}, 
6
	{name:'playerScore', src:'playerScore.mp3', instances:1}, 
7
	{name:'enemyScore', src:'enemyScore.mp3', instances:1}, 
8
	{name:'wall', src:'wall.mp3', instances:1}]);

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. Establece los objetos Image que creamos antes para que apunten a los archivos PNG de origen relevantes en nuestra carpeta de documentos.

Se le da un nombre a cada una, para poder detectar qué imagen se carga después, y por último se llama a la función que maneja las imágenes cargadas.

Añade esto a Main().

1
 
2
/* Load GFX */ 
3
 
4
bgImg.src = 'bg.png'; 
5
bgImg.name = 'bg'; 
6
bgImg.onload = loadGfx; 
7
 
8
mainImg.src = 'main.png'; 
9
mainImg.name = 'main'; 
10
mainImg.onload = loadGfx; 
11
 
12
startBImg.src = 'startB.png'; 
13
startBImg.name = 'startB'; 
14
startBImg.onload = loadGfx; 
15
 
16
creditsBImg.src = 'creditsB.png'; 
17
creditsBImg.name = 'creditsB'; 
18
creditsBImg.onload = loadGfx; 
19
 
20
creditsViewImg.src = 'credits.png'; 
21
creditsViewImg.name = 'credits'; 
22
creditsViewImg.onload = loadGfx; 
23
 
24
playerImg.src = 'paddle.png'; 
25
playerImg.name = 'player'; 
26
playerImg.onload = loadGfx; 
27
 
28
ballImg.src = 'ball.png'; 
29
ballImg.name = 'ball'; 
30
ballImg.onload = loadGfx; 
31
 
32
cpuImg.src = 'paddle.png'; 
33
cpuImg.name = 'cpu'; 
34
cpuImg.onload = loadGfx; 
35
 
36
winImg.src = 'win.png'; 
37
winImg.name = 'win'; 
38
winImg.onload = loadGfx; 
39
 
40
loseImg.src = 'lose.png'; 
41
loseImg.name = 'lose'; 
42
loseImg.onload = loadGfx;

Paso 23: Establecer el marcador

La clase Ticker proporciona una emisión centralizada de ticks o latidos en un intervalo establecido. Esto se puede utilizar para desencadenar el bucle del juego.

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. Añade esto a Main().

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 de mapa de bits y comprobará que todos los elementos están cargados antes de proceder.

1
 
2
function loadGfx(e) 
3
{ 
4
	if(e.target.name = 'bg'){bg = new Bitmap(bgImg);} 
5
	if(e.target.name = 'main'){main = new Bitmap(mainImg);} 
6
	if(e.target.name = 'startB'){startB = new Bitmap(startBImg);} 
7
	if(e.target.name = 'creditsB'){creditsB = new Bitmap(creditsBImg);} 
8
	if(e.target.name = 'credits'){credits = new Bitmap(creditsViewImg);} 
9
	if(e.target.name = 'player'){player = new Bitmap(playerImg);} 
10
	if(e.target.name = 'ball'){ball = new Bitmap(ballImg);} 
11
	if(e.target.name = 'cpu'){cpu = new Bitmap(cpuImg);} 
12
	if(e.target.name = 'win'){win = new Bitmap(winImg);} 
13
	if(e.target.name = 'lose'){lose = new Bitmap(loseImg);} 
14
	 
15
	gfxLoaded++; 
16
	 
17
	if(gfxLoaded == 10) // remember to change this if you add more images 

18
	{ 
19
		addTitleView(); 
20
	} 
21
}

Paso 25: Añadir vista de título

Cuando todos los gráficos están cargados, la vista de título se añade al escenario mediante la siguiente función:

1
 
2
function addTitleView() 
3
{ 
4
	startB.x = 240 - 31.5; 
5
	startB.y = 160; 
6
	startB.name = 'startB'; 
7
	 
8
	creditsB.x = 241 - 42; 
9
	creditsB.y = 200; 
10
	 
11
	TitleView.addChild(main, startB, creditsB); 
12
	stage.addChild(bg, TitleView); 
13
	stage.update();

Paso 26: Iniciar los botones de escucha

Esta función añade los listeners necesarios a los botones TitleView (es parte de addTitleView()):

1
 
2
	startB.onPress = addGameView; 
3
	creditsB.onPress = showCredits; 
4
}

Paso 27: Mostrar créditos

La pantalla de créditos se muestra cuando el usuario hace clic en el botón de créditos; se añade un oyente del ratón a la imagen completa para eliminarla.

1
 
2
function showCredits() 
3
{ 
4
	// Show Credits 

5
		 
6
	credits.x = 480; 
7
		 
8
	stage.addChild(credits); 
9
	stage.update(); 
10
	Tween.get(credits).to({x:0}, 300); 
11
	credits.onPress = hideCredits; 
12
}

Paso 28: Ocultar los créditos

Cuando se haga clic en la pantalla de créditos, ésta se interpolará hacia atrás y se retirará del escenario.

1
 
2
// Hide Credits 

3
 
4
function hideCredits(e) 
5
{ 
6
	Tween.get(credits).to({x:480}, 300).call(rmvCredits); 
7
} 
8
 
9
// Remove Credits 

10
 
11
function rmvCredits() 
12
{ 
13
	stage.removeChild(credits); 
14
}

Detengámonos aquí para probar lo que hemos hecho hasta ahora. Haz clic aquí para ver una demostración de los hitos.

Recuerda que algunas líneas han sido comentadas ya que algunas funciones aún no han sido creadas.

Recuerda que el hito está incluido en los archivos fuente, así que si por alguna razón tu archivo no imita a este, compara tu fuente con la mía para ver qué puede estar causando eso.


Paso 29: Mostrar la vista del juego

Las siguientes líneas eliminan el TitleView del escenario y añade los elementos GameView al escenario. Se añade un receptor de ratón en el fondo, para iniciar el juego cuando se haga clic.

1
 
2
function addGameView() 
3
{ 
4
	// Destroy Menu & Credits screen 

5
	 
6
	stage.removeChild(TitleView); 
7
	TitleView = null; 
8
	credits = null; 
9
	 
10
	// Add Game View 

11
	 
12
	player.x = 2; 
13
	player.y = 160 - 37.5; 
14
	cpu.x = 480 - 25; 
15
	cpu.y = 160 - 37.5; 
16
	ball.x = 240 - 15; 
17
	ball.y = 160 - 15; 
18
	 
19
	// Score 

20
	 
21
	playerScore = new Text('0', 'bold 20px Arial', '#A3FF24'); 
22
	playerScore.maxWidth = 1000;	//fix for Chrome 17 

23
	playerScore.x = 211; 
24
	playerScore.y = 20; 
25
	 
26
	cpuScore = new Text('0', 'bold 20px Arial', '#A3FF24'); 
27
	cpuScore.maxWidth = 1000;	//fix for Chrome 17 

28
	cpuScore.x = 262; 
29
	cpuScore.y = 20; 
30
	 
31
	stage.addChild(playerScore, cpuScore, player, cpu, ball); 
32
	stage.update(); 
33
	 
34
	// Start Listener  

35
	 
36
	bg.onPress = startGame; 
37
}

Paso 30: Movimiento del jugador

El jugador se moverá junto con la posición vertical del ratón:

1
 
2
function movePaddle(e) 
3
{ 
4
	// Mouse Movement 

5
	 
6
	player.y = e.stageY; 
7
}

Paso 31: Iniciar el juego

Este código se ejecuta cuando el jugador hace clic en el fondo del juego, añade el oyente del ratón que activa la función en el paso anterior, y añade un Ticker para controlar el bucle del juego.

Presta atención a la forma en que se crea el ticker: es el equivalente a un evento Timer en AS3.

1
 
2
function startGame(e) 
3
{ 
4
	bg.onPress = null; 
5
	stage.onMouseMove = movePaddle; 
6
	 
7
	Ticker.addListener(tkr, false); 
8
	tkr.tick = update; 
9
}

Paso 32: Reiniciar

Cuando se anota un punto (por el jugador o el ordenador), las palas y la pelota vuelven a su posición original y el juego se detiene:

1
 
2
function reset() 
3
{ 
4
	ball.x = 240 - 15; 
5
	ball.y = 160 - 15; 
6
	player.y = 160 - 37.5; 
7
	cpu.y = 160 - 37.5; 
8
	 
9
	stage.onMouseMove = null; //stop listening to the mouse 

10
	Ticker.removeListener(tkr); //pause the game 

11
	bg.onPress = startGame; 
12
}

Paso 33: Movimiento del balón

Si el juego no está pausado, la pelota se moverá en cada fotograma usando las variables que creamos antes.

1
 
2
function update() 
3
{ 
4
	// Ball Movement  

5
 
6
	ball.x = ball.x + xSpeed; 
7
	ball.y = ball.y + ySpeed;

Paso 34: Movimiento de la CPU

Este código controla el movimiento de la computadora; la paleta se mueve de manera que siga la pelota mientras tiene cierto margen de error.

1
 
2
if(cpu.y < ball.y) { 
3
	cpu.y = cpu.y + 2.5; 
4
} 
5
else if(cpu.y > ball.y) { 
6
	cpu.y = cpu.y - 2.5; 
7
}

Paso 35: Colisiones en la pared

Aquí comprobamos si la pelota está en el borde superior o inferior del lienzo; si es así, se invierte la velocidad vertical y se reproduce un sonido.

1
 
2
if((ball.y) < 0) { ySpeed = -ySpeed; SoundJS.play('wall');};//Up 

3
if((ball.y + (30)) > 320) { ySpeed = -ySpeed; SoundJS.play('wall');};//down

Paso 36: Puntuaciones

Ahora los lados izquierdo y derecho. Este código también modifica la puntuación, llama a la función de reinicio y reproduce un sonido diferente según el lado que haya tocado la pelota.

1
 
2
/* CPU Score */ 
3
 
4
if((ball.x) < 0) 
5
{ 
6
	xSpeed = -xSpeed; 
7
	cpuScore.text = parseInt(cpuScore.text + 1); 
8
	reset(); 
9
	SoundJS.play('enemyScore'); 
10
} 
11
 
12
/* Player Score */ 
13
 
14
if((ball.x + (30)) > 480) 
15
{ 
16
	xSpeed = -xSpeed; 
17
	playerScore.text = parseInt(playerScore.text + 1); 
18
	reset(); 
19
	SoundJS.play('playerScore'); 
20
}

Paso 37: Colisiones entre pelotas y paletas

El siguiente código comprueba si la pelota está colisionando con una paleta, comparando la posición de la paleta con las coordenadas de la pelota. Si las cajas delimitadoras de ambos se cruzan, hay una colisión, por lo que invertimos la velocidad x de la pelota y reproducimos un sonido.

1
 
2
/* Cpu collision */ 
3
 
4
if(ball.x + 30 > cpu.x && ball.x + 30 < cpu.x + 22 && ball.y >= cpu.y && ball.y < cpu.y + 75) 
5
{ 
6
	xSpeed *= -1; 
7
	SoundJS.play('hit'); 
8
} 
9
 
10
/* Player collision */ 
11
 
12
if(ball.x <= player.x + 22 && ball.x > player.x && ball.y >= player.y && ball.y < player.y + 75) 
13
{ 
14
	xSpeed *= -1; 
15
	SoundJS.play('hit'); 
16
}

Paso 38: Comprobar si se gana/se acaba el juego

Puedes modificar la condición de finalización en las siguientes líneas, por defecto está fijada en 10 puntos.

1
 
2
/* Check for Win */ 
3
 
4
if(playerScore.text == '10') 
5
{ 
6
	alert('win'); 
7
} 
8
 
9
/* Check for Game Over */ 
10
 
11
if(cpuScore.text == '10') 
12
{ 
13
	alert('lose'); 
14
}

Paso 39: Alerta

Esta función detendrá el juego y mostrará una alerta, cuyo contenido dependerá del resultado del juego.

1
 
2
function alert(e) 
3
{ 
4
	Ticker.removeListener(tkr); 
5
	stage.onMouseMove = null; 
6
	bg.onPress = null 
7
	 
8
	if(e == 'win') 
9
	{ 
10
		win.x = 140; 
11
		win.y = -90; 
12
	 
13
		stage.addChild(win); 
14
		Tween.get(win).to({y: 115}, 300); 
15
	} 
16
	else 
17
	{ 
18
		lose.x = 140; 
19
		lose.y = -90; 
20
	 
21
		stage.addChild(lose); 
22
		Tween.get(lose).to({y: 115}, 300); 
23
	} 
24
}

Paso 40: Prueba

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


Conclusión

¡Prueba a modificar las variables del juego para crear tu propia versión del mismo!

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