3D oyun menü bileşeni oluşturma

Duyarlı, uyarlanabilir ve erişilebilir bir 3D oyun menüsünün nasıl oluşturulacağına dair temel bilgiler.

Bu yayında, 3D oyun menüsü bileşeni oluşturmanın bir yolu üzerine düşünmek istiyorum. Demoyu deneyin.

Tanıtım

Videoyu tercih ediyorsanız bu yayının YouTube sürümünü burada bulabilirsiniz:

Genel bakış

Video oyunları genellikle kullanıcılara animasyonlu ve 3D uzayda yaratıcı ve alışılmadık bir menü sunar. Yeni AR/VR oyunlarında menünün uzayda kayıyormuş gibi görünmesi için popülerdir. Bugün bu efektin temel unsurlarını yeniden oluşturacağız, ancak bu efektlere uyum sağlayan renk şeması ve daha az hareket etmeyi tercih eden kullanıcılar için konaklama olanağı daha eklenecek.

HTML

Oyun menüsü düğmelerden oluşan bir listedir. Bunu HTML'de göstermenin en iyi yolu şu şekildedir:

<ul class="threeD-button-set">
  <li><button>New Game</button></li>
  <li><button>Continue</button></li>
  <li><button>Online</button></li>
  <li><button>Settings</button></li>
  <li><button>Quit</button></li>
</ul>

Bir düğme listesi, kendisini ekran okuyucu teknolojilerinde iyi bir şekilde duyurur ve JavaScript veya CSS olmadan çalışır.

öğe olarak normal düğmelere sahip,
çok genel görünümlü bir madde işareti listesi.

CSS

Düğme listesinin stil özelliklerini ayarlama, aşağıdaki üst düzey adımlara bölünür:

  1. Özel mülk oluşturma.
  2. Flexbox düzeni.
  3. Dekoratif sözde öğeler içeren özel bir düğme.
  4. Öğeleri 3D alana yerleştirme.

Özel özelliklere genel bakış

Özel özellikler, aksi halde rastgele görünen değerlere anlamlı adlar vererek, tekrarlanan kodlardan ve değerler arasında değer paylaşmaktan kaçınarak değerlerin anlaşılmasına yardımcı olur.

Aşağıda, özel medya olarak da bilinen CSS değişkenleri olarak kaydedilmiş medya sorguları bulunmaktadır. Bunlar geneldir ve kodun kısa ve okunabilir olması için çeşitli seçicilerde kullanılır. Oyun menüsü bileşeni, ekranın hareket tercihlerini, sistem renk şemasını ve renk aralığı özelliklerini kullanır.

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);

Aşağıdaki özel mülkler renk şemasını yönetir ve oyun menüsünü etkileşimli hale getirmek için fare konum değerlerini tutar. Özel özellikleri adlandırmak, değerin kullanım alanını veya değerin sonucu için bir kolay adı ortaya koyduğu için kodun okunabilirliğine yardımcı olur.

.threeD-button-set {
  --y:;
  --x:;
  --distance: 1px;
  --theme: hsl(180 100% 50%);
  --theme-bg: hsl(180 100% 50% / 25%);
  --theme-bg-hover: hsl(180 100% 50% / 40%);
  --theme-text: white;
  --theme-shadow: hsl(180 100% 10% / 25%);

  --_max-rotateY: 10deg;
  --_max-rotateX: 15deg;
  --_btn-bg: var(--theme-bg);
  --_btn-bg-hover: var(--theme-bg-hover);
  --_btn-text: var(--theme-text);
  --_btn-text-shadow: var(--theme-shadow);
  --_bounce-ease: cubic-bezier(.5, 1.75, .75, 1.25);

  @media (--dark) {
    --theme: hsl(255 53% 50%);
    --theme-bg: hsl(255 53% 71% / 25%);
    --theme-bg-hover: hsl(255 53% 50% / 40%);
    --theme-shadow: hsl(255 53% 10% / 25%);
  }

  @media (--HDcolor) {
    @supports (color: color(display-p3 0 0 0)) {
      --theme: color(display-p3 .4 0 .9);
    }
  }
}

Açık ve koyu tema arka plan konik arka planlar

Açık temada canlı bir cyan ila deeppink konik gradyan bulunurken koyu temada koyu ve hafif bir konik gradyan vardır. Konik gradyanlarla neler yapılabileceğini görmek için conic.style konusuna bakın.

