3D-Spielemenükomponente erstellen

Ein grundlegender Überblick über die Erstellung eines responsiven, adaptiven und barrierefreien 3D-Spielmenüs.

In diesem Beitrag möchte ich Ihnen zeigen, wie Sie eine 3D-Spielmenükomponente erstellen können. Demo ansehen

Demo

Falls Sie Videos bevorzugen, finden Sie hier eine YouTube-Version dieses Beitrags:

Überblick

Videospiele bieten Nutzern oft ein kreatives und ungewöhnliches Menü – animiert und im 3D-Raum. In neuen AR-/VR-Spielen ist es beliebt, dass das Menü im Weltraum schwebt. Heute zeigen wir die wesentlichen Elemente dieses Effekts wieder, aber mit dem zusätzlichen Flair eines adaptiven Farbschemas und Anpassungsmöglichkeiten für Nutzer, die weniger Bewegung bevorzugen.

HTML

Ein Spielmenü besteht aus einer Liste von Schaltflächen. So lässt sich dies am besten in HTML darstellen:

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

Eine Liste von Schaltflächen kommt für Screenreader-Technologien gut an und funktioniert ohne JavaScript oder CSS.

eine sehr allgemein aussehende Aufzählungsliste
mit regulären Schaltflächen als Elemente.

CSS

Der Stil der Schaltflächenliste ist in folgende übergeordnete Schritte unterteilt:

  1. Benutzerdefinierte Eigenschaften einrichten
  2. Ein Flexbox-Layout.
  3. Eine benutzerdefinierte Schaltfläche mit dekorativen Pseudoelementen.
  4. Elemente im 3D-Raum platzieren

Übersicht über benutzerdefinierte Eigenschaften

Mit benutzerdefinierten Attributen lassen sich Werte leichter unterscheiden, indem sie ansonsten zufällig aussehenden Werten aussagekräftige Namen geben. So werden wiederholte Codes vermieden und Werte unter untergeordneten Elementen geteilt.

Nachfolgend sind Medienabfragen aufgeführt, die als CSS-Variablen gespeichert sind. Diese werden auch als benutzerdefinierte Medien bezeichnet. Diese sind global und werden in verschiedenen Selektoren verwendet, um den Code prägnant und lesbar zu halten. Die Komponente des Spielmenüs verwendet Bewegungspräferenzen, Farbschemata des Systems und Farbbereichsfunktionen des Displays.

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

Die folgenden benutzerdefinierten Eigenschaften verwalten das Farbschema und halten die Mauspositionswerte, um das Spielmenü interaktiv zu gestalten, damit der Mauszeiger darauf bewegt werden kann. Durch das Benennen von benutzerdefinierten Eigenschaften ist der Code besser lesbar, da dadurch der Anwendungsfall für den Wert oder ein Anzeigename für das Ergebnis des Werts offengelegt wird.

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

Kegelförmiger Hintergrund in hellem und dunklem Design

Das helle Design hat einen leuchtenden konischen Farbverlauf von cyan bis deeppink, während das dunkle Design einen dunklen, subtilen Farbverlauf hat. Weitere Informationen zu konischen Farbverläufen finden Sie unter conic.style.

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

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
Demonstration des Wechsels zwischen hellen und dunklen Farbeinstellungen im Hintergrund.

3D-Perspektive aktivieren

Damit Elemente im 3D-Bereich einer Webseite vorhanden sind, muss ein Darstellungsbereich mit Perspektive initialisiert werden. Ich habe das body-Element mit der Perspektive eingeblendet und habe Darstellungsbereiche verwendet, um den Stil zu erstellen, der mir gefällt.

body {
  perspective: 40vw;
}

Dies ist die Art der Auswirkungsperspektive.

Stil der Schaltflächenliste für <ul> gestalten

Dieses Element ist für das Gesamtlayout der Schaltflächenliste mit Makros verantwortlich und ist eine interaktive und schwebende 3D-Karte. Das können Sie auf folgende Weise erreichen:

Layout der Schaltflächengruppe

Flexbox kann das Containerlayout verwalten. Ändern Sie die Standardrichtung der Flex-Funktion von Zeilen in Spalten mit flex-direction und achten Sie darauf, dass jedes Element die Größe seines Inhalts hat. Ändern Sie dazu für align-items von stretch in start.

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

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

