3D-Spielemenükomponente erstellen

Eine grundlegende Übersicht zum Erstellen eines responsiven, adaptiven und barrierefreien 3D-Spielmenüs.

In diesem Beitrag möchte ich meine Gedanken zu einer Möglichkeit zum Erstellen einer 3D-Spielmenükomponente teilen. Demo ansehen

Demo

Wenn du lieber ein Video ansiehst, findest du hier eine YouTube-Version dieses Beitrags:

Übersicht

In Videospielen wird Nutzern oft ein kreatives und ungewöhnliches Menü präsentiert, das animiert und in einem 3D-Raum ist. In neuen AR-/VR-Spielen ist es beliebt, das Menü so zu gestalten, dass es im Raum schwebt. Heute werden wir die wichtigsten Elemente dieses Effekts nachbilden, aber mit einem zusätzlichen Flair durch ein adaptives Farbschema und Anpassungen für Nutzer, die weniger Bewegung bevorzugen.

HTML

Ein Spielmenü ist eine Liste von Schaltflächen. Am besten wird dies in HTML so dargestellt:

<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 kann von Screenreadern gut erkannt werden und funktioniert ohne JavaScript oder CSS.

eine sehr allgemein aussehende Aufzählungsliste mit normalen Schaltflächen als Elementen.

CSS

Das Festlegen des Stils für die Schaltflächenliste umfasst die folgenden allgemeinen Schritte:

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

Benutzerdefinierte Properties

Mit benutzerdefinierten Properties lassen sich Werte eindeutig identifizieren, indem ansonsten zufällig erscheinende Werte aussagekräftige Namen erhalten. So wird wiederholter Code vermieden und Werte können für untergeordnete Elemente verwendet werden.

Unten sehen Sie Mediaabfragen, die als CSS-Variablen gespeichert sind, auch als benutzerdefinierte Medien bezeichnet. Sie sind global und werden in verschiedenen Auswahlelementen verwendet, um den Code prägnant und lesbar zu halten. Die Komponente „Spielmenü“ verwendet die Bewegungseinstellungen, das Farbschema des Systems und die Farbbereichsfunktionen des Displays.

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

Mit den folgenden benutzerdefinierten Eigenschaften wird das Farbschema verwaltet und die Mauspositionswerte gespeichert, um das Spielmenü interaktiv zu machen. Die Benennung benutzerdefinierter Properties trägt zur Lesbarkeit des Codes bei, da sie den Anwendungsfall für den Wert oder einen nutzerfreundlichen Namen für das Ergebnis des Werts preisgibt.

.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 cyan bis deeppink konischen Farbverlauf, während das dunkle Design einen dunklen, subtilen konischen 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 für den Hintergrund.

3D-Perspektive aktivieren

Damit Elemente im 3D-Raum einer Webseite existieren können, muss ein Darstellungsbereich mit Perspektive initialisiert werden. Ich habe die Perspektive auf das body-Element gelegt und Darstellungseinheiten verwendet, um den gewünschten Stil zu erstellen.

body {
  perspective: 40vw;
}

Das ist die Art von Auswirkungen, die eine Perspektive haben kann.

<ul>-Schaltflächenliste stylen

Dieses Element ist für das Gesamtlayout des Makros für die Schaltflächenliste verantwortlich und dient als interaktive und schwebende 3D-Karte. So gehts:

Layout der Schaltflächengruppe

Mit Flexbox kann das Container-Layout verwaltet werden. Ändern Sie die Standardausrichtung von Flex von Zeilen zu Spalten mit flex-direction und sorgen Sie dafür, dass jedes Element die Größe seines Inhalts hat, indem Sie für align-items von stretch zu start wechseln.

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

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

Legen Sie als Nächstes den Container als 3D-Raumkontext fest und richten Sie CSS-clamp()-Funktionen ein, damit sich die Karte nicht über lesbare Drehungen hinaus dreht. Der mittlere Wert für die Begrenzung ist eine benutzerdefinierte Eigenschaft. Diese --x- und --y-Werte werden später bei der Mausinteraktion über 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 der Besucher der Bewegung zustimmt, fügen Sie dem Browser einen Hinweis hinzu, dass sich die Transformation dieses Elements ständig mit will-change ändert. Aktivieren Sie außerdem die Interpolation, indem Sie für Transformationen eine transition festlegen. Dieser Übergang wird ausgeführt, wenn die Maus mit der Karte interagiert. So werden flüssige Übergänge zu Drehungsänderungen ermöglicht. Die Animation läuft kontinuierlich und zeigt den 3D-Raum, in dem sich die Karte befindet, auch wenn eine Maus nicht mit der Komponente interagieren kann oder dies nicht tut.

@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 der gleichen 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 ihre Rahmenelemente. Der Stil display wird geändert, sodass für das Element kein ::marker angezeigt wird. Der Stil position ist auf relative festgelegt, damit sich die Pseudoelemente der Schaltfläche im gesamten Bereich positionieren können, den die Schaltfläche einnimmt.

.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 Liste, die im 3D-Raum gedreht wurde, um die Perspektive zu zeigen. Die einzelnen Listenelemente haben keine Aufzählungspunkte mehr.

<button>-Elemente stylen

Das Designen von Schaltflächen kann schwierig sein, da viele Status und Interaktionstypen berücksichtigt werden müssen. Durch die richtige Balance zwischen Pseudoelementen, Animationen und Interaktionen werden diese Schaltflächen schnell komplex.

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

Pseudo-Elemente für Schaltflächen

