Come ho realizzato un gioco puzzle CSS puro PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.

Come ho creato un gioco di puzzle CSS puro

Di recente ho scoperto la gioia di creare giochi solo CSS. È sempre affascinante come HTML e CSS siano in grado di gestire la logica di un intero gioco online, quindi ho dovuto provarlo! Tali giochi di solito si basano sul vecchio Checkbox Hack in cui combiniamo lo stato selezionato/deselezionato di un input HTML con il :checked pseudo-classe in CSS. Possiamo fare molta magia con quell'unica combinazione!

In effetti, mi sono sfidato a costruire un intero gioco senza Checkbox. Non ero sicuro che sarebbe stato possibile, ma lo è sicuramente e ti mostrerò come.

Oltre al puzzle game che studieremo in questo articolo, ho realizzato una raccolta di giochi CSS puri, la maggior parte senza il Checkbox Hack. (Sono anche disponibili su CodePen.)

Vuoi giocare prima di iniziare?

Personalmente preferisco giocare in modalità a schermo intero, ma puoi giocarci di seguito o aprilo qui.

Fresco vero? Lo so, non è il miglior gioco di puzzle che tu abbia mai visto ™ ma non è affatto male per qualcosa che usa solo CSS e poche righe di HTML. Puoi facilmente regolare la dimensione della griglia, cambiare il numero di celle per controllare il livello di difficoltà e utilizzare l'immagine che desideri!

Rifaremo insieme quella demo, quindi ci metteremo un po' di brillantezza in più alla fine per alcuni calci.

La funzionalità di trascinamento della selezione

Mentre la struttura del puzzle è abbastanza semplice con CSS Grid, la possibilità di trascinare e rilasciare i pezzi del puzzle è un po' più complicata. Ho dovuto fare affidamento su una combinazione di transizioni, effetti al passaggio del mouse e selettori fratelli per farlo.

Se passi il mouse sopra la casella vuota in quella demo, l'immagine si sposta al suo interno e rimane lì anche se sposti il ​​cursore fuori dalla casella. Il trucco consiste nell'aggiungere una grande durata di transizione e un ritardo, così grande che l'immagine impiega molto tempo per tornare alla sua posizione iniziale.

img {
  transform: translate(200%);
  transition: 999s 999s; /* very slow move on mouseout */
}
.box:hover img {
  transform: translate(0);
  transition: 0s; /* instant move on hover */
}

Specificando solo il transition-delay è sufficiente, ma l'utilizzo di valori grandi sia sul ritardo che sulla durata diminuisce la possibilità che un giocatore veda l'immagine tornare indietro. Se aspetti 999s + 999s — che è di circa 30 minuti — quindi vedrai l'immagine muoversi. Ma non lo farai, giusto? Voglio dire, nessuno ci metterà così tanto tra un turno e l'altro a meno che non si allontani dal gioco. Quindi, lo considero un buon trucco per passare da uno stato all'altro.

Hai notato che il passaggio del mouse sull'immagine attiva anche le modifiche? Questo perché l'immagine fa parte dell'elemento box, il che non va bene per noi. Possiamo risolvere questo problema aggiungendo pointer-events: none sull'immagine ma non potremo trascinarla in seguito.

Ciò significa che dobbiamo introdurre un altro elemento all'interno di .box:

Quel extra div (stiamo usando una classe di .a) occuperà la stessa area dell'immagine (grazie a CSS Grid e grid-area: 1 / 1) e sarà l'elemento che attiverà l'effetto hover. Ed è qui che entra in gioco il selettore di fratelli:

.a {
  grid-area: 1 / 1;
}
img {
  grid-area: 1 / 1;
  transform: translate(200%);
  transition: 999s 999s;
}
.a:hover + img {
  transform: translate(0);
  transition: 0s;
}

In bilico sul .a element sposta l'immagine e, poiché occupa tutto lo spazio all'interno della scatola, è come se ci trovassimo invece sopra la scatola! Passare sopra l'immagine non è più un problema!

Trasciniamo e rilasciamo la nostra immagine all'interno del riquadro e vediamo il risultato:

Hai visto che? Per prima cosa prendi l'immagine e la sposti nella scatola, niente di speciale. Ma una volta rilasciata l'immagine, si attiva l'effetto al passaggio del mouse che sposta l'immagine, quindi simuliamo una funzione di trascinamento della selezione. Se rilasci il mouse fuori dagli schemi, non succede nulla.