Richten Sie als Nächstes den Container als 3D-Raumkontext ein und richten Sie CSS-clamp()-Funktionen ein, damit die Karte nicht über die lesbaren Rotationen hinaus gedreht wird. Der mittlere Wert für die Einschränkung ist eine benutzerdefinierte Eigenschaft. Diese --x- und --y-Werte werden später durch die Mausinteraktion aus JavaScript festgelegt.

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

Wenn eine Bewegung für den Besucher kein Problem ist, fügen Sie dem Browser einen Hinweis hinzu, dass sich die Transformation dieses Elements mit will-change ständig ändert. Aktivieren Sie außerdem die Interpolation, indem Sie für Transformationen einen transition festlegen. Dieser Übergang findet statt, wenn die Maus mit der Karte interagiert, wodurch reibungslose Übergänge bei Rotationsänderungen möglich sind. Die Animation ist eine ständig laufende Animation, die den 3D-Bereich zeigt, in dem sich die Karte befindet, auch wenn eine Maus nicht mit der Komponente interagieren kann oder wird.

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

Bei der rotate-y-Animation wird nur der mittlere Keyframe auf 50% gesetzt, da im Browser für 0% und 100% der Standardstil des Elements verwendet wird. Dies ist eine Abkürzung für Animationen, die sich wechseln und an derselben Position beginnen und enden müssen. Es ist eine großartige Möglichkeit, unendliche abwechselnde Animationen zu artikulieren.

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

Stil für <li>-Elemente festlegen

Jedes Listenelement (<li>) enthält die Schaltfläche und die dazugehörigen Rahmenelemente. Der display-Stil wird geändert, sodass für das Element kein ::marker angezeigt wird. Der Stil position ist auf relative gesetzt, sodass die Pseudoelemente der nächsten Schaltfläche sich innerhalb des gesamten Bereichs positionieren können, den die Schaltfläche belegt.

.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 der im 3D-Bereich gedrehten Liste zur Darstellung der Perspektive. Jedes Listenelement enthält keinen Aufzählungspunkt mehr.

Stil für <button>-Elemente festlegen

Die Gestaltung von Schaltflächen kann schwierig sein. Es gibt viele Stadien und Interaktionstypen, die berücksichtigt werden müssen. Durch die richtige Balance zwischen Pseudoelementen, Animationen und Interaktionen werden diese Schaltflächen schnell komplex.

Anfängliche <button>-Stile

Unten sind die grundlegenden Stile aufgeführt, die die anderen Stadien unterstützen.

.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 der Schaltflächenliste in 3D, dieses Mal mit Schaltflächen mit benutzerdefinierten Stilen

Pseudoelemente der Schaltfläche

Die Ränder der Schaltfläche sind keine herkömmlichen Rahmen, sondern Pseudoelemente mit Rahmen als absolute Positionselemente.

Screenshot des Bereichs „Elemente“ in Chrome-Entwicklertools mit einer Schaltfläche, die die Elemente „::before“ und „::after“ enthält.

Diese Elemente sind entscheidend, um die etablierte 3D-Perspektive zu präsentieren. Eines dieser Pseudoelemente wird von der Schaltfläche weg und eines dieser Elemente näher am Nutzer gezogen. Am deutlichsten wirkt sich die obere und die untere Taste aus.

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

Unter transform-style ist preserve-3d festgelegt, damit die Kinder sich auf der z-Achse freihalten können. Für transform ist das benutzerdefinierte Attribut --distance festgelegt, das beim Bewegen und Fokussieren erhöht wird.

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

Bedingte Animationsstile

Wenn der Nutzer mit der Bewegung einverstanden ist, zeigt die Schaltfläche dem Browser an, dass die Transformationseigenschaft bereit für Änderungen sein sollte und für die Eigenschaften transform und background-color ein Übergang festgelegt ist. Beachten Sie den Unterschied in der Dauer. Ich hatte den Eindruck, dass dies einen schönen, subtilen Effekt darstellt.

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

Interaktionsstile für Mauszeiger und Fokus

Das Ziel der Interaktionsanimation besteht darin, die Ebenen der flach erscheinenden Schaltfläche zu verteilen. Dazu legen Sie die Variable --distance anfangs auf 1px fest. Der im folgenden Codebeispiel gezeigte Selektor prüft, ob die Schaltfläche von einem Gerät, das eine Fokusanzeige sehen soll, bewegt oder hervorgehoben wird, und nicht aktiviert wird. In diesem Fall führt der CSS-Code Folgendes aus:

  • Wenden Sie die Hintergrundfarbe an, über die der Mauszeiger darauf bewegt wird.
  • Strecke vergrößern
  • Füge einen Entspannungseffekt hinzu.
  • Verteilen Sie die Übergänge des Pseudoelements.
