Membuat komponen menu game 3D

Ringkasan dasar tentang cara membangun menu game 3D yang responsif, adaptif, dan mudah diakses.

Dalam postingan ini saya ingin berbagi pemikiran tentang cara membangun komponen menu game 3D. Mulai demo.

Demo

Jika Anda lebih suka menonton video, berikut versi YouTube untuk postingan ini:

Ringkasan

{i>Video game<i} sering menampilkan menu yang kreatif dan tidak biasa, dengan animasi dan ruang 3D. Menu ini populer di game AR/VR baru karena menu tampak yang mengambang di angkasa. Hari ini kita akan mendesain ulang hal-hal penting dari efek ini tetapi dengan sentuhan tambahan dari skema warna adaptif dan akomodasi untuk pengguna yang lebih suka mengurangi gerakan.

HTML

Menu game merupakan daftar tombol. Cara terbaik untuk merepresentasikannya dalam HTML adalah dengan berikut ini:

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

Daftar tombol akan muncul dengan baik ke teknologi {i>screen reader<i} dan berfungsi tanpa JavaScript atau CSS.

suatu
daftar berbutir yang terlihat sangat umum 
dengan tombol biasa sebagai item.

CSS

Menata gaya daftar tombol dibagi menjadi langkah-langkah tingkat tinggi berikut:

  1. Menyiapkan properti kustom.
  2. Tata letak flexbox.
  3. Tombol kustom dengan elemen pseudo dekoratif.
  4. Menempatkan elemen ke dalam ruang 3D.

Ringkasan properti kustom

Properti kustom membantu membedakan nilai dengan memberikan nama menjadi nilai yang tampak acak, menghindari kode berulang dan berbagi di antara anak-anak.

Di bawah ini adalah kueri media yang disimpan sebagai variabel CSS, yang disebut juga kustom media. Model tersebut bersifat global dan akan digunakan di berbagai pemilih agar kode tetap ringkas dan dapat dibaca. Tujuan komponen menu game menggunakan gerakan preferensi, warna sistem skema, dan rentang warna dari tampilan.

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

Properti khusus berikut mengelola skema warna dan tahan mouse nilai posisi untuk membuat menu game interaktif saat kursor diarahkan ke atasnya. Penamaan kustom properti membantu keterbacaan kode karena mengungkap kasus penggunaan untuk nilai atau nama yang sesuai untuk hasil nilai.

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

Latar belakang kerucut latar belakang tema terang dan gelap

Tema terang memiliki kerucut cyan hingga deeppink yang cerah gradien sedangkan tema gelap memiliki gradien kerucut halus gelap. Untuk mengetahui lebih lanjut tentang dapat dilakukan dengan gradien kerucut, lihat conic.style.

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

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
Demonstrasi latar belakang yang berubah antara preferensi warna terang dan gelap.

Mengaktifkan perspektif 3D

Agar elemen ada dalam ruang 3D laman web, area pandang dengan perspektif perlu diinisialisasi. Saya memilih untuk memberikan perspektif pada elemen body dan menggunakan unit area pandang untuk membuat gaya yang saya sukai.

body {
  perspective: 40vw;
}

Inilah jenis perspektif dampak yang dapat ditimbulkan.

Menata gaya daftar tombol <ul>

Elemen ini bertanggung jawab atas keseluruhan tata letak makro daftar tombol serta menjadi kartu mengambang 3D yang interaktif. Berikut cara untuk melakukannya.

Tata letak grup tombol

Flexbox dapat mengelola tata letak penampung. Mengubah arah default flex dari baris ke kolom dengan flex-direction dan pastikan setiap item berukuran isinya dengan mengubah dari stretch menjadi start untuk align-items.

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

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

Selanjutnya, tetapkan penampung sebagai konteks ruang 3D dan siapkan clamp() CSS fungsi guna memastikan kartu tidak berputar di luar rotasi yang dapat dibaca. Pemberitahuan bahwa nilai tengah untuk klem adalah properti khusus, --x dan --y ini nilai akan ditetapkan dari JavaScript dengan mouse interaksi nanti.

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

