Wie ich ein reines CSS-Puzzlespiel erstellt habe PlatoBlockchain Data Intelligence. Vertikale Suche. Ai.

Wie ich ein reines CSS-Puzzlespiel gemacht habe

Ich habe kürzlich die Freude entdeckt, reine CSS-Spiele zu erstellen. Es ist immer wieder faszinierend, wie HTML und CSS in der Lage sind, die Logik eines ganzen Online-Spiels zu handhaben, also musste ich es ausprobieren! Solche Spiele verlassen sich normalerweise auf den alten Checkbox-Hack, bei dem wir den aktivierten/nicht aktivierten Zustand einer HTML-Eingabe mit dem kombinieren :checked Pseudoklasse in CSS. Mit dieser einen Kombination können wir viel zaubern!

Tatsächlich habe ich mich selbst herausgefordert, ein ganzes Spiel ohne Checkbox zu bauen. Ich war mir nicht sicher, ob es möglich wäre, aber es ist definitiv möglich, und ich werde Ihnen zeigen, wie.

Zusätzlich zu dem Puzzlespiel, das wir in diesem Artikel studieren werden, habe ich es gemacht eine Sammlung von reinen CSS-Spielen, die meisten davon ohne den Checkbox-Hack. (Sie sind auch verfügbar auf CodePen.)

Willst du spielen, bevor wir anfangen?

Ich persönlich bevorzuge es, das Spiel im Vollbildmodus zu spielen, aber Sie können es unten oder spielen öffne es hier drüben.

Cool oder? Ich weiß, es ist nicht das beste Puzzlespiel, das Sie je gesehen haben™, aber es ist auch überhaupt nicht schlecht für etwas, das nur CSS und ein paar Zeilen HTML verwendet. Sie können die Größe des Gitters ganz einfach anpassen, die Anzahl der Zellen ändern, um den Schwierigkeitsgrad zu steuern, und jedes gewünschte Bild verwenden!

Wir werden diese Demo zusammen neu machen und ihr dann am Ende für ein paar Kicks ein wenig mehr Glanz verleihen.

Die Drag-and-Drop-Funktionalität

Während die Struktur des Puzzles mit CSS Grid ziemlich einfach ist, ist die Möglichkeit, Puzzleteile per Drag & Drop zu verschieben, etwas kniffliger. Ich musste mich auf eine Kombination aus Übergängen, Hover-Effekten und Geschwisterselektoren verlassen, um es zu erledigen.

Wenn Sie den Mauszeiger über das leere Kästchen in dieser Demo bewegen, bewegt sich das Bild darin und bleibt dort, auch wenn Sie den Cursor aus dem Kästchen herausbewegen. Der Trick besteht darin, eine große Übergangsdauer und -verzögerung hinzuzufügen – so groß, dass das Bild viel Zeit braucht, um in seine Ausgangsposition zurückzukehren.

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

Nur die Angabe transition-delay reicht aus, aber die Verwendung großer Werte sowohl für die Verzögerung als auch für die Dauer verringert die Wahrscheinlichkeit, dass ein Spieler jemals sieht, wie sich das Bild zurückbewegt. Wenn Sie warten 999s + 999s – was ungefähr 30 Minuten dauert – dann sehen Sie, wie sich das Bild bewegt. Aber das wirst du nicht, oder? Ich meine, niemand wird so lange zwischen den Zügen brauchen, es sei denn, er verlässt das Spiel. Ich halte dies also für einen guten Trick, um zwischen zwei Zuständen zu wechseln.

Haben Sie bemerkt, dass auch das Bewegen des Mauszeigers über das Bild die Änderungen auslöst? Das liegt daran, dass das Bild Teil des Box-Elements ist, was nicht gut für uns ist. Wir können dies beheben, indem wir hinzufügen pointer-events: none zum Bild, aber wir können es später nicht mehr ziehen.

Das bedeutet, dass wir ein weiteres Element in die einführen müssen .box:

Das Extra div (Wir verwenden eine Klasse von .a) wird den gleichen Bereich wie das Bild einnehmen (dank CSS Grid und grid-area: 1 / 1) und wird das Element sein, das den Hover-Effekt auslöst. Und hier kommt der Geschwisterselektor ins Spiel:

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

