Juegos HTML: Crea un Rompecabezas con Intercambio de Piezas en HTML5 Canvas
() translation by (you can also view the original English article)
Crea un juego de intercambio de piezas en JavaScript. El resultado funcionará con cualquier imagen y tendrá niveles de dificultad ajustables.
El Rompecabezas HTML5 Canvas Completado
Aquí está una demostración del rompecabezas que estaremos creando en nuestro tutorial de HTML:
Un par de comentarios:
-
Compatibilidad entre navegadores: Este rompecabezas se probó y funciona en todas las versiones de Safari, Firefox y Chrome que admiten el elemento
canvas
.
- Dificultad Ajustable: Construiremos un control deslizante que te permite cambiar la dificultad antes de cada juego.
Comencemos
Para comenzar, crea un directorio para el proyecto. Coloca la imagen en el directorio que quieres utilizar para crear un rompecabezas. Cualquier imagen compatible con la web funciona y pude tener cualquier tamaño que desees. Solo asegúrate que se ajuste dentro del doblez de la ventana de tu navegador
1. Creando la Plantilla para Juegos HTML
Abre un nuevo archivo usando tu editor de texto favorito y guárdalo dentro del directorio del proyecto, junto a tu imagen. A continuación, completa esta plantilla HTML básica.
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head>
|
4 |
<title>HTML5 Puzzle</title> |
5 |
</head>
|
6 |
|
7 |
<body>
|
8 |
<canvas id="canvas"></canvas> |
9 |
<br /> |
10 |
<label for="difficulty">Difficulty</label> |
11 |
<input type="range" min="2" max="16" value="4" id="difficulty" /> |
12 |
<script>
|
13 |
</script>
|
14 |
</body>
|
15 |
</html>
|
Todo lo que necesitamos hacer aquí es crear una plantilla HTML5 estándar que contenga una etiqueta canvas
con el id de “canvas”
, así como la dificultad del control deslizante. Trabajaremos en crear el control deslizante de dificultad más adelante.
Ahora comienza colocando tu cursor dentro de la etiqueta de script
. De ahora en adelante, todo JavaScript está dentro de esa etiqueta con la excepción de las variables iniciales. Estaré organizando las secciones por función, primero te mostraré el código y luego explicaré la lógica.
¿Listo? Veamos cómo hacer rompecabezas.
2. Configurando Nuestras Variables
Configuremos nuestras variables en el tutorial de HTML y echemos un vistazo a cada una.
1 |
const PUZZLE_HOVER_TINT = '#009900'; |
2 |
|
3 |
|
4 |
const canvas = document.querySelector("#canvas"); |
5 |
const stage = canvas.getContext("2d"); |
6 |
const img = new Image(); |
7 |
|
8 |
let difficulty = 4; |
9 |
let pieces; |
10 |
let puzzleWidth; |
11 |
let puzzleHeight; |
12 |
let pieceWidth; |
13 |
let pieceHeight; |
14 |
let currentPiece; |
15 |
let currentDropPiece; |
16 |
|
17 |
let mouse; |
Para comenzar, tenemos la constante PUZZLE_HOVER_TINT
. La constante PUZZLE_HOVER_TINT
define cuál debe ser el tinte del color al desplazarte por las piezas de la imagen.
A continuación tenemos una serie de variables:
-
CANVAS
ySTAGE
mantendrán una referencia al canvas y a su contexto de dibujo respectivamente. Hacemos esto para no tener que escribir la consulta completa cada vez que los utilicemos. ¡Y los estaremos usando mucho para crear un rompecabezas! -
difficulty
contiene el nivel de dificultad actual. Más adelante configuraremos este conjunto utilizando un menú deslizante, por ahora manténlo en 4. -
img
será una referencia de la imagen descargada, de la cual copiaremos pixeles durante toda la aplicación. -
puzzleWidth
,puzzleHeight
,pieceWidth
ypieceHeight
se usarán para almacenar las dimensiones de todo el rompecabezas JS y cada pieza individual. Los configuramos una vez para evitar calcularlos de nuevo cada vez que los necesitemos. -
currentPiece
contiene una referencia a cada pieza que actualmente se arrastre. -
currentDropPiece
contiene una referencia a la pieza actualmente en posición para ser soltada. (En la demostración del tutorial de HTML, esta pieza está resaltada en verde.) -
mouse
es una referencia que contendrá las posiciones actualesx
yy
del cursor. Estas se actualizan cuando se da clic en el rompecabezas, así se determina determinar cuál pieza está seleccionada y cuando una pieza se arrastra para determinar cuál pieza está flotando.
Ahora continuamos con las funciones en nuestro tutorial de HTML.
3. Inicializar la Imagen
1 |
img.addEventListener('load',onImage,false); |
2 |
img.src = "mke.jpg"; |
La primer cosa que queremos hacer en nuestra aplicación es descargar la imagen para el rompecabezas JS. Primero se crea una instancia del objeto y se establece en nuestra variable img
. A continuación escuchamos el evento load
, que luego detonará nuestra función onImage()
cuando la imagen termine de cargar. Finalmente, establecemos la fuente de la imagen que detona la carga.
4. La FunciónonImage()
1 |
function onImage(e) { |
2 |
pieceWidth = Math.floor(img.width / difficulty); |
3 |
pieceHeight = Math.floor(img.height / difficulty); |
4 |
puzzleWidth = pieceWidth * difficulty; |
5 |
puzzleHeight = pieceHeight * difficulty; |
6 |
setCanvas(); |
7 |
initPuzzle(); |
8 |
}
|
Ahora que la imagen para nuestros juegos HTML se cargó con éxito, podemos establecer la mayoría de las variables que declaramos anteriormente. Hacemos esto aquí porque ahora tenemos la información sobre la imagen y podemos establecer nuestros valores apropiadamente.
Lo primero que haremos para crear un rompecabezas es calcular el tamaño de cada pieza. Lo haremos dividiendo el valor difficulty
por el ancho y altura de la imagen cargada. También recortamos algunos bordes para que nos den números pares para trabajar y asegurarnos que cada pieza se puede cambiar de lugar con otra.
A continuación para cómo hacer rompecabezas, usamos los nuevos valores para determinar el tamaño total del rompecabezas y configurar dichos valores a puzzleWidth
y puzzleHeight
.
Finalmente, cancelamos algunas funciones: setCanvas()
y initPuzzle()
.
5. La Función setCanvas()
1 |
function setCanvas() { |
2 |
canvas.width = puzzleWidth; |
3 |
canvas.height = puzzleHeight; |
4 |
canvas.style.border = "1px solid black"; |
5 |
}
|
Ahora que los valores para crear un rompecabezas están completos, queremos establecer nuestro elemento canvas
. Primero, configuramos nuestra variable canvas
para hacer referencia a nuestro elemento canvas
y stage
para referir a su context
.
Ahora configuramos el width
y el height
de nuestro canvas
para ajustar el tamaño de nuestra imagen recortada, luego aplicamos algunos estilos simples para crear un borde negro alrededor de nuestro canvas
para mostrar los límites de nuestro rompecabezas.
6. La Función initPuzzle()
1 |
function initPuzzle() { |
2 |
pieces = []; |
3 |
mouse = { x: 0, y: 0 }; |
4 |
currentPiece = null; |
5 |
currentDropPiece = null; |
6 |
stage.drawImage( |
7 |
img, |
8 |
0, |
9 |
0, |
10 |
puzzleWidth, |
11 |
puzzleHeight, |
12 |
0, |
13 |
0, |
14 |
puzzleWidth, |
15 |
puzzleHeight
|
16 |
);
|
17 |
createTitle("Click to Start Puzzle"); |
18 |
buildPieces(); |
19 |
}
|
Aquí, inicializamos el rompecabezas. Configuramos esta función de manera que podamos llamarla más tarde cuando queramos volver a jugar el rompecabezas JS. Cualquier otra cosa que se necesite configurar antes de jugar no se necesitará ajustar nuevamente para nuestros juegos HTML.
Primero, configuramos pieces
como una formación vacía y creamos el objeto mouse
, que contendrá la posición de nuestro cursor a lo largo de la aplicación. A continuación, configuramos currentPiece
y currentPieceDrop
a null
. (En la primer partida estos valores ya estarán como null
, pero queremos asegurarnos que se reinicien al volver a jugar el rompecabezas.)
Finalmente, ¡es momento de dibujar! Primero, para crear un rompecabezas dibujaremos toda la imagen que mostrará al jugador lo que creará. Después, haremos algunas instrucciones simples llamando a nuestra función createTitle()
.
7. La Función createTitle()
1 |
function createTitle(msg) { |
2 |
stage.fillStyle = "#000000"; |
3 |
stage.globalAlpha = 0.4; |
4 |
stage.fillRect(100, puzzleHeight - 40, puzzleWidth - 200, 40); |
5 |
stage.fillStyle = "#FFFFFF"; |
6 |
stage.globalAlpha = 1; |
7 |
stage.textAlign = "center"; |
8 |
stage.textBaseline = "middle"; |
9 |
stage.font = "20px Arial"; |
10 |
stage.fillText(msg, puzzleWidth / 2, puzzleHeight - 20); |
11 |
}
|
Aquí, crearemos un mensaje bastante simple que le indique al usuario hacer clic en el rompecabezas para comenzar. Nuestro mensaje estará en un rectángulo semi transparente que servirá como el fondo de nuestro texto. Esto permite al usuario ver la imagen detrás de él y también se asegura que el texto blanco será legible en cualquier imagen que elijas al crear un rompecabezas.
Solamente configuramos fillStyle
a negro y globalAlpha
a 0.4
, antes de rellenar un pequeño rectángulo negro en la parte inferior de la imagen.
Debido a que globalAlpha
afecta a todo el canvas, tenemos que volver a configurarlo a 1
(opaco) antes de dibujar el texto. Para configurar nuestro título, establecemos el textAlign
a "center"
y el textBaseline
a "middle"
. También podemos cambiar la fuente por medio de la propiedad font
.
Para dibujar el texto, usamos el método fillText()
. Mostramos la variable msg
y la colocamos en el centro horizontal del canvas
y el centro vertical del rectángulo.
8. La Función buildPieces()
1 |
function buildPieces() { |
2 |
let i; |
3 |
let piece; |
4 |
let xPos = 0; |
5 |
let yPos = 0; |
6 |
for (i = 0; i < difficulty * difficulty; i++) { |
7 |
piece = {}; |
8 |
piece.sx = xPos; |
9 |
piece.sy = yPos; |
10 |
pieces.push(piece); |
11 |
xPos += pieceWidth; |
12 |
if (xPos >= puzzleWidth) { |
13 |
xPos = 0; |
14 |
yPos += pieceHeight; |
15 |
}
|
16 |
}
|
17 |
document.onpointerdown = shufflePuzzle; |
18 |
}
|
Finalmente, ¡es momento de crear un rompecabezas!
Lo haremos construyendo un objeto para cada pieza. Estos objetos no serán responsables de renderizar al canvas, más bien contendrán referencias sobre lo que hay que dibujar y dónde. Dicho esto, veamos como hacer rompecabezas.
Antes que nada, declaremos algunas variables que estaremos reutilizando durante el ciclo. Queremos configurar el ciclo para iterar a través de la cantidad de piezas que necesitamos. Obtenemos este valor multiplicando difficulty
por sí mismo. En este caso obtenemos 16.
En el Ciclo
1 |
for (i = 0; i < difficult * difficulty; i++) { |
2 |
piece = {}; |
3 |
piece.sx = xPos; |
4 |
piece.sy = yPos; |
5 |
pieces.push(piece); |
6 |
xPos += pieceWidth; |
7 |
if (xPos >= puzzleWidth) { |
8 |
xPos = 0; |
9 |
yPos += pieceHeight; |
10 |
}
|
11 |
}
|
Comienza creando un objeto piece
vacío. A continuación, añade las propiedades sx
y sy
al objeto. En la primera iteración, estos valores son 0
y representan el punto en nuestra imagen desde donde comenzaremos a dibujar. Ahora, empújalo a la agrupación pieces
. Este objeto también contendrá las propiedades xPos
y yPos
, que nos dirán la posición actual del rompecabezas donde la pieza se debe dibujar. Estaremos alternando los objetos antes de que sea reproducible, por lo que aún no es necesario establecer estos valores.
La última cosa haremos en cada ciclo es aumentar la variable xPos
junto a pieceWidth
. Antes de continuar con el ciclo, determinamos si necesitamos bajar a la siguiente fila de piezas verificando si xPos
está más allá del ancho del rompecabezas. Si es así, volvemos a establecer xPos
de nuevo a 0 y aumentamos yPos
junto a pieceHeight
.
Ahora ya tenemos debidamente guardadas todas nuestras piezas de rompecabezas en nuestra agrupación pieces
. Para este punto, el código se termina de ejecutar y espera a que el usuario interactúe. Configuramos un listener para los eventos de clic en el document
para lanzar la función shufflePuzzle()
cuando se detone, lo que comenzará el juego. Un gran consejo para cómo usar HTML.
9. La FunciónshufflePuzzle()
1 |
function shufflePuzzle() { |
2 |
pieces = shuffleArray(pieces); |
3 |
stage.clearRect(0, 0, puzzleWidth, puzzleHeight); |
4 |
let xPos = 0; |
5 |
let yPos = 0; |
6 |
for (const piece of pieces) { |
7 |
piece.xPos = xPos; |
8 |
piece.yPos = yPos; |
9 |
stage.drawImage( |
10 |
img, |
11 |
piece.sx, |
12 |
piece.sy, |
13 |
pieceWidth, |
14 |
pieceHeight, |
15 |
xPos, |
16 |
yPos, |
17 |
pieceWidth, |
18 |
pieceHeight
|
19 |
);
|
20 |
stage.strokeRect(xPos, yPos, pieceWidth, pieceHeight); |
21 |
xPos += pieceWidth; |
22 |
if (xPos >= puzzleWidth) { |
23 |
xPos = 0; |
24 |
yPos += pieceHeight; |
25 |
}
|
26 |
}
|
27 |
document.onpointerdown = onPuzzleClick; |
28 |
}
|
1 |
function shuffleArray(o){ |
2 |
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); |
3 |
return o; |
4 |
}
|
Primero lo primero: revuelve la agrupación pieces[]
. Aquí estaré usando una buena función de utilidad que revolverá los índices de la agrupación a la que se transfirió. La explicación de esta función va más allá de nuestro tutorial de HTML, así que seguiremos adelante, sabiendo que revolvimos exitosamente nuestras piezas. (Para obtener una introducción básica al shuffling, echa un vistazo a este tutorial.)
Primero hay que despejar los gráficos dibujados en el canvas
para hacer espacio al dibujo de nuestras piezas. A continuación, configuramos la agrupación de forma similar a como lo hicimos cuando creamos los objetos de pieza por primera vez.
En el Ciclo
1 |
for (i = 0; i < pieces.length; i++) { |
2 |
piece = pieces[i]; |
3 |
piece.xPos = xPos; |
4 |
piece.yPos = yPos; |
5 |
stage.drawImage( |
6 |
img, |
7 |
piece.sx, |
8 |
piece.sy, |
9 |
pieceWidth, |
10 |
pieceHeight, |
11 |
xPos, |
12 |
yPos, |
13 |
pieceWidth, |
14 |
pieceHeight
|
15 |
);
|
16 |
stage.strokeRect(xPos, yPos, pieceWidth, pieceHeight); |
17 |
xPos += _pieceWidth; |
18 |
if (xPos >= puzzleWidth) { |
19 |
xPos = 0; |
20 |
yPos += pieceHeight; |
21 |
}
|
22 |
}
|
Primero, utiliza la variable i
para configurar nuestra referencia a la pieza del objeto en el ciclo. Ahora completamos las propiedades xPos
y yPos
que mencioné anteriormente, lo que será nuestro 0
en nuestra primera itinerancia.
Ahora, por fin dibujamos nuestras piezas en el rompecabezas JS.
El primer parámetro de drawImage()
asigna la fuente de la imagen de la que queremos dibujar. A continuación usamos las propiedades sx
y sy
de los objetos piece, junto con pieceWidth
y pieceHeight
, para rellenar los parámetros que declaran el área de la imagen de la que hay que dibujar. Los cuatro últimos parámetros ajustan el área del canvas
donde queremos dibujar. Usamos los valores xPos
y yPos
que estamos construyendo en el ciclo y asignando al objeto.
Inmediatamente después de esto, dibujamos un trazo rápido alrededor de la pieza para darle un borde, lo que la separará gentilmente de las otras piezas.
Ahora esperamos a que el usuario tome una pieza para configurar otro click
listener. Esta vez, este detonará una función onPuzzleClick()
.