Hmm, la tua simulazione non è perfetta perché possiamo anche passare con il mouse sulla scatola e ottenere lo stesso effetto.

Vero e lo correggeremo. Dobbiamo disabilitare l'effetto al passaggio del mouse e consentirlo solo se rilasciamo l'immagine all'interno della scatola. Giocheremo con la dimensione del nostro .a elemento per farlo accadere.

Ora, passare sopra la scatola non fa nulla. Ma se inizi a trascinare l'immagine, il file .a appare l'elemento, e una volta rilasciato all'interno della scatola, possiamo attivare l'effetto hover e spostare l'immagine.

Analizziamo il codice:

.a {
  width: 0%;
  transition: 0s .2s; /* add a small delay to make sure we catch the hover effect */
}
.box:active .a { /* on :active increase the width */
  width: 100%;
  transition: 0s; /* instant change */
}
img {
  transform: translate(200%);
  transition: 999s 999s;
}
.a:hover + img {
  transform: translate(0);
  transition: 0s;
}

Cliccando sull'immagine si attiva il :active pseudo-classe che rende il .a elemento a larghezza intera (inizialmente è uguale a 0). Lo stato attivo rimarrà attivo finché non rilasciamo l'immagine. Se rilasciamo l'immagine all'interno della scatola, il .a l'elemento risale a width: 0, ma attiveremo l'effetto al passaggio del mouse prima che accada e l'immagine cadrà all'interno della scatola! Se lo rilasci fuori dagli schemi, non succede nulla.

C'è una piccola stranezza: facendo clic sulla casella vuota si sposta anche l'immagine e si interrompe la nostra funzione. Attualmente, :active è collegato al .box elemento, quindi cliccando su di esso o su uno qualsiasi dei suoi figli lo attiverà; e così facendo, finiamo per mostrare il .a elemento e attivando l'effetto hover.

Possiamo risolverlo giocando con pointer-events. Ci consente di disabilitare qualsiasi interazione con il .box mantenendo le interazioni con gli elementi figlio.

.box {
  pointer-events: none;
}
.box * {
  pointer-events: initial;
}

Adesso la nostra funzione di trascinamento della selezione è perfetta. A meno che tu non riesca a trovare come hackerarlo, l'unico modo per spostare l'immagine è trascinarla e rilasciarla all'interno della casella.

Costruire la griglia del puzzle

Mettere insieme il puzzle sembrerà facile rispetto a quello che abbiamo appena fatto per la funzione di trascinamento della selezione. Faremo affidamento sulla griglia CSS e sui trucchi in background per creare il puzzle.

Ecco la nostra griglia, scritta in Pug per comodità:

- let n = 4; /* number of columns/rows */
- let image = "https://picsum.photos/id/1015/800/800";

g(style=`--i:url(${image})`)
  - for(let i = 0; i < n*n; i++)
    z
      a
      b(draggable="true") 

Il codice può sembrare strano ma viene compilato in un semplice HTML:

<g style="--i: url(https://picsum.photos/id/1015/800/800)">
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
  <!-- etc. -->
</g>

Scommetto che ti starai chiedendo cosa succede con quei tag. Nessuno di questi elementi ha un significato speciale: trovo solo che il codice sia molto più facile da scrivere usando <z> di un mucchio di <div class="z"> o qualsiasi altra cosa

Ecco come li ho mappati:

  • <g> è il nostro contenitore di griglia che contiene N*N <z> elementi.
  • <z> rappresenta i nostri elementi della griglia. Svolge il ruolo di .box elemento che abbiamo visto nella sezione precedente.
  • <a> attiva l'effetto hover.
  • <b> rappresenta una parte della nostra immagine. Applichiamo il draggable attributo su di esso perché non può essere trascinato per impostazione predefinita.

Va bene, registriamo il nostro contenitore della griglia <g>. Questo è in Sass invece di CSS:

$n : 4; /* number of columns/rows */

g {
  --s: 300px; /* size of the puzzle */

  display: grid;
  max-width: var(--s);
  border: 1px solid;
  margin: auto;
  grid-template-columns: repeat($n, 1fr);
}

In realtà creeremo i nostri figli della griglia: il <z> elementi — anche griglie e hanno entrambi <a> ed <b> all'interno della stessa area della griglia:

z {
  aspect-ratio: 1;
  display: grid;
  outline: 1px dashed;
}
a {
  grid-area: 1/1;
}
b {
  grid-area: 1/1;
}

