Hogyan készítettem egy tiszta CSS kirakós játékot, PlatoBlockchain Data Intelligence. Függőleges keresés. Ai.

Hogyan készítettem egy tiszta CSS kirakós játékot

Nemrég fedeztem fel a csak CSS-játékok készítésének örömét. Mindig lenyűgöző, hogy a HTML és a CSS mennyire képes kezelni egy egész online játék logikáját, ezért ki kellett próbálnom! Az ilyen játékok általában a régi Checkbox Hack-re támaszkodnak, ahol a HTML bemenet bejelölt/ellenőrzött állapotát kombináljuk a :checked pszeudoosztály a CSS-ben. Ezzel az egyetlen kombinációval rengeteg varázslatot művelhetünk!

Valójában kihívtam magam, hogy egy egész játékot építsek Checkbox nélkül. Nem voltam benne biztos, hogy lehetséges-e, de határozottan az, és megmutatom, hogyan.

Amellett, hogy a kirakós játékot fogunk tanulmányozni ebben a cikkben, én készítettem tiszta CSS játékok gyűjteménye, legtöbbjük a Checkbox Hack nélkül. (Ezek is kaphatók a CodePen-en.)

Akarsz játszani, mielőtt elkezdenénk?

Én személy szerint jobban szeretem a játékot teljes képernyős módban játszani, de lent, ill nyisd ki itt.

Menő ugye? Tudom, hogy nem ez a legjobb kirakós játék, amit valaha látott™, de egyáltalán nem rossz valamihez, ami csak CSS-t és néhány soros HTML-t használ. Könnyedén beállíthatja a rács méretét, módosíthatja a cellák számát a nehézségi szint szabályozásához, és bármilyen képet használhat!

Együtt újrakészítjük ezt a demót, majd a végére egy kis extra csillogást teszünk bele néhány rúgás kedvéért.

A drag and drop funkció

Míg a rejtvény felépítése meglehetősen egyszerű a CSS Grid segítségével, a puzzle-darabok drag and drop funkciója kissé bonyolultabb. Átmenetek, lebegő effektusok és testvérválasztók kombinációjára kellett hagyatkoznom, hogy elvégezzem.

Ha a demóban az üres mezőre viszi az egérmutatót, a kép benne mozog, és akkor is ott marad, ha kimozdítja a kurzort a dobozból. A trükk az, hogy nagy átmeneti időtartamot és késleltetést adjunk hozzá – akkora, hogy a képnek sok időbe telik, amíg visszaáll a kiindulási helyzetébe.

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

Csak a transition-delay elég, de a késleltetés és az időtartam nagy értékeinek használata csökkenti annak esélyét, hogy a játékos valaha is látja, hogy a kép visszamozdul. Ha vársz 999s + 999s — ami körülbelül 30 perc — akkor látni fogja a kép mozgását. De nem fogod, igaz? Úgy értem, senkinek sem kell ilyen hosszú ideig tartania a kanyarokat, hacsak nem hagyja el a játékot. Szóval ezt jó trükknek tartom a két állapot közötti váltáshoz.

Észrevetted, hogy a kép lebegtetése is kiváltja a változásokat? Ez azért van, mert a kép a doboz elem része, ami nekünk nem jó. Ezt kiegészítéssel javíthatjuk pointer-events: none a képre, de később nem tudjuk áthúzni.

Ez azt jelenti, hogy egy másik elemet kell bevezetnünk a .box:

Az az extra div (egy osztályt használunk .a) ugyanazt a területet foglalja el, mint a kép (hála a CSS Gridnek és grid-area: 1 / 1), és ez lesz az az elem, amely kiváltja a lebegési effektust. És itt jön képbe a testvérválasztó:

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

Lebeg a .a elem mozgatja a képet, és mivel a dobozon belül minden helyet elfoglal, olyan, mintha a doboz fölött tartanánk az egérmutatót! A kép lebegtetése már nem probléma!

Húzzuk a képünket a dobozba, és nézzük meg az eredményt:

Láttad azt? Először megragadja a képet, és áthelyezi a dobozba, semmi különös. De ha egyszer elengedi a képet, akkor aktiválja a lebegtetési effektust, amely mozgatja a képet, majd szimulálunk egy fogd és vidd funkciót. Ha elengedi az egeret a dobozon kívül, semmi sem történik.

Hmm, a szimulációd nem tökéletes, mert mi is lebegtethetjük a dobozt, és ugyanazt a hatást érhetjük el.

Ez igaz, és ezt orvosolni fogjuk. Ki kell kapcsolnunk a lebegés effektust, és csak akkor engedjük meg, ha kiengedjük a képet a dobozon belül. Játszunk majd a miénk dimenziójával .a elemet, hogy ez megtörténjen.

Most a doboz lebegtetése nem tesz semmit. De ha elkezdi húzni a képet, a .a elem jelenik meg, és miután kiengedjük a dobozon belül, elindíthatjuk a lebegés effektust és mozgathatjuk a képet.