.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 }
    }
  }
}

Die 3D-Perspektive war für die Bewegungseinstellung reduced immer noch ansprechend. Die Elemente oben und unten stellen den Effekt auf subtile Weise zur Verfügung.

Kleine Verbesserungen mit JavaScript

Die Benutzeroberfläche kann bereits über Tastaturen, Screenreader, Gamepads, Touch-Gesten und Maus genutzt werden, aber wir können JavaScript-Code hinzufügen, um einige Szenarien zu vereinfachen.

Unterstützende Pfeiltasten

Mit der Tabulatortaste können Sie gut im Menü navigieren. Ich würde jedoch erwarten, dass die Richtungstasten oder Joysticks den Fokus auf einem Gamepad bewegen. Die roving-ux-Bibliothek, die häufig für GUI Challenge-Oberflächen verwendet wird, übernimmt die Pfeiltasten für uns. Mit dem folgenden Code wird die Bibliothek angewiesen, den Fokus in .threeD-button-set abzufangen und an die untergeordneten Schaltflächen weiterzuleiten.

import {rovingIndex} from 'roving-ux'

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

Interaktion mit Maus-Parallaxe

Durch das Verschieben der Maus und das Neigen des Menüs sollen AR- und VR-Videospiele nachgeahmt werden, wo statt einer Maus möglicherweise ein virtueller Zeiger verwendet wird. Es kann lustig sein, wenn die Elemente den Zeiger sehr bewusst sind.

Da es sich um eine kleine Zusatzfunktion handelt, stellen wir die Interaktion hinter einer Abfrage der Bewegungspräferenz des Nutzers. Speichern Sie außerdem im Rahmen der Einrichtung die Schaltflächenlistenkomponente mit querySelector im Arbeitsspeicher und speichern Sie die Begrenzungen des Elements in menuRect im Cache. Verwenden Sie diese Grenzen, um den Rotations-Offset der Karte basierend auf der Mausposition zu bestimmen.

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

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

Als Nächstes benötigen wir eine Funktion, die die Mauspositionen x und y akzeptiert und einen Wert zurückgibt, mit dem die Karte gedreht werden kann. Die folgende Funktion bestimmt anhand der Mausposition, in welcher Seite des Rechtecks sie sich befindet und um wie viel. Das Delta wird von der Funktion zurückgegeben.

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

Beobachten Sie schließlich die Mausbewegung, übergeben Sie die Position an die Funktion getAngles() und verwenden Sie die Deltawerte als benutzerdefinierte Eigenschaftsstile. dividiert durch 20, um das Delta aufzufüllen, gibt es vielleicht eine bessere Methode. Wie Sie sich von Anfang an erinnern, platzieren wir die Attribute --x und --y in der Mitte einer clamp()-Funktion. Dadurch wird verhindert, dass die Mausposition die Karte zu einer unleserlichen Position dreht.

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

Übersetzungen und Wegbeschreibungen

Beim Testen des Spielmenüs in anderen Schreibmodi und Sprachen gab es ein Gratcha.

<button>-Elemente haben im User-Agent-Stylesheet den Stil !important für writing-mode. Das bedeutet, dass der HTML-Code des Spielmenüs an das gewünschte Design angepasst werden musste. Wenn du die Schaltflächenliste in eine Liste mit Links veränderst, kann die Menürichtung über die logischen Eigenschaften geändert werden, da <a>-Elemente keinen vom Browser bereitgestellten !important-Stil haben.

Fazit

Jetzt, wo Sie wissen, wie ich das gemacht habe, wie würden Sie... ‽ 🙂 Können Sie dem Menü die Interaktion mit dem Beschleunigungsmesser hinzufügen, Können wir die Funktion „Keine Bewegung“ verbessern?

Lassen Sie uns unsere Herangehensweisen diversifizieren und alle Möglichkeiten kennenlernen, wie wir das Web entwickeln können. Erstelle eine Demo, Tweets an mich und füge sie unten im Abschnitt zu Community-Remixen hinzu.

Community-Remixe

Hier gibt es noch nichts zu sehen.