Zephyrnet Logosu

Saf CSS Yapboz Oyunu Nasıl Yaptım

Tarih:

Kısa süre önce yalnızca CSS oyunları yaratmanın keyfini keşfettim. HTML ve CSS'nin tüm bir çevrimiçi oyunun mantığını nasıl idare edebileceği her zaman büyüleyicidir, bu yüzden denemek zorunda kaldım! Bu tür oyunlar genellikle, bir HTML girişinin işaretli/işaretsiz durumunu, :checked CSS'de sözde sınıf. Bu tek kombinasyonla çok fazla sihir yapabiliriz!

Aslında, Checkbox olmadan bütün bir oyun oluşturmak için kendime meydan okudum. Bunun mümkün olup olmayacağından emin değildim ama kesinlikle öyle ve size nasıl olduğunu göstereceğim.

Bu yazıda inceleyeceğimiz puzzle oyununa ek olarak yapmış olduğum saf CSS oyunları koleksiyonu, çoğu Onay Kutusu Hack olmadan. (Onlar da mevcuttur CodePen'de.)

Başlamadan önce oynamak ister misin?

Ben şahsen oyunu tam ekran modunda oynamayı tercih ediyorum ama aşağıdan oynayabilirsiniz veya burayı aç.

Güzel değil mi? Biliyorum, Gördüğün En İyi Bulmaca Oyunu™ değil ama aynı zamanda sadece CSS ve birkaç satır HTML kullanan bir şey için hiç de fena değil. Izgaranın boyutunu kolayca ayarlayabilir, zorluk seviyesini kontrol etmek için hücre sayısını değiştirebilir ve istediğiniz görüntüyü kullanabilirsiniz!

Bu demoyu birlikte yeniden yapacağız, sonra biraz daha ışıltı katacağız.

Sürükle ve bırak işlevi

Yapbozun yapısı CSS Izgarası ile oldukça basit olsa da, yapboz parçalarını sürükleyip bırakma yeteneği biraz daha zordur. Bunu yapmak için geçişler, fareyle üzerine gelme efektleri ve kardeş seçicilerin bir kombinasyonuna güvenmek zorunda kaldım.

Bu demodaki boş kutunun üzerine geldiğinizde, imleci kutunun dışına çıkarsanız bile görüntü kutunun içinde hareket eder ve orada kalır. İşin püf noktası, büyük bir geçiş süresi ve gecikme eklemektir - o kadar büyüktür ki, görüntünün ilk konumuna dönmesi çok zaman alır.

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

Yalnızca belirterek transition-delay yeterlidir, ancak hem gecikme hem de süre üzerinde büyük değerler kullanmak, bir oyuncunun görüntünün geri hareket ettiğini görme şansını azaltır. eğer beklersen 999s + 999s - yaklaşık 30 dakika - sonra görüntünün hareket ettiğini göreceksiniz. Ama yapmayacaksın, değil mi? Demek istediğim, oyundan uzaklaşmadıkça kimse dönüşler arasında o kadar uzun sürmeyecek. Bu yüzden, bunu iki durum arasında geçiş yapmak için iyi bir numara olarak görüyorum.

Resmin üzerine gelmenin de değişiklikleri tetiklediğini fark ettiniz mi? Bunun nedeni, görüntünün bizim için iyi olmayan kutu öğesinin bir parçası olmasıdır. Bunu ekleyerek düzeltebiliriz pointer-events: none görüntüye ancak daha sonra sürükleyemeyeceğiz.

Bu, içine başka bir öğe eklememiz gerektiği anlamına gelir. .box:

bu ekstra div (bir sınıf kullanıyoruz .a) görüntü ile aynı alanı alacaktır (CSS Grid ve grid-area: 1 / 1) ve vurgulu efekti tetikleyen öğe olacaktır. Kardeş seçicinin devreye girdiği yer burasıdır:

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

üzerinde gezinen .a eleman görüntüyü hareket ettirir ve kutunun içindeki tüm alanı kapladığı için, kutunun üzerinde geziniyor gibiyiz! Görüntüyü gezdirmek artık sorun değil!

Resmimizi kutunun içine sürükleyip bırakalım ve sonucu görelim:

Şunu gördün mü? Önce görüntüyü alıp kutuya taşırsınız, hiçbir şey fantezi değildir. Ancak görüntüyü bıraktığınızda, görüntüyü hareket ettiren vurgulu efekti tetiklersiniz ve ardından bir sürükle ve bırak özelliğini simüle ederiz. Fareyi kutunun dışına bırakırsanız hiçbir şey olmaz.

Hmm, simülasyonunuz mükemmel değil çünkü biz de kutunun üzerine gelip aynı efekti elde edebiliriz.

Doğru ve bunu düzelteceğiz. Fareyle üzerine gelme efektini devre dışı bırakmamız ve yalnızca kutunun içindeki görüntüyü serbest bırakırsak izin vermemiz gerekiyor. boyutumuzla oynayacağız .a Bunu gerçekleştirecek unsur.

Şimdi, kutuyu gezdirmek hiçbir şey yapmıyor. Ancak görüntüyü sürüklemeye başlarsanız, .a öğesi görünür ve kutunun içinde bırakıldığında, fareyle üzerine gelme efektini tetikleyebilir ve görüntüyü hareket ettirebiliriz.

Kodu inceleyelim:

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

Resmin üzerine tıklamak ateşliyor :active yapan sözde sınıf .a eleman tam genişlikte (başlangıçta 0). Aktif durum kalacak aktif resmi yayınlayana kadar. Kutunun içindeki görüntüyü serbest bırakırsak, .a eleman geri döner width: 0, ancak vurgulu efekti gerçekleşmeden önce tetikleyeceğiz ve görüntü kutunun içine düşecek! Kutunun dışında bırakırsanız hiçbir şey olmaz.

Küçük bir tuhaflık var: boş kutuya tıklamak da görüntüyü hareket ettiriyor ve özelliğimizi bozuyor. Şu anda, :active ile bağlantılı .box öğe, bu nedenle üzerine veya alt öğelerinden herhangi birine tıklamak onu etkinleştirir; ve bunu yaparak, sonunda gösteriyoruz .a öğe ve vurgulu efekti tetikleme.

ile oynayarak düzeltebiliriz. pointer-events. ile herhangi bir etkileşimi devre dışı bırakmamıza izin verir. .box alt öğelerle etkileşimleri sürdürürken.

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

şimdi sürükle ve bırak özelliğimiz mükemmel. Nasıl kırılacağını bulamazsan, görüntüyü taşımanın tek yolu onu sürükleyip kutunun içine bırakmaktır.

Bulmaca ızgarasını oluşturma

Sürükle ve bırak özelliği için yaptığımız şeyle karşılaştırıldığında, bulmacayı bir araya getirmek çok kolay olacak. Bulmacayı oluşturmak için CSS ızgarasına ve arka plan püf noktalarına güveneceğiz.

İşte size kolaylık olması için Pug ile yazılmış ızgaramız:

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

Kod garip görünebilir ancak düz HTML olarak derlenir:


 
   
   
 
 
   
   
 
 
   
   
 
  

Bahse girerim bu etiketlere ne olduğunu merak ediyorsundur. Bu öğelerin hiçbirinin özel bir anlamı yok - sadece kodu kullanarak yazmanın çok daha kolay olduğunu görüyorum. bir demetten daha

ya da her neyse.

Bunları şu şekilde haritalandırdım:

  • içeren ızgara kapsayıcımızdır N*N elemanları.
  • ızgara öğelerimizi temsil eder. Şu rolü oynar: .box önceki bölümde gördüğümüz eleman.
  • vurgulu efekti tetikler.
  • imajımızın bir bölümünü temsil eder. biz uygularız draggable özniteliği, varsayılan olarak sürüklenemediği için üzerindedir.

Pekala, grid kapsayıcımızı şuraya kaydedelim: . Bu, CSS yerine Sass'ta:

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

Aslında şebeke çocuklarımızı yapacağız - elemanlar - ızgaralar da ve her ikisine de sahip ve aynı ızgara alanı içinde:

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

Gördüğünüz gibi, hiçbir şey fantezi değil - belirli bir boyutta bir ızgara oluşturduk. İhtiyacımız olan CSS'nin geri kalanı, parçaları tahtanın etrafına rastgele yerleştirmemizi gerektiren sürükle ve bırak özelliği içindir. Bunun için yine Sass'a döneceğim, yine tüm yapboz parçalarını bir işlevle döngüye sokabilmenin ve stil verebilmenin rahatlığı için:

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

Sass kullandığımı fark etmiş olabilirsiniz. random() işlev. Yapboz parçaları için rastgele konumları bu şekilde elde ederiz. Unutmayın ki Enable / Disable üzerine gelindiğinde bu konum ilgili öğeyi sürükleyip bıraktıktan sonra öğe ızgara hücresinin içindeki eleman.

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

Aynı döngüde, bulmacanın her parçası için arka plan konfigürasyonunu da tanımlıyorum. Hepsi mantıksal olarak arka planla aynı görüntüyü paylaşacaktır ve boyutu tüm ızgaranın boyutuna eşit olmalıdır ( --s değişken). Aynısını kullanmak background-image ve biraz matematik, güncelliyoruz background-position görüntünün yalnızca bir parçasını göstermek için

Bu kadar! Yalnızca CSS bulmaca oyunumuz teknik olarak tamamlandı!

Ama her zaman daha iyisini yapabiliriz, değil mi? ben sana gösterdim yapboz parçası şekillerinden oluşan bir ızgara nasıl yapılır başka bir makalede. Aynı fikri alıp buraya uygulayalım, olur mu?

Bulmaca parçası şekilleri

İşte yeni bulmaca oyunumuz. Aynı işlevsellik, ancak daha gerçekçi şekillerle!

Bu, ızgaradaki şekillerin bir gösterimidir:

Yakından bakarsanız, dokuz farklı yapboz parçası şeklimiz olduğunu fark edeceksiniz: dört köşe, dört kenar, ve diğer her şey için bir.

Bahsettiğim diğer makalede yaptığım yapboz parçaları ızgarası biraz daha basit:

Farklı şekiller oluşturmak için CSS maskelerini ve degradeleri birleştiren aynı tekniği kullanabiliriz. aşina değilseniz mask ve gradyanlar, kontrol etmenizi şiddetle tavsiye ederim bu basitleştirilmiş durum Bir sonraki bölüme geçmeden önce tekniği daha iyi anlamak için.

İlk olarak, aynı şekli paylaşan her bir öğe grubunu hedeflemek için belirli seçiciler kullanmamız gerekir. Dokuz grubumuz var, bu yüzden sekiz seçici ve hepsini seçen varsayılan bir seçici kullanacağız.

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

Bunun ızgaramıza nasıl eşlendiğini gösteren bir şekil:

Şimdi şekilleri ele alalım. Şekillerden sadece bir veya ikisini öğrenmeye odaklanalım çünkü hepsi aynı tekniği kullanıyor - ve bu şekilde, öğrenmeye devam etmek için bazı ödevleriniz var!

Izgaranın ortasındaki yapboz parçaları için, 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);