Boncoljuk a kódot:

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

A képre kattintva beindul a :active pszeudoosztály, amely a .a elem teljes szélességű (kezdetben egyenlő 0). Az aktív állapot megmarad aktív amíg el nem engedjük a képet. Ha kiengedjük a képet a dobozon belül, a .a elemre tér vissza width: 0, de kiváltjuk a lebegő effektust, mielőtt ez megtörténne, és a kép a dobozba esik! Ha kiengedi a dobozon kívül, nem történik semmi.

Van egy kis furcsaság: az üres mezőre kattintva a kép is elmozdul, és megszakad a funkciónk. Jelenleg :active kapcsolódik a .box elemet, így ha rá vagy valamelyik gyermekére kattint, az aktiválódik; és ezzel a végén megmutatjuk a .a elemet, és kiváltja a lebegő effektust.

Ezt úgy tudjuk megoldani, ha játszunk vele pointer-events. Lehetővé teszi számunkra, hogy letiltsunk minden interakciót a .box miközben fenntartja a gyermekelemekkel való interakciókat.

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

Most a drag and drop funkciónk tökéletes. Hacsak nem találja meg, hogyan lehet feltörni, csak úgy mozgathatja a képet, ha húzza és ejtse a dobozba.

A puzzle rács építése

A puzzle összerakása egyszerű lesz ahhoz képest, amit a drag and drop funkciónál tettünk. A rejtvény elkészítéséhez CSS-rácsra és háttértrükkökre fogunk támaszkodni.

Íme a rácsunk, mopsz nyelven írva a kényelem kedvéért:

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

A kód furcsán nézhet ki, de egyszerű HTML-be fordítja le:

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

Fogadok, hogy kíváncsi, mi van ezekkel a címkékkel. Ezeknek az elemeknek nincs különösebb jelentése – csak azt tapasztalom, hogy a kódot sokkal könnyebb megírni <z> mint egy csomó <div class="z"> vagy mindegy.

Így térképeztem fel őket:

  • <g> a mi rácstartályunk, amely tartalmazza N*N <z> elemek.
  • <z> rácselemeinket képviseli. Szerepét játssza a .box elemet láttunk az előző részben.
  • <a> kiváltja a lebegés effektust.
  • <b> képünk egy részét képviseli. Alkalmazzuk a draggable attribútumot, mert alapértelmezés szerint nem húzható.

Rendben, regisztráljuk a rácstárolónkat <g>. Ez a Sass nyelven van CSS helyett:

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

Valójában a rácsunk gyermekeit fogjuk csinálni – a <z> elemeket – rácsokat is, és mindkettőt <a> és a <b> ugyanazon a rácsterületen belül:

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

Amint látja, semmi különös – egy meghatározott méretű rácsot hoztunk létre. A többi CSS-re a drag and drop funkcióhoz van szükségünk, ami megköveteli, hogy véletlenszerűen helyezzük el a darabokat a tábla körül. Ehhez ismét Sasshoz fogok fordulni, még egyszer a kényelem kedvéért, hogy az összes puzzle-darabot egy funkcióval átnézhessem és stílusozhassam:

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

Talán észrevetted, hogy a Sasst használom random() funkció. Így kapjuk meg a puzzle-darabok véletlenszerű pozícióit. Ne feledje, hogy fogunk letiltása abban a helyzetben, amikor az egérmutatót a <a> elemet a megfelelő húzás után <b> elem a rácscellán belül.

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

Ugyanebben a ciklusban a háttérkonfigurációt is meghatározom a puzzle minden egyes darabjához. Logikailag mindegyik ugyanazt a képet fogja megosztani, mint a háttér, és méretének meg kell egyeznie a teljes rács méretével (a --s változó). Ugyanazt használva background-image és némi matek, frissítjük a background-position hogy a képnek csak egy darabja jelenjen meg.

Ez az! A csak CSS-t használó kirakós játékunk technikailag elkészült!

De mindig tudunk jobbat csinálni, igaz? megmutattam hogyan készítsünk egy rácsot a kirakós darab formákból egy másik cikkben. Vegyük ugyanezt az ötletet, és alkalmazzuk itt is, jó?

Puzzle darab formák

Íme az új kirakós játékunk. Ugyanaz a funkcionalitás, de valósághűbb formákkal!

Ez a rácson lévő alakzatok illusztrációja:

Hogyan készítettem egy tiszta CSS kirakós játékot

Ha alaposan megnézed, észre fogod venni, hogy kilenc különböző kirakós formánk van: a négy sarok, a négy élés egy minden máshoz.

A kirakós darabokból álló rács, amelyet a másik hivatkozott cikkben készítettem, egy kicsit egyszerűbb:

Ugyanazt a technikát használhatjuk, amely a CSS-maszkokat és a színátmeneteket kombinálja a különböző alakzatok létrehozásához. Abban az esetben, ha nem ismeri mask és színátmenetek, erősen ajánlom az ellenőrzést az az egyszerűsített eset hogy jobban megértsük a technikát, mielőtt a következő részre lépnénk.