10. La Función onPuzzleClick()
1 |
function onPuzzleClick(e) { |
2 |
if (e.layerX || e.layerX === 0) { |
3 |
mouse.x = e.layerX - canvas.offsetLeft; |
4 |
mouse.y = e.layerY - canvas.offsetTop; |
5 |
} else if (e.offsetX || e.offsetX === 0) { |
6 |
mouse.x = e.offsetX - canvas.offsetLeft; |
7 |
mouse.y = e.offsetY - canvas.offsetTop; |
8 |
}
|
9 |
currentPiece = checkPieceClicked(); |
10 |
if (currentPiece !== null) { |
11 |
stage.clearRect( |
12 |
currentPiece.xPos, |
13 |
currentPiece.yPos, |
14 |
pieceWidth, |
15 |
pieceHeight
|
16 |
);
|
17 |
stage.save(); |
18 |
stage.globalAlpha = 0.9; |
19 |
stage.drawImage( |
20 |
img, |
21 |
currentPiece.sx, |
22 |
currentPiece.sy, |
23 |
pieceWidth, |
24 |
pieceHeight, |
25 |
mouse.x - pieceWidth / 2, |
26 |
mouse.y - pieceHeight / 2, |
27 |
pieceWidth, |
28 |
pieceHeight
|
29 |
);
|
30 |
stage.restore(); |
31 |
document.onpointermove = updatePuzzle; |
32 |
document.onpointerup = pieceDropped; |
33 |
}
|
34 |
}
|
Sabemos que se le dio clic al rompecabezas JS, ahora necesitamos determinar la pieza a la que se le dio clic. Este sencillo condicional nos dará la posición de nuestro cursor en todos los navegadores de escritorio modernos que admiten canvas
, utilizando tanto e.layerX
y e.layerY
o e.offsetX
y e.offsetY
. Utiliza estos valores para actualizar nuestro objeto mouse
al asignarle una propiedad x
y y
para mantener la posición actual del cursor. En este caso, la posición a la que se le dio clic.
En la línea 112, establecemos inmediatamente currentPiece
en el valor devuelto por nuestra función checkPieceClicked()
. Separamos este código porque queremos utilizarlo más adelante cuando arrastremos una pieza de nuestro rompecabezas JS. Te explicaré esta función en el siguiente paso.
Si el valor devuelto fue null
, simplemente no hacemos nada, ya que esto implica que el usuario no hizo clic en alguna pieza del rompecabezas. Sin embargo, si recuperamos una pieza del rompecabezas, queremos adjuntarla al mouse y desvanecerla un poco para mostrar las piezas de abajo. ¿Cómo lo hacemos?
Primero, borramos el área del canvas
donde se encontraba la pieza antes de dar clic en ella. Usamos clearRect()
de nuevo, solo que en este caso solo pasamos al área obtenida del objeto currentPiece
. Antes de volver a dibujarla, queremos save()
el contexto del canvas antes de seguir. Esto asegurará que todo lo que dibujemos después de guardar, no se dibuje simplemente sobre cualquier cosa en su camino. Hacemos esto porque desvaneceremos ligeramente la pieza arrastrada y queremos ver las piezas debajo de ella. Si no llamamos save()
, solamente dibujamos sobre cualquier gráfico que esté en el camino, aún si está desvanecido o no.
Ahora, dibujamos la imagen de manera que su centro se posicione en el puntero del mouse. Los primeros cinco parámetros de drawImage
siempre serán los mismos durante toda la aplicación. Al hacer clic, los dos parámetros se actualizarán para centrarse en el puntero del mouse. Los dos últimos parámetros, el width
y el height
para dibujar, tampoco cambiarán nunca.
Finalmente, llamamos el método restore()
. Esto esencialmente significa que terminamos de usar el nuevo valor alfa y queremos reestablecer las propiedades como estaban. Para finalizar esta función, agregamos dos listeners más: uno para cuando movemos el mouse (arrastrando la pieza del rompecabezas) y otro para cuando lo soltamos (soltar la pieza del rompecabezas).
11. La Función checkPieceClicked()
1 |
function checkPieceClicked() { |
2 |
for (const piece of pieces) { |
3 |
if ( |
4 |
mouse.x < piece.xPos || |
5 |
mouse.x > piece.xPos + pieceWidth || |
6 |
mouse.y < piece.yPos || |
7 |
mouse.y > piece.yPos + pieceHeight |
8 |
) { |
9 |
//PIECE NOT HIT
|
10 |
} else { |
11 |
return piece; |
12 |
}
|
13 |
}
|
14 |
return null; |
15 |
}
|
Ahora necesitamos retroceder un poco en nuestro tutorial de HTML. Ya determinamos a qué pieza se le dio clic, ¿pero cómo lo hicimos? Esto es muy sencillo de hecho. Lo que necesitamos hacer es recorrer todas las piezas del rompecabezas y determinar si el clic estaba dentro de los límites de nuestros objetos. Si encontramos uno, devolvemos el objeto que coincide y terminamos la función. Si no encontramos nada, devolvemos null
.
12. La Función updatePuzzle()
1 |
function updatePuzzle(e) { |
2 |
currentDropPiece = null; |
3 |
if (e.layerX || e.layerX === 0) { |
4 |
mouse.x = e.layerX - canvas.offsetLeft; |
5 |
mouse.y = e.layerY - canvas.offsetTop; |
6 |
} else if (e.offsetX || e.offsetX === 0) { |
7 |
mouse.x = e.offsetX - canvas.offsetLeft; |
8 |
mouse.y = e.offsetY - canvas.offsetTop; |
9 |
}
|
10 |
stage.clearRect(0, 0, puzzleWidth, puzzleHeight); |
11 |
for (const piece of pieces) { |
12 |
if (piece === currentPiece) { |
13 |
continue; |
14 |
}
|
15 |
stage.drawImage( |
16 |
img, |
17 |
piece.sx, |
18 |
piece.sy, |
19 |
pieceWidth, |
20 |
pieceHeight, |
21 |
piece.xPos, |
22 |
piece.yPos, |
23 |
pieceWidth, |
24 |
pieceHeight
|
25 |
);
|
26 |
stage.strokeRect(piece.xPos, piece.yPos, pieceWidth, pieceHeight); |
27 |
if (currentDropPiece === null) { |
28 |
if ( |
29 |
mouse.x < piece.xPos || |
30 |
mouse.x > piece.xPos + pieceWidth || |
31 |
mouse.y < piece.yPos || |
32 |
mouse.y > piece.yPos + pieceHeight |
33 |
) { |
34 |
//NOT OVER
|
35 |
} else { |
36 |
currentDropPiece = piece; |
37 |
stage.save(); |
38 |
stage.globalAlpha = 0.4; |
39 |
stage.fillStyle = PUZZLE_HOVER_TINT; |
40 |
stage.fillRect( |
41 |
currentDropPiece.xPos, |
42 |
currentDropPiece.yPos, |
43 |
pieceWidth, |
44 |
pieceHeight
|
45 |
);
|
46 |
stage.restore(); |
47 |
}
|
48 |
}
|
49 |
}
|
50 |
stage.save(); |
51 |
stage.globalAlpha = 0.6; |
52 |
stage.drawImage( |
53 |
img, |
54 |
currentPiece.sx, |
55 |
currentPiece.sy, |
56 |
pieceWidth, |
57 |
pieceHeight, |
58 |
mouse.x - pieceWidth / 2, |
59 |
mouse.y - pieceHeight / 2, |
60 |
pieceWidth, |
61 |
pieceHeight
|
62 |
);
|
63 |
stage.restore(); |
64 |
stage.strokeRect( |
65 |
mouse.x - pieceWidth / 2, |
66 |
mouse.y - pieceHeight / 2, |
67 |
pieceWidth, |
68 |
pieceHeight
|
69 |
);
|
70 |
}
|
De vuelta al arrastre y continuando con nuestro tutorial para cómo usar HTML. Llamaremos esta función cuando el usuario mueva el mouse. Esta el la función más grande de la aplicación, ya que hace muchas cosas y te ayuda a saber cómo usar HTML. Comencemos. Iré explicando a medida que avancemos.
1 |
currentDropPiece = null; |
2 |
if (e.layerX || e.layerX === 0) { |
3 |
mouse.x = e.layerX - canvas.offsetLeft; |
4 |
mouse.y = e.layerY - canvas.offsetTop; |
5 |
} else if (e.offsetX || e.offsetX === 0) { |
6 |
mouse.x = e.offsetX - canvas.offsetLeft; |
7 |
mouse.y = e.offsetY - canvas.offsetTop; |
8 |
}
|
9 |
Comienza configurando currentDropPiece
a null
. Necesitamos reestablecer esto de vuelta a null
en la actualización debido a la posibilidad de que nuestra pieza se arrastre de vuelta a su sitio original. No queremos que el valor anterior currentDropPiece
se quede colgando. A continuación, ajustamos el objeto mouse
de la misma forma que hicimos al hacer clic.
1 |
stage.clearRect(0, 0, puzzleWidth, puzzleHeight); |
Aquí, necesitamos borrar todos los gráficos en el canvas. Básicamente necesitamos volver a dibujar las piezas del rompecabezas, ya que el objeto que se arrastre a la parte superior afectará su apariencia. De lo contrario, veríamos resultados muy extraños al seguir el camino de la pieza arrastrada.
1 |
for (const piece of pieces) { |
Comienza por configurar nuestras piezas de siempre en el ciclo.
En el Ciclo
1 |
if(piece === currentPiece){ |
2 |
continue; |
3 |
} |
Crea nuestra piece
de referencia de manera usual. A continuación, revisa si la pieza a la que hacemos referencia es la que actualmente estamos arrastrando. Si este es el caso, sigue con el ciclo. Esto mantendrá vacío el espacio de inicio de la pieza arrastrada.
1 |
stage.drawImage( |
2 |
img, |
3 |
piece.sx, |
4 |
piece.sy, |
5 |
pieceWidth, |
6 |
pieceHeight, |
7 |
piece.xPos, |
8 |
piece.yPos, |
9 |
pieceWidth, |
10 |
pieceHeight
|
11 |
);
|
12 |
stage.strokeRect(piece.xPos, piece.yPos, pieceWidth, pieceHeight); |
Sigamos adelante, vuelve a dibujar la pieza del rompecabezas utilizando exactamente las propiedades que empleamos la primera vez que las dibujamos. También necesitarás dibujar los bordes.
1 |
if (currentDropPiece === null) { |
2 |
if ( |
3 |
mouse.x < piece.xPos || |
4 |
mouse.x > piece.xPos + pieceWidth || |
5 |
mouse.y < piece.yPos || |
6 |
mouse.y > piece.yPos + pieceHeight |
7 |
) { |
8 |
//NOT OVER
|
9 |
} else { |
10 |
currentDropPiece = piece; |
11 |
stage.save(); |
12 |
stage.globalAlpha = 0.4; |
13 |
stage.fillStyle = PUZZLE_HOVER_TINT; |
14 |
stage.fillRect( |
15 |
currentDropPiece.xPos, |
16 |
currentDropPiece.yPos, |
17 |
pieceWidth, |
18 |
pieceHeight
|
19 |
);
|
20 |
stage.restore(); |
21 |
}
|
22 |
}
|
Debido a que tenemos una referencia para cada objeto en el ciclo, también podemos usar esta oportunidad para revisar si la pieza arrastrada está en la parte superior. Hacemos esto debido a que queremos darle al usuario retroalimentación respecto a qué pieza se puede colocar. Ahora adentrémonos en el código.
Primero, queremos ver si este ciclo produjo un destino de colocación. Si es así, no necesitamos molestarnos, ya que se puede colocar un objetivo para cada movimiento del cursor. De no ser así, currentDropPiece
será null
, y podemos proceder con la lógica. Debido a que nuestro cursor está en medio de la pieza arrastrada, todo lo que realmente necesitamos hacer es determinar sobre qué otra pieza está nuestro mouse.
A continuación, usaremos nuestra útil función checkPieceClicked()
para determinar si el cursor se está desplazando sobre el objeto de la pieza actual en el ciclo. De ser así, configuramos la variable currentDropPiece
y dibujaremos un cuadro coloreado sobre la pieza del rompecabezas, así indicarás que ahora es el destino de colocación.
Recuerda hacer save()
y restore()
. De otro modo, obtendrás el cuadro coloreado y no la imagen de abajo.
Fuera del Ciclo
1 |
stage.save(); |
2 |
stage.globalAlpha = 0.6; |
3 |
stage.drawImage( |
4 |
img, |
5 |
currentPiece.sx, |
6 |
currentPiece.sy, |
7 |
pieceWidth, |
8 |
pieceHeight, |
9 |
mouse.x - pieceWidth / 2, |
10 |
mouse.y - pieceHeight / 2, |
11 |
pieceWidth, |
12 |
pieceHeight
|
13 |
);
|
14 |
stage.restore(); |
15 |
stage.strokeRect( |
16 |
mouse.x - pieceWidth / 2, |
17 |
mouse.y - pieceHeight / 2, |
18 |
pieceWidth, |
19 |
pieceHeight
|
20 |
);
|
Por último pero no menos importante, necesitamos volver a dibujar la pieza arrastrada. El código es el mismo que cuando le dimos clic por primera vez, pero el cursor se movió, por lo que su posición se actualizará.
13. La Función pieceDropped()
1 |
function pieceDropped(e){ |
2 |
document.onpointermove = null; |
3 |
document.onpointerup = null; |
4 |
if(currentDropPiece !== null){ |
5 |
let tmp = {xPos:currentPiece.xPos,yPos:currentPiece.yPos}; |
6 |
currentPiece.xPos = currentDropPiece.xPos; |
7 |
currentPiece.yPos = currentDropPiece.yPos; |
8 |
currentDropPiece.xPos = tmp.xPos; |
9 |
currentDropPiece.yPos = tmp.yPos; |
10 |
}
|
11 |
resetPuzzleAndCheckWin(); |
12 |
}
|
Muy bien, ya pasamos la parte más difícil. Ya arrastramos con éxito la pieza del rompecabezas e incluso obtuvimos retroalimentación visual sobre dónde se colocará. Ahora lo que falta es soltar la pieza. Primero, quitemos los listeners inmediatamente debido a que no se está arrastrando nada.
Ahora, revisa que _currentDropPiece
no está en null
. Si lo está, significa que la estamos arrastrando de vuelta al área de inicio de la pieza y no sobre otro espacio. Si no está en null
, podemos continuar con la función.
Lo que ahora tenemos que hacer es solamente cambiar el xPos
y yPos
de cada pieza. Para este punto de nuestros juegos HTML, ambas piezas tienen los nuevos valores xPos
y yPos
, y se instalarán en sus nuevas posiciones en la siguiente partida. Eso es lo que haremos a continuación, revisar simultáneamente si se ganó el juego.
14. La Función resetPuzzleAndCheckWin()
1 |
function resetPuzzleAndCheckWin() { |
2 |
stage.clearRect(0, 0, puzzleWidth, puzzleHeight); |
3 |
let gameWin = true; |
4 |
for (piece in pieces) { |
5 |
stage.drawImage( |
6 |
img, |
7 |
piece.sx, |
8 |
piece.sy, |
9 |
pieceWidth, |
10 |
pieceHeight, |
11 |
piece.xPos, |
12 |
piece.yPos, |
13 |
pieceWidth, |
14 |
pieceHeight
|
15 |
);
|
16 |
stage.strokeRect(piece.xPos, piece.yPos, pieceWidth, pieceHeight); |
17 |
if (piece.xPos != piece.sx || piece.yPos != piece.sy) { |
18 |
gameWin = false; |
19 |
}
|
20 |
}
|
21 |
if (gameWin) { |
22 |
setTimeout(gameOver, 500); |
23 |
}
|
24 |
}
|
El siguiente paso para cómo hacer rompecabezas, limpiaremos el canvas
y configuraremos la variable gameWin
, ajustándola a true
por defecto. Ahora procede con nuestro conocido ciclo de piezas.
El código aquí debería verse familiar, así que no lo repasaremos. Simplemente dibuja las piezas de vuelta a su posición original o a nuevos espacios. Dentro de este ciclo, queremos ver si cada pieza se está dibujando en su posición ganadora. Esto es sencillo: revisamos para ver si nuestras propiedades sx
y sy
se ajustan con xPos
y yPos
. De no ser así, no podríamos ganar el juego y configurar gameWin
a false
. Si superamos el ciclo con todos en sus posiciones de ganar, configuramos un timeout
rápido para llamar a nuestro método gameOver()
. (Configuramos un tiempo de espera para que la pantalla no cambie tan drásticamente al dejar caer la pieza del rompecabezas)
15. La Función gameOver()
1 |
function gameOver() { |
2 |
document.onpointerdown = null; |
3 |
document.onpointermove = null; |
4 |
document.onpointerup = null; |
5 |
initPuzzle(); |
6 |
}
|
¡Esta es nuestra última función para las mecánicas del juego principal y para aprender cómo hacer rompecabezas! Aquí, simplemente eliminaremos todos los listeners y llamaremos initPuzzle()
, el cual reinicia todos los valores necesarios y espera a que el usuario vuelva a jugar
16. Agrega un Control Deslizante de Dificultad
Ahora, agregaremos el comportamiento al control deslizante de dificultad para cómo usar HTML. Primero, crea una función updateDifficulty()
.
1 |
function updateDifficulty(e) { |
2 |
difficulty = e.target.value; |
3 |
pieceWidth = Math.floor(img.width / difficulty); |
4 |
pieceHeight = Math.floor(img.height / difficulty); |
5 |
puzzleWidth = pieceWidth * difficulty; |
6 |
puzzleHeight = pieceHeight * difficulty; |
7 |
gameOver(); |
8 |
}
|
Veamos esto paso a paso para saber cómo usar HTML.
1 |
difficulty = e.target.value; |
Esta parte actualiza la dificultad para que se establezca en el valor del control deslizante.
1 |
pieceWidth = Math.floor(img.width / difficulty); |
2 |
pieceHeight = Math.floor(img.height / difficulty); |
3 |
puzzleWidth = pieceWidth * difficulty; |
4 |
puzzleHeight = pieceHeight * difficulty; |
Este fragmento actualiza las piezas para que tengan el tamaño correcto según la dificultad.
1 |
gameOver(); |
Finalmente, esto reinicia el juego con la nueva dificultad.
Conclusión
Como ves, puedes hacer muchas cosas nuevas y creativas en HTML5 mediante el uso de áreas de bit seleccionadas de imágenes y dibujos. Puedes ampliar esta aplicación fácilmente con tan solo agregar puntuación e incluso un temporizador para darle más aspectos a tus juegos HTML.
Este artículo se actualizó con las contribuciones de Jacob Jackson. Jacob es un desarrollador web, escritor técnico y colaborador de código abierto.