Kod karmaşık görünebilir, ancak neler olduğunu görmek için her seferinde bir degradeye odaklanalım:

İki degrade, iki daire oluşturur (demoda yeşil ve mor olarak işaretlenmiştir) ve diğer iki degrade, diğer parçaların bağlandığı yuvaları oluşturur (mavi ile işaretlenmiş olan şeklin çoğunu doldururken kırmızı ile işaretlenmiş olan üst kısmı doldurur). Bir CSS değişkeni, --r, dairesel şekillerin yarıçapını ayarlar.

Merkezdeki yapboz parçalarının şekli (işaretli 0 çizimde), dört gradyan kullandığı ve dört eğriliği olduğu için yapılması en zor olanıdır. Diğer tüm parçalar daha az degradeyi dengeler.

Örneğin, yapbozun üst kenarındaki yapboz parçaları (işaretli 2 çizimde) dört yerine üç degrade kullanır:

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

İlk (üst) degradeyi kaldırdık ve ikinci degradenin değerlerini, geride kalan alanı kaplayacak şekilde ayarladık. İki örneği karşılaştırırsanız, kodda büyük bir fark görmezsiniz. Aynı şekli oluşturmak için farklı arka plan konfigürasyonları bulabileceğimize dikkat edilmelidir. Degradelerle oynamaya başlarsanız, kesinlikle benim yaptığımdan farklı bir şey bulacaksınız. Daha özlü bir şey bile yazabilirsiniz - öyleyse, yorumlarda paylaşın!