Selanjutnya, jika {i>motion <i}diizinkan oleh pengguna yang berkunjung, tambahkan petunjuk ke {i>browser<i} yang transformasi item ini akan terus berubah dengan will-change Selain itu, aktifkan interpolasi dengan menetapkan transition pada transformasi. Ini akan terjadi ketika mouse berinteraksi dengan kartu, sehingga memungkinkan perubahan rotasi. Animasi adalah animasi yang berjalan secara konstan yang menunjukkan ruang 3D tempat kartu berada, meskipun {i>mouse<i} tidak dapat atau tidak berinteraksi dengan komponen.

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

Animasi rotate-y hanya menetapkan keyframe tengah pada 50% sejak browser akan menetapkan 0% dan 100% ke gaya default elemen. Ini adalah singkatan untuk animasi yang bergantian, harus dimulai dan diakhiri pada saat yang sama posisi Anda. Ini adalah cara yang bagus untuk menyampaikan animasi alternatif tanpa batas.

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

Menata gaya elemen <li>

Setiap item daftar (<li>) berisi tombol dan elemen batasnya. Tujuan Gaya display diubah sehingga item tidak menampilkan ::marker. Gaya position disetel ke relative sehingga elemen pseudo tombol yang akan datang dapat memosisikan sendiri dalam area penuh yang dikonsumsi tombol.

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

Screenshot daftar yang diputar dalam ruang 3D untuk menampilkan perspektif, dan
setiap item daftar tidak lagi memiliki butir.

Menata gaya elemen <button>

Menata gaya tombol bisa menjadi pekerjaan sulit, ada banyak status dan jenis interaksi yang harus diperhitungkan. Tombol-tombol ini menjadi kompleks dengan cepat karena menyeimbangkan elemen semu, animasi, dan interaksi.

Gaya <button> awal

Berikut adalah gaya dasar yang akan mendukung status lain.

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

Screenshot daftar tombol dalam perspektif 3D, kali ini dengan gaya
tombol.

Elemen pseudo tombol

Batas tombol bukanlah batas tradisional, melainkan posisi mutlak {i>pseudo-element<i} dengan {i>border<i}.

Screenshot panel Elemen Devtools Chrome dengan tombol yang ditampilkan memiliki
elemen ::before dan ::after.

Elemen-elemen ini sangat penting dalam menampilkan perspektif 3D yang telah mapan. Salah satu elemen-pseudo ini akan didorong menjauhi tombol, dan satu item akan ditarik lebih dekat ke pengguna. Efeknya paling terlihat dalam tombol atas dan bawah.

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

Gaya transformasi 3D

Di bawah transform-style disetel ke preserve-3d sehingga turunan dapat memberi spasi berada di sumbu z. transform disetel ke --distance properti kustom, yang akan ditingkatkan dengan arah kursor dan fokus.

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

Gaya animasi bersyarat

Jika pengguna setuju dengan {i>motion-<i}nya, tombol itu memberi petunjuk ke browser bahwa properti transform harus siap untuk diubah dan transisi diatur untuk Properti transform dan background-color. Perhatikan perbedaan durasi singkat, saya merasa itu menghasilkan efek bergiliran yang halus dan bagus.

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

Gaya interaksi arahkan kursor dan fokus

Tujuan dari animasi interaksi adalah untuk menyebarkan {i>layer<i} yang membentuk muncul datar. Selesaikan ini dengan menetapkan variabel --distance, awalnya ke 1px. Pemilih yang ditampilkan dalam contoh kode berikut memeriksa melihat apakah tombol diarahkan atau difokuskan oleh perangkat yang seharusnya melihat indikator fokus, dan tidak diaktifkan. Jika demikian, berlaku CSS untuk melakukan berikut ini:

  • Terapkan warna latar belakang pengarahan kursor.
  • Tambah jarak .
  • Tambahkan efek kemudahan pantulan.
  • Lakukan transisi elemen semu secara bertahap.
