Creazione di un componente del menu di un gioco 3D

Una panoramica di base su come creare un menu di gioco 3D reattivo, adattivo e accessibile.

In questo post voglio condividere le idee su un modo per creare un componente del menu di un gioco in 3D. Prova la demo.

Demo

Se preferisci i video, ecco una versione di questo post su YouTube:

Panoramica

I videogiochi spesso presentano agli utenti un menu creativo e insolito, animato e nello spazio 3D. È popolare nei nuovi giochi AR/VR per far sembrare il menu che fluttuano nello spazio. Oggi creeremo gli elementi essenziali di questo effetto, ma con l'aggiunta di una combinazione di colori adattiva e adattamento per gli utenti che preferiscono la riduzione del movimento.

HTML

Il menu di un gioco è costituito da un elenco di pulsanti. Il modo migliore per rappresentarlo in HTML è che segue:

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

Un elenco di pulsanti si annuncia bene per le tecnologie di screen reader e funziona senza JavaScript o CSS.

un
elenco puntato molto generico con pulsanti normali come elementi.

CSS

L'impostazione di uno stile per l'elenco dei pulsanti è suddivisa nei seguenti passaggi generali:

  1. Impostazione di proprietà personalizzate.
  2. Un layout flexbox.
  3. Un pulsante personalizzato con pseudoelementi decorativi.
  4. Inserire elementi nello spazio 3D.

Panoramica delle proprietà personalizzate

Le proprietà personalizzate aiutano a distinguere i valori mediante l'attribuzione di contenuti con nomi casuali a valori altrimenti casuali, evitando codice ripetuto e tra bambini e ragazzi.

Di seguito sono riportate le query supporti salvate come variabili CSS, note anche come contenuti multimediali. Questi sono globali verrà utilizzata in vari selettori per mantenere il codice conciso e leggibile. La il componente menu del gioco usa motion preferenze, colore sistema schematica, e intervallo di colori funzionalità del display.

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

Le seguenti proprietà personalizzate gestiscono la combinazione di colori e tieni premuto il mouse valori di posizione per rendere interattivo il menu del gioco per passare il mouse sopra il video. Denominazione personalizzata proprietà del codice, in quanto rivela il caso d'uso del valore o di una nome descrittivo per il risultato del valore.

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

Sfondi conici di sfondo con tema chiaro e scuro

Il tema chiaro ha una conica vivace da cyan a deeppink sfumatura mentre il tema scuro ha un gradiente conico sottile. Per scoprire di più su cosa può essere fatto con i gradienti conici; consulta conic.style.

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

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
Dimostrazione dello sfondo che cambia preferenze di colore chiaro e scuro.

Attivazione della prospettiva 3D

Per fare in modo che gli elementi esistano nello spazio 3D di una pagina web, viene utilizzata un'area visibile con prospettiva deve essere inizializzato. Ho scelto di mettere la prospettiva all'elemento body e ho utilizzato le unità area visibile per creare lo stile che mi piaceva.

body {
  perspective: 40vw;
}

Questo è il tipo di impatto che la prospettiva può avere.

Stili dell'elenco di pulsanti <ul>

Questo elemento è responsabile del layout generale delle macro dell'elenco di pulsanti, nonché una scheda interattiva e mobile in 3D. Ecco un modo per farlo.

Layout del gruppo di pulsanti

Flexbox può gestire il layout del container. Modifica la direzione predefinita di Flex da righe a colonne con flex-direction e assicurati che ogni elemento abbia le dimensioni i suoi contenuti modificando da stretch a start per 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;
}

Successivamente, definisci il container come contesto dello spazio 3D e configura il CSS clamp() per garantire che la scheda non ruoti oltre i giri leggibili. Avvisi che il valore medio del morsetto è una proprietà personalizzata, questi --x e --y i valori verranno impostati da JavaScript al momento del mouse un'interazione in un secondo momento.

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

Quindi, se il movimento è accettabile per l'utente che visita il sito, aggiungi un suggerimento al browser la trasformazione di questo elemento cambierà costantemente will-change Inoltre, abilita l'interpolazione impostando un transition sulle trasformazioni. Questo avverrà quando il mouse interagisce con la scheda, consentendo di creare transizioni alle modifiche di rotazione. L'animazione è sempre in esecuzione che dimostri lo spazio 3D all'interno della scheda, anche se il mouse non riesce o non sta interagendo con il componente.

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

L'animazione rotate-y imposta solo il fotogramma chiave centrale in 50% dal browser utilizzerà lo stile predefinito dell'elemento per 0% e 100%. Questo è una forma abbreviata di animazioni che si alternano, e devono iniziare e terminare allo stesso posizione. È un ottimo modo per articolare animazioni infinite alterne.

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

Stili degli elementi <li>

Ogni elemento dell'elenco (<li>) contiene il pulsante e i relativi elementi del bordo. La Lo stile display è stato modificato, quindi l'elemento non mostra ::marker Lo stile position è impostato su relative, quindi gli pseudo-elementi del pulsante successivo possono posizionarsi all'interno dell'intera area occupata dal pulsante.

.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 dell&#39;elenco ruotato in uno spazio 3D per mostrare la prospettiva e
ogni elemento dell&#39;elenco non ha più un punto elenco.

Stili degli elementi <button>

L'applicazione di stili ai pulsanti può essere complessa, ci sono molti stati e tipi di interazione da tenere in considerazione. Questi pulsanti diventano rapidamente complessi a causa del bilanciamento pseudo-elementi, animazioni e interazioni.

Stili iniziali di <button>