Şekilleri oluşturmaya ek olarak, aşağıdaki gibi öğelerin genişliğini ve/veya yüksekliğini artırdığımı da göreceksiniz:

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

Bulmacanın parçalarının bağlanabilmesi için ızgara hücrelerinden taşması gerekir.

Son demo

İşte yine tam demo. İlk sürümle karşılaştırırsanız, ızgarayı oluşturmak için aynı kod yapısını ve sürükle ve bırak özelliğini ve ayrıca şekilleri oluşturmak için kodu göreceksiniz.

Olası geliştirmeler

Makale burada sona eriyor, ancak bulmacamızı daha da fazla özellikle geliştirmeye devam edebiliriz! aa zamanlayıcıya ne dersin? Ya da oyuncu bulmacayı bitirdiğinde bir tür tebrikler olabilir mi?

Tüm bu özellikleri gelecekteki bir sürümde düşünebilirim, bu yüzden GitHub depoma göz kulak ol.

Tamamlayan

Ve CSS bir programlama dili değildir, onlar söylüyor. Ha!

Bununla biraz #HotDrama kıvılcım çıkarmaya çalışmıyorum. Bunu söylüyorum çünkü gerçekten zor mantık işleri yaptık ve yol boyunca birçok CSS özelliği ve tekniğini ele aldık. CSS Izgarası, geçişler, maskeleme, degradeler, seçiciler ve arka plan özellikleriyle oynadık. Kodumuzu ayarlamayı kolaylaştırmak için kullandığımız birkaç Sass hilesinden bahsetmiyorum bile.

Amaç oyunu oluşturmak değil, CSS'yi keşfetmek ve diğer projelerde kullanabileceğiniz yeni özellikler ve püf noktaları keşfetmekti. CSS'de çevrimiçi bir oyun oluşturmak, sizi CSS özelliklerini ayrıntılı olarak keşfetmeye ve bunları nasıl kullanacağınızı öğrenmeye iten bir meydan okumadır. Ayrıca, her şey söylenip yapıldığında oynayacak bir şeyler bulmamız çok eğlenceli.

CSS'nin bir programlama dili olup olmaması, her zaman yenilikçi şeyler inşa ederek ve yaratarak öğrendiğimiz gerçeğini değiştirmez.

spot_img

En Son İstihbarat

spot_img

Bizimle sohbet

Merhaba! Size nasıl yardım edebilirim?