Die Rahmen der Schaltfläche sind keine herkömmlichen Rahmen, sondern Pseudoelemente mit Rahmen, die sich absolut positionieren lassen.

Screenshot des Elements-Steuerfelds in den Chrome DevTools mit einer Schaltfläche mit den Elementen ::before und ::after

Diese Elemente sind entscheidend, um die etablierte 3D-Perspektive zu präsentieren. Eines dieser Pseudoelemente wird von der Schaltfläche weggedrückt und eines näher an den Nutzer herangezogen. 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

Unten ist transform-style auf preserve-3d festgelegt, damit die untergeordneten Elemente auf der z-Achse gleichmäßig verteilt werden. Die transform ist auf die benutzerdefinierte Eigenschaft --distance festgelegt, die beim Bewegen des Mauszeigers und beim Fokus vergrößert 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 Bewegungen einverstanden ist, gibt die Schaltfläche dem Browser zu verstehen, dass die Transform-Property geändert werden kann und ein Übergang für die transform- und background-color-Properties festgelegt wird. 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 Hover- und Fokuseffekte

Ziel der Interaktionsanimation ist es, die Ebenen zu verteilen, aus denen die flach erscheinende Schaltfläche besteht. Legen Sie dazu die Variable --distance anfangs auf 1px fest. Mit dem im folgenden Codebeispiel gezeigten Auswahlelement wird geprüft, ob die Schaltfläche von einem Gerät angetippt oder fokussiert wird, auf dem ein Fokusindikator angezeigt werden sollte, und nicht aktiviert wird. In diesem Fall wird CSS angewendet, um Folgendes zu tun:

  • Wenden Sie die Hintergrundfarbe an, über die der Mauszeiger darauf bewegt wird.
  • Erhöhen Sie den Abstand.
  • Fügen Sie einen Bounce-Effekt hinzu.
  • Die Pseudoelementübergänge versetzt ausführen.
.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 reduced-Bewegungseinstellung immer noch sehr cool. Die oberen und unteren Elemente zeigen den Effekt auf dezente Weise.

Kleine Verbesserungen mit JavaScript

Die Benutzeroberfläche kann bereits über Tastaturen, Screenreader, Gamepads, Touchbedienung und Maus verwendet werden. Wir können aber einige JavaScript-Elemente hinzufügen, um einige Szenarien zu vereinfachen.

Unterstützung von Pfeiltasten

Die Tabulatortaste ist eine gute Möglichkeit, sich im Menü zu bewegen. Ich würde jedoch erwarten, dass der Fokus auf einem Gamepad mit dem Steuerkreuz oder dem Joystick verschoben wird. Die Bibliothek roving-ux, die häufig für GUI-Herausforderungen verwendet wird, übernimmt die Pfeiltasten für uns. Der folgende Code weist die Bibliothek an, den Fokus in .threeD-button-set zu fangen 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

Die Maus wird verfolgt und das Menü wird durch das Neigen der Maus gedreht. Das soll die Benutzeroberfläche von AR- und VR-Videospielen nachahmen, bei denen anstelle einer Maus ein virtueller Cursor verwendet wird. Es kann Spaß machen, wenn Elemente sehr auf den Cursor reagieren.

Da dies eine kleine zusätzliche Funktion ist, binden wir die Interaktion in eine Abfrage der Bewegungspräferenz des Nutzers ein. Speichern Sie im Rahmen der Einrichtung außerdem die Schaltflächenlistenkomponente mit querySelector im Arbeitsspeicher und speichern Sie die Begrenzungen des Elements in menuRect im Cache. Anhand dieser Grenzen wird der Drehungsoffset bestimmt, der auf die Karte basierend auf der Mausposition angewendet wird.

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. Bei der folgenden Funktion wird anhand der Mausposition ermittelt, auf welcher Seite des Felds sich die Maus befindet und wie weit. Die 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 zuletzt die Mausbewegung, übergeben Sie die Position an die getAngles()-Funktion und verwenden Sie die Deltawerte als benutzerdefinierte Property-Stile. Ich habe durch 20 geteilt, um die Deltawerte zu glätten und weniger ruckelig zu machen. Es gibt möglicherweise eine bessere Möglichkeit, das zu tun. Wie Sie sich erinnern, haben wir die Props --x und --y in die Mitte einer clamp()-Funktion gesetzt. Dadurch wird verhindert, dass die Karte durch die Mausposition zu stark gedreht wird und unlesbar wird.

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 Schriftarten und Sprachen gab es ein Problem.

<button>-Elemente haben im User-Agent-Stylesheet einen !important-Stil für writing-mode. Das bedeutet, dass der HTML-Code des Spielmenüs an das gewünschte Design angepasst werden musste. Wenn Sie die Schaltflächenliste in eine Liste von Links ändern, können Sie mithilfe logischer Eigenschaften die Menürichtung ändern, da <a>-Elemente keinen vom Browser bereitgestellten !important-Stil haben.

Fazit

Jetzt, da Sie wissen, wie ich es gemacht habe, wie würden Sie es machen? 🙂 Können Sie dem Menü eine Interaktion mit dem Beschleunigungsmesser hinzufügen, sodass das Menü gedreht wird, wenn Sie Ihr Smartphone schwenken? Können wir die Funktion für Videos ohne Bewegung verbessern?

Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie Sie im Web entwickeln können. Erstelle eine Demo, tweete mir Links und ich füge sie unten in den Abschnitt „Community-Remixe“ hinzu.

Remixe der Community

Noch keine Aktivität hierzu.