Először is speciális szelektorokat kell használnunk, hogy megcélozzuk az azonos alakú elemcsoportokat. Kilenc csoportunk van, tehát nyolc választót fogunk használni, plusz egy alapértelmezett választót, amely mindegyiket kiválasztja.

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

Íme egy ábra, amely bemutatja, hogy ez hogyan illeszkedik a rácsunkhoz:

Hogyan készítettem egy tiszta CSS kirakós játékot, PlatoBlockchain Data Intelligence. Függőleges keresés. Ai.
Hogyan készítettem egy tiszta CSS kirakós játékot

Most foglalkozzunk a formákkal. Koncentráljunk arra, hogy csak egy vagy kettő alakzatot tanuljunk meg, mert mindegyik ugyanazt a technikát használja – és így van egy kis házi feladatod a tanuláshoz!

A rács közepén lévő puzzle-darabokhoz 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);

A kód bonyolultnak tűnhet, de összpontosítsunk egyszerre egy színátmenetre, hogy lássuk, mi történik:

Két színátmenet két kört hoz létre (a bemutatóban zölddel és lilával jelölve), két másik színátmenet pedig azokat a nyílásokat, amelyekhez a többi darab csatlakozik (a kékkel jelölt az alakzat nagy részét, míg a pirossal jelölt a felső részt). CSS-változó, --r, beállítja a kör alakzatok sugarát.

Hogyan készítettem egy tiszta CSS kirakós játékot, PlatoBlockchain Data Intelligence. Függőleges keresés. Ai.
Hogyan készítettem egy tiszta CSS kirakós játékot

A puzzle darabjainak formája a közepén (jelölve 0 az ábrán) a legnehezebb elkészíteni, mivel négy színátmenetet használ, és négy görbülete van. Az összes többi darab kevesebb színátmenettel zsonglőrködik.

Például a puzzle darabjai a puzzle felső széle mentén (jelölve 2 az ábrán) három színátmenetet használ négy helyett:

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

Az első (felső) színátmenetet eltávolítottuk, a második színátmenet értékeit pedig úgy módosítottuk, hogy az lefedje a hátrahagyott teret. Nem fog nagy különbséget észrevenni a kódban, ha összehasonlítja a két példát. Megjegyzendő, hogy különböző háttérkonfigurációkat találhatunk ugyanazon alakzat létrehozásához. Ha elkezdesz játszani a színátmenetekkel, biztosan valami mást fogsz kitalálni, mint amit én csináltam. Akár tömörebbet is írhatsz – ha igen, oszd meg kommentben!

Az alakzatok létrehozása mellett azt is tapasztalhatja, hogy az alábbiak szerint növelem az elemek szélességét és/vagy magasságát:

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

A kirakós daraboknak túl kell csordulniuk a rácscellánkon, hogy összekapcsolódjanak.

Hogyan készítettem egy tiszta CSS kirakós játékot, PlatoBlockchain Data Intelligence. Függőleges keresés. Ai.
Hogyan készítettem egy tiszta CSS kirakós játékot

Végső demó

Ismét itt a teljes demó. Ha összehasonlítja az első verzióval, ugyanazt a kódstruktúrát fogja látni a rács létrehozásához és a fogd és vidd funkcióhoz, valamint az alakzatok létrehozásához szükséges kódot.

Lehetséges fejlesztések

A cikk itt ér véget, de még több funkcióval bővíthetnénk rejtvényünket! Mit szólnál az időzítőhöz? Vagy esetleg valamiféle gratuláció, amikor a játékos befejezi a rejtvényt?

Lehetséges, hogy ezeket a funkciókat egy jövőbeli verzióban figyelembe veszem, így tartsa szemmel a GitHub-repót.

Csomagolta

És A CSS nem programozási nyelv, azt mondják. Ha!

Nem próbálok ezzel valami #HotDrama-t kelteni. Azért mondom, mert nagyon trükkös logikai dolgokat csináltunk, és sok CSS tulajdonságot és technikát lefedtünk az út során. Játszottunk a CSS Griddel, az átmenetekkel, a maszkolással, a színátmenetekkel, a kiválasztókkal és a háttértulajdonságokkal. Nem is beszélve arról a néhány Sass-trükkről, amellyel a kódunkat könnyen beállíthattuk.

A cél nem a játék felépítése volt, hanem a CSS felfedezése, és olyan új tulajdonságok és trükkök felfedezése, amelyeket más projektekben is felhasználhat. Online játék létrehozása CSS-ben olyan kihívás, amely arra készteti, hogy alaposan fedezze fel a CSS-funkciókat, és tanulja meg használatukat. Ráadásul nagyon mulatságos, hogy ha mindennel elhangzik, van mit játszani.

Az, hogy a CSS programozási nyelv-e vagy sem, nem változtat azon a tényen, hogy mindig innovatív dolgok építésével és létrehozásával tanulunk.

Időbélyeg:

Még több CSS trükkök