Schweben auf der .a -Element verschiebt das Bild, und da es den gesamten Platz innerhalb der Box einnimmt, ist es, als würden wir stattdessen über der Box schweben! Das Bewegen des Bildes ist kein Problem mehr!

Lassen Sie uns unser Bild per Drag & Drop in das Feld ziehen und das Ergebnis sehen:

Hast du das gesehen? Sie nehmen zuerst das Bild und verschieben es in die Box, nichts Besonderes. Aber sobald Sie das Bild loslassen, lösen Sie den Hover-Effekt aus, der das Bild bewegt, und dann simulieren wir eine Drag-and-Drop-Funktion. Wenn Sie die Maus außerhalb des Feldes loslassen, passiert nichts.

Hmm, Ihre Simulation ist nicht perfekt, weil wir die Box auch schweben lassen können und den gleichen Effekt erzielen.

Stimmt und wir werden dies korrigieren. Wir müssen den Hover-Effekt deaktivieren und ihn nur zulassen, wenn wir das Bild in der Box freigeben. Wir werden mit der Dimension unserer spielen .a Element, um dies zu ermöglichen.

Jetzt bewirkt das Schweben der Box nichts. Aber wenn Sie anfangen, das Bild zu ziehen, wird die .a Element erscheint, und sobald es in der Box losgelassen wird, können wir den Hover-Effekt auslösen und das Bild verschieben.

Lassen Sie uns den Code analysieren:

.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;
}

Ein Klick auf das Bild löst die aus :active Pseudo-Klasse, die die macht .a Element in voller Breite (es ist anfänglich gleich 0). Der aktive Zustand bleibt bestehen aktiv bis wir das Bild freigeben. Wenn wir das Bild in der Box freigeben, wird die .a Element geht zurück auf width: 0, aber wir lösen den Hover-Effekt aus, bevor er passiert, und das Bild fällt in die Box! Wenn Sie es außerhalb der Box loslassen, passiert nichts.

Es gibt eine kleine Eigenart: Das Klicken auf das leere Kästchen verschiebt auch das Bild und unterbricht unsere Funktion. Zur Zeit, :active ist mit dem verbunden .box -Element, also wird es durch Klicken darauf oder eines seiner untergeordneten Elemente aktiviert; und dadurch zeigen wir am Ende die .a Element und löst den Hover-Effekt aus.

Wir können das beheben, indem wir mit spielen pointer-events. Es ermöglicht uns, jede Interaktion mit dem zu deaktivieren .box während die Interaktionen mit den untergeordneten Elementen beibehalten werden.

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

Unsere Drag-and-Drop-Funktion ist perfekt. Wenn Sie nicht wissen, wie Sie es hacken können, können Sie das Bild nur verschieben, indem Sie es per Drag & Drop in das Feld ziehen.

Aufbau des Puzzlerasters

Das Zusammensetzen des Puzzles wird sich im Vergleich zu dem, was wir gerade für die Drag-and-Drop-Funktion getan haben, als kinderleicht anfühlen. Wir werden uns auf CSS-Raster- und Hintergrundtricks verlassen, um das Puzzle zu erstellen.

Hier ist unser Raster, der Einfachheit halber in Pug geschrieben:

- 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") 

Der Code sieht vielleicht seltsam aus, lässt sich aber in einfaches HTML kompilieren:

<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>

Ich wette, Sie fragen sich, was es mit diesen Tags auf sich hat. Keines dieser Elemente hat eine besondere Bedeutung – ich finde nur, dass der Code viel einfacher zu schreiben ist <z> als ein Haufen <div class="z"> oder Wasauchimmer.

So habe ich sie abgebildet:

  • <g> ist unser Grid-Container, der enthält N*N <z> Elemente.
  • <z> stellt unsere Gitterelemente dar. Es spielt die Rolle des .box Element, das wir im vorherigen Abschnitt gesehen haben.
  • <a> löst den Hover-Effekt aus.
  • <b> stellt einen Teil unseres Bildes dar. Wir wenden die an draggable Attribut darauf, da es standardmäßig nicht gezogen werden kann.