Di seguito sono riportati gli stili fondamentali che supporteranno gli altri stati.

.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 dell&#39;elenco di pulsanti in prospettiva 3D, questa volta con stili applicati
pulsanti.

Pseudo-elementi pulsante

I bordi del pulsante non sono bordi tradizionali, ma posizioni assolute pseudo-elementi con bordi.

Screenshot del riquadro Elementi per sviluppatori di Chrome con un pulsante con un pulsante
Elementi ::before e ::after.

Questi elementi sono fondamentali per mostrare la prospettiva 3D che è stata la creazione di un progetto. Uno di questi pseudo-elementi viene spinto via dal pulsante, e una viene avvicinata all'utente. L'effetto si nota soprattutto pulsanti in alto e in basso.

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

Stili di trasformazione 3D

Inferiore a transform-style è impostato su preserve-3d per consentire ai bambini di lasciare lo spazio sull'asse z. Il valore transform è impostato su --distance personalizzata, che verrà aumentata al passaggio del mouse e il focus.

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

Stili di animazione condizionali

Se l'utente è d'accordo con il movimento, il pulsante suggerisce al browser che proprietà di trasformazione deve essere pronta per il cambiamento ed è impostata una transizione Proprietà transform e background-color. Nota la differenza durata, ho ritenuto che fosse un bel effetto sfalsato, sottile.

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

Passa il mouse sopra gli stili di interazione e imposta lo stato attivo

L'obiettivo dell'animazione di interazione è diffondere gli strati che compongono pulsante visualizzato piatto. A questo scopo, imposta la variabile --distance. inizialmente a 1px. Il selettore mostrato nel seguente esempio di codice verifica verifica se passa il mouse sopra il pulsante o se lo stato attivo è impostato su un dispositivo che dovrebbe visualizzare dell'indicatore di messa a fuoco e la mancata attivazione. In tal caso, applica un CSS per svolgere seguenti:

  • Applica il colore di sfondo al passaggio del mouse.
  • Aumenta la distanza .
  • Aggiungi un effetto di rimbalzo in caso di rimbalzo.
  • Sfalsa le transizioni dello pseudo-elemento.
di Gemini Advanced.
.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 }
    }
  }
}

La prospettiva 3D era comunque molto utile per la preferenza di movimento reduced. Gli elementi superiore e inferiore mostrano l'effetto in modo elegante e discreto.

Piccoli miglioramenti con JavaScript

L'interfaccia è utilizzabile da tastiere, screen reader, gamepad, touch e una mouse, ma possiamo aggiungere alcuni tocchi leggeri di JavaScript per facilitare di scenari diversi.

Tasti freccia di supporto

Il tasto Tab consente di navigare nel menu, ma mi aspetterei la barra di pad o joystick per spostare lo stato attivo su un gamepad. La Libreria roving-ux spesso usata per GUI Le interfacce della sfida gestiranno i tasti freccia al posto nostro. Il codice riportato di seguito indica libreria per bloccare l'elemento attivo all'interno di .threeD-button-set e inoltrarlo al pulsanti secondari.

import {rovingIndex} from 'roving-ux'

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

Interazione con parallasse del mouse

Il tracciamento del mouse e l'inclinazione del menu hanno lo scopo di simulare AR e VR interfacce di videogiochi, dove al posto del mouse potresti avere un puntatore virtuale. Può essere divertente quando gli elementi sono estremamente consapevoli del puntatore.

Poiché si tratta di una piccola caratteristica aggiuntiva, mettiamo l'interazione dietro una query in base alla preferenza di movimento dell'utente. Inoltre, nell'ambito della configurazione, memorizza l'elenco dei pulsanti in memoria con querySelector e memorizzare nella cache i limiti dell'elemento menuRect. Utilizza questi limiti per determinare l'offset della rotazione applicato alla scheda in base alla posizione del mouse.

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

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

Successivamente, abbiamo bisogno di una funzione che accetti le posizioni x e y del mouse e restituisca un valore che possiamo usare per ruotare la scheda. La seguente funzione utilizza il mouse posizione per determinare in quale lato della scatola si trova e di quanto. La delta viene restituito dalla funzione.

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

Infine, controlla il movimento del mouse e passa la posizione alla funzione getAngles(). e utilizzare i valori delta come stili di proprietà personalizzati. Ho diviso per 20 per coprire delta e renderlo meno nervoso, potrebbe esserci un modo migliore per farlo. Se ricorda che dall'inizio abbiamo messo gli oggetti --x e --y nel mezzo di clamp(), impedisce alla posizione del mouse di ruotare eccessivamente carta in una posizione illeggibile.

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

Traduzioni e indicazioni stradali

C'era una cosa da fare quando si provava il menu del gioco in altre modalità di scrittura e lingue diverse.

Gli elementi <button> hanno uno stile !important per writing-mode nell'utente foglio di stile dell'agente. Ciò significava che l'HTML del menu del gioco doveva cambiare per il design desiderato. La modifica dell'elenco di pulsanti in un elenco di link abilita per modificare la direzione del menu, in quanto gli elementi <a> non hanno un browser stile !important specificato.

Conclusione

Ora che sai come ci ho fatto, come lo faresti‽ 🙂 Puoi aggiungere l'accelerometro un'interazione con il menu, l'utilizzo del riquadro consente di ruotare il menu? Possiamo migliorare? l'esperienza senza movimento?

Diversificaamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, twittami con i link e la aggiungerò alla sezione dei remix della community qui sotto.

Remix della community

Ancora niente da visualizzare qui.