html {
  background: conic-gradient(at -10% 50%, deeppink, cyan);

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
Açık ve koyu renk tercihleri arasında geçiş yapan arka plan gösterimi.

3D perspektifi etkinleştirme

Öğelerin bir web sayfasının 3D alanında var olabilmesi için perspektifli bir görüntü alanının başlatılması gerekir. Perspektifi body öğesine koymayı seçtim ve beğendiğim stili oluşturmak için görüntü alanı birimlerini kullandım.

body {
  perspective: 40vw;
}

Bu, bakış açısının sahip olabileceği etki türüdür.

<ul> düğme listesinin stil özelliklerini ayarlama

Bu öğe, etkileşimli ve 3D kayan kart olmanın yanı sıra genel düğme listesi makro düzeninden de sorumludur. Bunu başarmanın bir yolunu burada bulabilirsiniz.

Düğme grubu düzeni

Flexbox, container düzenini yönetebilir. Varsayılan esneklik yönünü satırlardan flex-direction içeren sütunlara değiştirin ve align-items için stretch değerini start olarak değiştirerek her bir öğenin, içeriğinin boyutunda olduğundan emin olun.

.threeD-button-set {
  /* remove <ul> margins */
  margin: 0;

  /* vertical rag-right layout */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2.5vh;
}

Daha sonra, kapsayıcıyı 3D alan içeriği olarak sağlayın ve kartın okunabilir dönüşlerden fazla dönmemesini sağlamak için CSS clamp() işlevlerini ayarlayın. Zaman sınırlaması için orta değerin özel bir özellik olduğuna dikkat edin. Bu --x ve --y değerleri, daha sonra fare etkileşimi olduğunda JavaScript'ten ayarlanır.

.threeD-button-set {
  …

  /* create 3D space context */
  transform-style: preserve-3d;

  /* clamped menu rotation to not be too extreme */
  transform:
    rotateY(
      clamp(
        calc(var(--_max-rotateY) * -1),
        var(--y),
        var(--_max-rotateY)
      )
    )
    rotateX(
      clamp(
        calc(var(--_max-rotateX) * -1),
        var(--x),
        var(--_max-rotateX)
      )
    )
  ;
}

Daha sonra, ziyaret eden kullanıcının hareket etmesi sorun yaratmazsa tarayıcıya bu öğenin dönüşümünün will-change ile sürekli değişeceğine dair bir ipucu ekleyin. Ayrıca, dönüşümlerde bir transition ayarlayarak interpolasyonu etkinleştirin. Bu geçiş, fare ile kart etkileşime geçtiğinde meydana gelir ve dönüş değişikliklerine sorunsuz geçiş sağlar. Animasyon, bir fare bileşenle etkileşimde bulunamasa veya etkileşimde bulunmasa bile, kartın içinde bulunduğu 3D alanı gösteren sürekli çalışan bir animasyondur.

@media (--motionOK) {
  .threeD-button-set {
    /* browser hint so it can be prepared and optimized */
    will-change: transform;

    /* transition transform style changes and run an infinite animation */
    transition: transform .1s ease;
    animation: rotate-y 5s ease-in-out infinite;
  }
}

Tarayıcı, 0% ve 100% öğelerini öğenin varsayılan stiline varsayılan olarak ayarlayacağından rotate-y animasyonu, yalnızca ortadaki animasyon karesini 50% olarak ayarlar. Bu, aynı konumda başlayıp bitmesi gereken, değişen animasyonların kısaltmasıdır. Sonsuz alternatif animasyonları ifade etmenin harika bir yoludur.

@keyframes rotate-y {
  50% {
    transform: rotateY(15deg) rotateX(-6deg);
  }
}

<li> öğelerinin stilini belirleme

Her liste öğesi (<li>) düğmeyi ve onun kenarlık öğelerini içerir. display stili değiştirildiğinde öğe ::marker görünmeyecek. position stili, relative olarak ayarlanır. Böylece, yakında sunulacak düğme sözde öğeleri kendilerini düğmenin kapladığı tam alan içinde konumlandırabilir.

.threeD-button-set > li {
  /* change display type from list-item */
  display: inline-flex;

  /* create context for button pseudos */
  position: relative;

  /* create 3D space context */
  transform-style: preserve-3d;
}

Listenin ekran görüntüsü, perspektifi göstermek için 3D boşlukta döndürüldü ve artık her bir liste öğesinde madde işareti kalmayacak.

<button> öğelerinin stilini belirleme

Düğmelerin stilini belirlemek zor bir iş olabilir, hesaba katılması gereken pek çok durum ve etkileşim türü vardır. Bu düğmeler, yapay öğelerin, animasyonların ve etkileşimlerin dengelenmesi nedeniyle hızla karmaşık hale gelir.

İlk <button> stilleri

Diğer eyaletleri destekleyecek temel stilleri aşağıda bulabilirsiniz.

.threeD-button-set button {
  /* strip out default button styles */
  appearance: none;
  outline: none;
  border: none;

  /* bring in brand styles via props */
  background-color: var(--_btn-bg);
  color: var(--_btn-text);
  text-shadow: 0 1px 1px var(--_btn-text-shadow);

  /* large text rounded corner and padded*/
  font-size: 5vmin;
  font-family: Audiowide;
  padding-block: .75ch;
  padding-inline: 2ch;
  border-radius: 5px 20px;
}

Düğme listesinin 3D perspektifle, bu kez stil düğmeleriyle gösterilen ekran görüntüsü.

Düğme sözde öğeleri

Düğmenin kenarlıkları geleneksel kenarlıklar değil, kenarlıkları olan mutlak konumdur.

Chrome Geliştirici Araçları&#39;ndaki Öğeler panelinin ekran görüntüsünde ::before ve ::after öğelerine sahip bir düğme gösteriliyor.

Bu öğeler, oluşturulan 3D perspektifi göstermede hayati önem taşır. Bu sözde öğelerden biri düğmeden uzağa itilir ve bir tanesi kullanıcıya yaklaştırılır. Efektin en iyi farkı, üst ve alt düğmelerde göze çarpmaktadır.

.threeD-button button {
  …

  &::after,
  &::before {
    /* create empty element */
    content: '';
    opacity: .8;

    /* cover the parent (button) */
    position: absolute;
    inset: 0;

    /* style the element for border accents */
    border: 1px solid var(--theme);
    border-radius: 5px 20px;
  }

  /* exceptions for one of the pseudo elements */
  /* this will be pushed back (3x) and have a thicker border */
  &::before {
    border-width: 3px;

    /* in dark mode, it glows! */
    @media (--dark) {
      box-shadow:
        0 0 25px var(--theme),
        inset 0 0 25px var(--theme);
    }
  }
}

3D dönüştürme stilleri

transform-style öğesinin altı preserve-3d olarak ayarlanır. Bu sayede çocuklar z ekseninde kendilerini boşluk bırakabilir. transform, --distance özel özelliğine ayarlandı. Bu özellik, fareyle üzerine gelme ve odaklama ile artırılacak.

.threeD-button-set button {
  …

  transform: translateZ(var(--distance));
  transform-style: preserve-3d;

  &::after {
    /* pull forward in Z space with a 3x multiplier */
    transform: translateZ(calc(var(--distance) / 3));
  }

  &::before {
    /* push back in Z space with a 3x multiplier */
    transform: translateZ(calc(var(--distance) / 3 * -1));
  }
}

Koşullu animasyon stilleri

Kullanıcı hareket konusunda sorun değilse bu düğme, tarayıcıya dönüşüm özelliğinin değişime hazır olması gerektiğini ve transform ile background-color özellikleri için bir geçişin ayarlandığını bildirir. Süre farkına dikkat edince, hoş ve hoş bir aşamalı efekt sağladığını hissettim.

.threeD-button-set button {
  …

  @media (--motionOK) {
    will-change: transform;
    transition:
      transform .2s ease,
      background-color .5s ease
    ;

    &::before,
    &::after {
      transition: transform .1s ease-out;
    }

    &::after    { transition-duration: .5s }
    &::before { transition-duration: .3s }
  }
}

Üzerine gelme ve odaklama etkileşim stilleri

Etkileşim animasyonunun amacı, düz görünen düğmeyi oluşturan katmanları yaymaktır. Bu işlemi, --distance değişkenini başlangıçta 1px olacak şekilde ayarlayarak yapabilirsiniz. Aşağıdaki kod örneğinde gösterilen seçici, düğmenin üzerine gelme veya odaklanma durumu göstergesi olması gereken bir cihazın düğme üzerinde durup durmadığını ve etkin olup olmadığını kontrol eder. Bu durumda aşağıdakileri yapmak için CSS uygulanır:

  • Fareyle üzerine gelinen arka plan rengini uygulayın.
  • Mesafeyi artırın .
  • Hemen çıkma yumuşatma efekti ekleyin.
  • Sözde öğe geçişlerini uzatın.
.threeD-button-set button {
  …

  &:is(:hover, :focus-visible):not(:active) {
    /* subtle distance plus bg color change on hover/focus */
    --distance: 15px;
    background-color: var(--_btn-bg-hover);

    /* if motion is OK, setup transitions and increase distance */
    @media (--motionOK) {
      --distance: 3vmax;

      transition-timing-function: var(--_bounce-ease);
      transition-duration: .4s;

      &::after  { transition-duration: .5s }
      &::before { transition-duration: .3s }
    }
  }
}

3D perspektif, reduced hareket tercihi için yine de oldukça düzenliydi. Üstteki ve alttaki öğeler efekti hoş ve hoş bir şekilde gösteriyor.

JavaScript ile küçük geliştirmeler

Arayüz zaten klavyelerden, ekran okuyuculardan, oyun kumandalarından, dokunmadan ve fareden kullanılabiliyor, ancak birkaç senaryoyu kolaylaştırmak için JavaScript'e biraz ufak dokunuşlar ekleyebiliriz.

Ok tuşlarını destekleme

Sekme tuşu, menüde gezinmenin iyi bir yoludur, ancak yön tuşları veya kontrol çubuklarının odağı oyun kumandasına getirmesini beklerim. GUI Meydan Okuması arayüzleri için genellikle kullanılan roving-ux kitaplığı bizim için ok tuşlarını yönetir. Aşağıdaki kod, kütüphaneye odağı .threeD-button-set içinde yakalamasını ve odağı düğme alt öğelerine yönlendirmesini bildirir.

import {rovingIndex} from 'roving-ux'

rovingIndex({
  element: document.querySelector('.threeD-button-set'),
  target: 'button',
})

Fare paralaks etkileşimi

Fareyi takip etmek ve menüyü yatırmak, artırılmış gerçeklik (AR) ve sanal gerçeklik (VR) video oyunu arayüzlerini taklit edecek şekilde tasarlanmıştır. Bu arayüzde, fare yerine sanal bir işaretçiye sahip olabilirsiniz. Öğeler işaretçiden tamamen haberdar olduğunda eğlenceli olabilir.

Bu ekstra küçük bir özellik olduğundan, etkileşimi kullanıcının hareket tercihiyle ilgili bir sorgunun arkasına koyacağız. Ayrıca, kurulumun bir parçası olarak düğme listesi bileşenini querySelector ile belleğe depolayın ve öğenin sınırlarını menuRect olarak önbelleğe alın. Fare konumuna göre karta uygulanan döndürme ofsetini belirlemek için bu sınırları kullanın.

const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

Şimdi, fare x ve y konumlarını kabul eden ve kartı döndürmek için kullanabileceğimiz bir değer döndüren bir işleve ihtiyacımız var. Aşağıdaki işlev kutunun içinde ve ne kadar yer aldığını belirlemek için farenin konumunu kullanır. Delta, işlevden döndürülür.

const getAngles = (clientX, clientY) => {
  const { x, y, width, height } = menuRect

  const dx = clientX - (x + 0.5 * width)
  const dy = clientY - (y + 0.5 * height)

  return {dx,dy}
}

Son olarak, farenin hareketini izleyin, konumu getAngles() işlevimize geçirin ve delta değerlerini özel özellik stilleri olarak kullanın. Deltayı doldurmak ve titremeyi azaltmak için 20'ye böldüm, bunu yapmanın daha iyi bir yolu olabilir. Başından beri --x ve --y özelliklerini bir clamp() işlevinin ortasına yerleştirdiğimizi varsayalım. Bu, fare konumunun kartı aşırı döndürerek okunaksız bir konuma getirmesini önler.

if (motionOK) {
  window.addEventListener('mousemove', ({target, clientX, clientY}) => {
    const {dx,dy} = getAngles(clientX, clientY)

    menu.attributeStyleMap.set('--x', `${dy / 20}deg`)
    menu.attributeStyleMap.set('--y', `${dx / 20}deg`)
  })
}

Çeviriler ve yol tarifleri

Diğer yazma modlarında ve dillerde oyun menüsünü test ederken bir sorun oluştu.

<button> öğeleri, kullanıcı aracısı stil sayfasında writing-mode için !important stiline sahip. Bu, oyun menüsü HTML'sinin istenen tasarıma uyacak şekilde değiştirilmesi anlamına geliyordu. Düğme listesinin bağlantı listesine dönüştürülmesi, <a> öğelerinin tarayıcı tarafından sağlanan bir !important stiline sahip olmaması nedeniyle menü yönünü değiştirmek için mantıksal özelliklerin kullanılmasını sağlar.

Sonuç

Nasıl yapıldığını öğrendiniz. Peki, nasıl yapardınız? 🙂 Menüye ivme ölçer etkileşimi ekleyebilir misiniz? Böylece, telefonunuzu serpiştirdiğinizde menüyü döndürür müsünüz? Hareket yok deneyimini iyileştirebilir miyiz?

Yaklaşımlarımızı çeşitlendirelim ve web'de geliştirme yapmanın tüm yollarını öğrenelim. Bir demo oluşturun, bana tweet atın bağlantıları, aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.

Topluluk remiksleri

Henüz burada görülecek bir şey yok.