.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 }
    }
  }
}

Perspektif 3D masih sangat rapi untuk preferensi gerakan reduced. Elemen atas dan bawah menampilkan efek dengan cara yang halus dan bagus.

Peningkatan kecil dengan JavaScript

Antarmuka ini dapat digunakan dari {i>keyboard<i}, {i>screen reader<i}, {i>gamepad<i}, {i>touch<i} dan {i>mouse<i}, tapi kita dapat menambahkan sedikit sentuhan pada JavaScript untuk memudahkan beberapa skenario.

Mendukung tombol panah

Tombol tab adalah cara baik untuk menavigasi menu, tetapi saya mengharapkan tombol atau joystick untuk memindahkan fokus pada gamepad. Tujuan Library roving-ux yang sering digunakan untuk GUI Antarmuka tantangan akan menangani tombol panah untuk kita. Kode di bawah ini memberi tahu library untuk menjebak fokus dalam .threeD-button-set dan meneruskan fokus ke turunan tombol.

import {rovingIndex} from 'roving-ux'

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

Interaksi paralaks mouse

Melacak mouse dan memiringkannya ke menu dimaksudkan untuk meniru AR dan VR antarmuka {i>video game<i}, di mana alih-alih {i>mouse<i} Anda mungkin memiliki pointer virtual. Akan sangat menyenangkan ketika elemen sangat sadar terhadap pointer.

Karena ini adalah fitur tambahan kecil, kita akan menempatkan interaksi di belakang kueri preferensi {i>motion <i} pengguna. Selain itu, sebagai bagian dari penyiapan, simpan daftar tombol komponen ke dalam memori dengan querySelector dan meng-cache batas elemen ke dalam menuRect. Gunakan batas ini untuk menentukan offset rotasi yang diterapkan pada kartu berdasarkan posisi mouse.

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

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

Selanjutnya, kita memerlukan fungsi yang menerima posisi x dan y mouse dan menampilkan nilai yang dapat kita gunakan untuk memutar kartu. Fungsi berikut menggunakan {i>mouse<i} posisi untuk menentukan di sisi mana kotak itu berada dan berapa banyak. Tujuan delta ditampilkan dari fungsi.

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

Terakhir, perhatikan gerakan mouse, teruskan posisi ke fungsi getAngles() kita dan menggunakan nilai delta sebagai gaya properti kustom. saya bagi dengan 20 untuk delta dan membuatnya tidak terlalu gugup, mungkin ada cara yang lebih baik untuk melakukannya. Jika Anda ingat sejak awal, kita menempatkan properti --x dan --y di tengah clamp(), hal ini mencegah posisi mouse terlalu sering memutar ke posisi yang tidak terbaca.

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

Terjemahan dan arahan

Ada satu hal yang tidak berguna saat menguji menu game dalam mode penulisan lainnya dan bahasa.

Elemen <button> memiliki gaya !important untuk writing-mode di pengguna stylesheet agen. Artinya, HTML menu {i>game<i} perlu diubah untuk mengakomodasi desain yang diinginkan. Mengubah daftar tombol menjadi daftar tautan memungkinkan logika properti untuk mengubah arah menu, karena elemen <a> tidak memiliki browser gaya !important yang diberikan.

Kesimpulan

Sekarang setelah Anda tahu cara saya melakukannya, bagaimana Anda‽ 🙂 Bisakah Anda menambahkan akselerometer pengguna ke menu, jadi memutar menu akan menggunakan ponsel Anda? Bisakah kita memperbaiki pengalaman tanpa gerakan?

Mari kita diversifikasi pendekatan kami dan mempelajari semua cara untuk membangun di web. Buat demo, link tweet saya, dan saya akan menambahkannya ke bagian remix komunitas di bawah ini.

Remix komunitas

Belum ada apa-apa di sini.