Crear un juego de Pong en HTML5 con EaselJS
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!