Come puoi vedere, niente di speciale: abbiamo creato una griglia con una dimensione specifica. Il resto del CSS di cui abbiamo bisogno è per la funzione di trascinamento della selezione, che richiede di posizionare casualmente i pezzi sul tabellone. Mi rivolgerò a Sass per questo, sempre per la comodità di poter scorrere e modellare tutti i pezzi del puzzle con una funzione:

b {
  background: var(--i) 0/var(--s) var(--s);
}

@for $i from 1 to ($n * $n + 1) {
  $r: (random(180));
  $x: (($i - 1)%$n);
  $y: floor(($i - 0.001) / $n);
  z:nth-of-type(#{$i}) b{
    background-position: ($x / ($n - 1)) * 100% ($y / ($n - 1)) * 100%;
    transform: 
      translate((($n - 1) / 2 - $x) * 100%, (($n - 1)/2 - $y) * 100%) 
      rotate($r * 1deg) 
      translate((random(100)*1% + ($n - 1) * 100%)) 
      rotate((random(20) - 10 - $r) * 1deg)
   }
}

Potresti aver notato che sto usando il Sass random() funzione. È così che otteniamo le posizioni casuali per i pezzi del puzzle. Ricorda che lo faremo disable quella posizione quando si passa sopra il <a> elemento dopo aver trascinato e rilasciato il suo corrispondente <b> elemento all'interno della cella della griglia.

z a:hover ~ b {
  transform: translate(0);
  transition: 0s;
}

Nello stesso ciclo, sto anche definendo la configurazione di sfondo per ogni pezzo del puzzle. Tutti loro condivideranno logicamente la stessa immagine dello sfondo e la sua dimensione dovrebbe essere uguale alla dimensione dell'intera griglia (definita con il --s variabile). Usando lo stesso background-image e un po' di matematica, aggiorniamo il background-position per mostrare solo una parte dell'immagine.

Questo è tutto! Il nostro gioco di puzzle solo CSS è tecnicamente fatto!

Ma possiamo sempre fare di meglio, giusto? Ti ho mostrato come creare una griglia di forme di pezzi di puzzle in un altro articolo. Prendiamo la stessa idea e applichiamola qui, vero?

Forme di pezzi di puzzle

Ecco il nostro nuovo gioco di puzzle. Stessa funzionalità ma con forme più realistiche!

Questa è un'illustrazione delle forme sulla griglia:

Come ho creato un gioco di puzzle CSS puro

Se guardi da vicino noterai che abbiamo nove diverse forme di pezzi di puzzle: il quattro angoli, le quattro bordie uno per tutto il resto.

La griglia di pezzi del puzzle che ho creato nell'altro articolo a cui ho fatto riferimento è un po' più semplice:

Possiamo usare la stessa tecnica che combina maschere CSS e gradienti per creare le diverse forme. Nel caso tu non abbia familiarità con mask e gradienti, consiglio vivamente di controllare quel caso semplificato per comprendere meglio la tecnica prima di passare alla parte successiva.

Innanzitutto, dobbiamo utilizzare selettori specifici per indirizzare ogni gruppo di elementi che condividono la stessa forma. Abbiamo nove gruppi, quindi utilizzeremo otto selettori, più un selettore predefinito che li seleziona tutti.

z  /* 0 */

z:first-child  /* 1 */

z:nth-child(-n + 4):not(:first-child) /* 2 */

z:nth-child(5) /* 3 */

z:nth-child(5n + 1):not(:first-child):not(:nth-last-child(5)) /* 4 */

z:nth-last-child(5)  /* 5 */

z:nth-child(5n):not(:nth-child(5)):not(:last-child) /* 6 */

z:last-child /* 7 */

z:nth-last-child(-n + 4):not(:last-child) /* 8 */

Ecco una figura che mostra come viene mappata sulla nostra griglia:

Come ho realizzato un gioco puzzle CSS puro PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.
Come ho creato un gioco di puzzle CSS puro

Ora affrontiamo le forme. Concentriamoci sull'apprendimento solo di una o due delle forme perché usano tutte la stessa tecnica e, in questo modo, hai dei compiti per continuare ad imparare!

Per i pezzi del puzzle al centro della griglia, 0:

mask: 
  radial-gradient(var(--r) at calc(50% - var(--r) / 2) 0, #0000 98%, #000) var(--r)  
    0 / 100% var(--r) no-repeat,
  radial-gradient(var(--r) at calc(100% - var(--r)) calc(50% - var(--r) / 2), #0000 98%, #000) 
    var(--r) 50% / 100% calc(100% - 2 * var(--r)) no-repeat,
  radial-gradient(var(--r) at var(--r) calc(50% - var(--r) / 2), #000 98%, #0000),
  radial-gradient(var(--r) at calc(50% + var(--r) / 2) calc(100% - var(--r)), #000 98%, #0000);

Il codice può sembrare complesso, ma concentriamoci su un gradiente alla volta per vedere cosa sta succedendo:

Due gradienti creano due cerchi (contrassegnati in verde e viola nella demo) e altri due gradienti creano gli slot a cui si collegano gli altri pezzi (quello contrassegnato in blu riempie la maggior parte della forma mentre quello contrassegnato in rosso riempie la parte superiore). Una variabile CSS, --r, imposta il raggio delle forme circolari.

Come ho realizzato un gioco puzzle CSS puro PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.
Come ho creato un gioco di puzzle CSS puro

La forma dei pezzi del puzzle al centro (contrassegnata 0 nell'illustrazione) è il più difficile da realizzare poiché utilizza quattro gradienti e ha quattro curvature. Tutti gli altri pezzi si destreggiano con meno gradienti.

Ad esempio, i pezzi del puzzle lungo il bordo superiore del puzzle (contrassegnati 2 nell'illustrazione) utilizza tre gradienti invece di quattro:

mask: 
  radial-gradient(var(--r) at calc(100% - var(--r)) calc(50% + var(--r) / 2), #0000 98%, #000) var(--r) calc(-1 * var(--r)) no-repeat,
  radial-gradient(var(--r) at var(--r) calc(50% - var(--r) / 2), #000 98%, #0000),
  radial-gradient(var(--r) at calc(50% + var(--r) / 2) calc(100% - var(--r)), #000 98%, #0000);

Abbiamo rimosso il primo gradiente (in alto) e regolato i valori del secondo gradiente in modo che copra lo spazio lasciato. Non noterai una grande differenza nel codice se confronti i due esempi. Va notato che possiamo trovare diverse configurazioni di sfondo per creare la stessa forma. Se inizi a giocare con i gradienti troverai sicuramente qualcosa di diverso da quello che ho fatto io. Potresti anche scrivere qualcosa di più conciso, in tal caso condividilo nei commenti!

Oltre a creare le forme, scoprirai anche che sto aumentando la larghezza e/o l'altezza degli elementi come di seguito:

height: calc(100% + var(--r));
width: calc(100% + var(--r));

I pezzi del puzzle devono traboccare dalla loro cella della griglia per connettersi.

Come ho realizzato un gioco puzzle CSS puro PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.
Come ho creato un gioco di puzzle CSS puro

Demo finale

Ecco di nuovo la demo completa. Se lo confronti con la prima versione vedrai la stessa struttura del codice per creare la griglia e la funzione di trascinamento della selezione, oltre al codice per creare le forme.

Possibili miglioramenti

L'articolo finisce qui ma potremmo continuare a migliorare il nostro puzzle con ancora più funzionalità! Che ne dici di un timer? O forse una sorta di congratulazioni quando il giocatore finisce il puzzle?

Potrei considerare tutte queste funzionalità in una versione futura, quindi tieni d'occhio il mio repository GitHub.

Concludendo

E altre ancora… I CSS non sono un linguaggio di programmazione, dicono. Ah!

Non sto cercando di suscitare un po' di #HotDrama con questo. Lo dico perché abbiamo fatto alcune cose di logica davvero complicate e abbiamo coperto molte proprietà e tecniche CSS lungo il percorso. Abbiamo giocato con la griglia CSS, le transizioni, il mascheramento, i gradienti, i selettori e le proprietà dello sfondo. Per non parlare dei pochi trucchi Sass che abbiamo usato per rendere il nostro codice facile da regolare.

L'obiettivo non era costruire il gioco, ma esplorare i CSS e scoprire nuove proprietà e trucchi che puoi usare in altri progetti. La creazione di un gioco online in CSS è una sfida che ti spinge a esplorare le funzionalità CSS in modo molto dettagliato e ad imparare a usarle. Inoltre, è molto divertente avere qualcosa con cui giocare quando tutto è stato detto e fatto.

Che i CSS siano o meno un linguaggio di programmazione, non cambia il fatto che impariamo sempre costruendo e creando cose innovative.

Timestamp:

Di più da Trucchi CSS