In Ordnung, registrieren wir unseren Grid-Container auf <g>. Dies ist in Sass anstelle von 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);
}

Wir werden tatsächlich unsere Grid-Kinder machen – die <z> Elemente — auch Gitter und haben beides <a> und <b> im selben Rasterbereich:

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

Wie Sie sehen können, nichts Besonderes – wir haben ein Raster mit einer bestimmten Größe erstellt. Der Rest des CSS, das wir brauchen, ist für die Drag-and-Drop-Funktion, bei der wir die Teile zufällig auf dem Brett platzieren müssen. Ich werde mich dafür an Sass wenden, wieder für die Bequemlichkeit, alle Puzzleteile mit einer Funktion durchlaufen und gestalten zu können:

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)
   }
}

Sie haben vielleicht bemerkt, dass ich den Sass benutze random() Funktion. So erhalten wir die zufälligen Positionen für die Puzzleteile. Denken Sie daran, dass wir es tun werden deaktivieren diese Position, wenn Sie mit der Maus über die schweben <a> Element nach dem Ziehen und Ablegen des entsprechenden Elements <b> Element innerhalb der Gitterzelle.

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

In derselben Schleife definiere ich auch die Hintergrundkonfiguration für jedes Puzzleteil. Alle teilen logischerweise dasselbe Bild wie der Hintergrund, und seine Größe sollte gleich der Größe des gesamten Rasters sein (definiert mit der --s Variable). Unter Verwendung des gleichen background-image und etwas Mathematik aktualisieren wir die background-position um nur einen Teil des Bildes zu zeigen.

Das ist es! Unser Nur-CSS-Puzzlespiel ist technisch fertig!

Aber wir können es immer besser machen, oder? ich habe es Ihnen gezeigt Wie erstelle ich ein Gitter aus Puzzleteilen in einem anderen Artikel. Nehmen wir die gleiche Idee und wenden sie hier an, sollen wir?

Formen von Puzzleteilen

Hier ist unser neues Puzzlespiel. Gleiche Funktionalität, aber mit realistischeren Formen!

Dies ist eine Illustration der Formen auf dem Gitter:

Wie ich ein reines CSS-Puzzlespiel gemacht habe

Wenn Sie genau hinsehen, werden Sie feststellen, dass wir neun verschiedene Formen von Puzzleteilen haben: die vier Ecken, der vier Kanten und einer für alles andere.

Das Raster aus Puzzleteilen, das ich in dem anderen Artikel erstellt habe, auf den ich mich bezogen habe, ist etwas einfacher:

Wir können dieselbe Technik verwenden, die CSS-Masken und Farbverläufe kombiniert, um die verschiedenen Formen zu erstellen. Falls Sie sich nicht auskennen mask und Farbverläufe, empfehle ich dringend, dies zu überprüfen dieser vereinfachte Fall um die Technik besser zu verstehen, bevor Sie zum nächsten Teil übergehen.

Zunächst müssen wir bestimmte Selektoren verwenden, um auf jede Gruppe von Elementen abzuzielen, die dieselbe Form haben. Wir haben neun Gruppen, also verwenden wir acht Selektoren plus einen Standardselektor, der alle auswählt.

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 */

Hier ist eine Abbildung, die zeigt, wie das unserem Gitter zugeordnet ist:

Wie ich ein reines CSS-Puzzlespiel erstellt habe PlatoBlockchain Data Intelligence. Vertikale Suche. Ai.
Wie ich ein reines CSS-Puzzlespiel gemacht habe

Kommen wir nun zu den Formen. Konzentrieren wir uns darauf, nur eine oder zwei der Formen zu lernen, da sie alle dieselbe Technik verwenden – und auf diese Weise haben Sie einige Hausaufgaben, die Sie weiter lernen müssen!

Für die Puzzleteile in der Mitte des Rasters 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);

Der Code mag komplex aussehen, aber konzentrieren wir uns auf jeweils einen Gradienten, um zu sehen, was passiert:

Zwei Farbverläufe erzeugen zwei Kreise (in der Demo grün und violett markiert), und zwei weitere Farbverläufe erzeugen die Schlitze, mit denen andere Teile verbunden sind (der blau markierte füllt den größten Teil der Form aus, während der rot markierte den oberen Teil ausfüllt). Eine CSS-Variable, --r, legt den Radius der Kreisformen fest.

Wie ich ein reines CSS-Puzzlespiel erstellt habe PlatoBlockchain Data Intelligence. Vertikale Suche. Ai.
Wie ich ein reines CSS-Puzzlespiel gemacht habe

Die Form der Puzzleteile in der Mitte (markiert 0 in der Abbildung) ist am schwierigsten herzustellen, da es vier Farbverläufe verwendet und vier Krümmungen hat. Alle anderen Teile jonglieren mit weniger Steigungen.

Zum Beispiel die Puzzleteile am oberen Rand des Puzzles (gekennzeichnet 2 in der Abbildung) verwendet drei Farbverläufe statt vier:

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);

Wir haben den ersten (oberen) Farbverlauf entfernt und die Werte des zweiten Farbverlaufs so angepasst, dass er den verbleibenden Raum abdeckt. Sie werden keinen großen Unterschied im Code feststellen, wenn Sie die beiden Beispiele vergleichen. Es sollte beachtet werden, dass wir verschiedene Hintergrundkonfigurationen finden können, um dieselbe Form zu erzeugen. Wenn Sie anfangen, mit Farbverläufen zu spielen, werden Sie sicher etwas anderes finden als das, was ich getan habe. Du kannst sogar etwas Prägnanteres schreiben – wenn ja, teile es in den Kommentaren!

Zusätzlich zum Erstellen der Formen werden Sie auch feststellen, dass ich die Breite und/oder Höhe der Elemente wie unten vergrößere:

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

Die Puzzleteile müssen ihre Gitterzelle überlaufen lassen, um sich zu verbinden.

Wie ich ein reines CSS-Puzzlespiel erstellt habe PlatoBlockchain Data Intelligence. Vertikale Suche. Ai.
Wie ich ein reines CSS-Puzzlespiel gemacht habe

Letzte Demo

Hier ist noch einmal die vollständige Demo. Wenn Sie es mit der ersten Version vergleichen, sehen Sie dieselbe Codestruktur zum Erstellen des Rasters und der Drag-and-Drop-Funktion sowie den Code zum Erstellen der Formen.

Mögliche Erweiterungen

Der Artikel endet hier, aber wir könnten unser Puzzle mit noch mehr Funktionen erweitern! Wie wäre es mit einem Timer? Oder vielleicht eine Art Glückwunsch, wenn der Spieler das Puzzle beendet hat?

Ich werde all diese Funktionen in einer zukünftigen Version berücksichtigen, also Behalten Sie mein GitHub-Repo im Auge.

Wrapping up

Und CSS ist keine Programmiersprache, Sie sagen. Ha!

Ich versuche damit kein #HotDrama zu entfachen. Ich sage es, weil wir ein paar wirklich knifflige logische Sachen gemacht und dabei viele CSS-Eigenschaften und -Techniken behandelt haben. Wir haben mit CSS Grid, Übergängen, Maskierung, Farbverläufen, Selektoren und Hintergrundeigenschaften gespielt. Ganz zu schweigen von den wenigen Sass-Tricks, die wir verwendet haben, um unseren Code einfach anzupassen.

Das Ziel war nicht, das Spiel zu bauen, sondern CSS zu erkunden und neue Eigenschaften und Tricks zu entdecken, die Sie in anderen Projekten verwenden können. Das Erstellen eines Online-Spiels in CSS ist eine Herausforderung, die Sie dazu bringt, CSS-Funktionen im Detail zu erkunden und zu lernen, wie man sie verwendet. Außerdem macht es einfach viel Spaß, dass wir am Ende etwas zum Spielen bekommen.

Ob CSS eine Programmiersprache ist oder nicht, ändert nichts an der Tatsache, dass wir immer lernen, indem wir innovative Dinge bauen und erschaffen.

Zeitstempel:

Mehr von CSS-Tricks