3D 게임 메뉴 구성요소 빌드

액세스가 가능하고 반응형의 적응형 3D 게임 메뉴를 빌드하는 방법에 관한 기본 개요입니다.

이 게시물에서는 3D 게임 메뉴 구성 요소를 빌드하는 방법에 대한 생각을 공유하고자 합니다. 데모 사용해 보기

데모

동영상을 선호한다면 이 게시물의 YouTube 버전을 참고하세요.

개요

비디오 게임은 사용자에게 창의적이고 특이한 메뉴, 애니메이션 및 3D 공간을 제공하는 경우가 많습니다. 새로운 AR/VR 게임에서 메뉴가 우주에 떠 있는 것처럼 보이게 하는 데 널리 사용됩니다. 오늘은 이 효과의 필수 요소를 다시 만들어 보겠습니다. 더불어 낮은 모션을 선호하는 사용자를 위해 적응형 색 구성표와 조정 기능을 추가하였습니다.

HTML

게임 메뉴는 버튼 목록입니다. 이를 HTML로 표현하는 가장 좋은 방법은 다음과 같습니다.

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

버튼 목록은 스크린 리더 기술에 잘 알려지며 JavaScript 또는 CSS 없이도 작동합니다.

글머리기호 목록으로 항목을 만들 수 있습니다.

CSS

버튼 목록의 스타일 지정은 다음과 같은 상위 단계로 분류됩니다.

  1. 맞춤 속성 설정
  2. Flexbox 레이아웃
  3. 장식용 유사 요소가 포함된 맞춤 버튼
  4. 요소를 3D 공간에 배치

맞춤 속성 개요

커스텀 속성은 무작위로 보이는 값에 의미 있는 이름을 지정하여 값의 명확성을 높여주고, 코드 반복을 방지하고 하위 요소 간에 값을 공유하는 것을 방지합니다.

다음은 CSS 변수로 저장된 미디어 쿼리(맞춤 미디어라고도 함)입니다. 이는 전역적이며 코드를 간결하고 읽기 쉽게 유지하기 위해 다양한 선택기에서 사용됩니다. 게임 메뉴 구성요소는 디스플레이의 모션 환경설정, 시스템 색 구성표색상 범위 기능을 사용합니다.

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

다음 맞춤 속성은 색 구성표를 관리하고 마우스 위치 값을 길게 유지하여 게임 메뉴가 마우스 오버가 되도록 만듭니다. 커스텀 속성에 이름을 지정하면 값의 사용 사례 또는 값 결과의 친숙한 이름을 나타내므로 코드 가독성에 도움이 됩니다.

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

밝은 테마 및 어두운 테마 배경 원뿔 배경

밝은 테마에는 생동감 넘치는 cyan~deeppink 원뿔 그라데이션이 표시되고 어두운 테마에는 어둡고 은은한 원뿔 그라데이션이 있습니다. 원뿔 그라데이션으로 할 수 있는 작업에 관한 자세한 내용은 conic.style을 참고하세요.

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

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
밝은 색상 환경설정과 어두운 색상 환경설정 간에 변경되는 배경입니다.

3D 관점 사용 설정

요소가 웹페이지의 3D 공간에 존재하려면 원시의 표시 영역을 초기화해야 합니다. body 요소에 원근을 적용하기로 선택하고 표시 영역 단위를 사용하여 원하는 스타일을 만들었습니다.

body {
  perspective: 40vw;
}

이것은 관점이 미칠 수 있는 영향의 유형입니다.

<ul> 버튼 목록 스타일 지정

이 요소는 전체 버튼 목록 매크로 레이아웃 및 양방향 3D 플로팅 카드를 담당합니다. 이를 실현할 수 있는 방법을 소개합니다

버튼 그룹 레이아웃

Flexbox는 컨테이너 레이아웃을 관리할 수 있습니다. flex-direction를 사용하여 가변의 기본 방향을 행에서 열로 변경하고, align-itemsstretch에서 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;
}

다음으로, 컨테이너를 3D 공간 컨텍스트로 설정하고 카드가 읽을 수 있는 회전을 넘어 회전하지 않도록 CSS clamp() 함수를 설정합니다. 고정의 중간 값은 맞춤 속성이며 --x--y 값은 나중에 마우스 상호작용 시 JavaScript에서 설정됩니다.

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

다음으로, 방문하는 사용자의 모션이 정상이면 이 항목의 변환이 will-change를 통해 지속적으로 변경된다는 힌트를 브라우저에 추가합니다. 또한 변환에 transition를 설정하여 보간 유형을 사용 설정합니다. 이 전환은 마우스가 카드와 상호작용할 때 발생하므로 회전 변경으로 원활하게 전환할 수 있습니다. 애니메이션은 일정하게 실행되는 애니메이션으로, 마우스가 구성요소와 상호작용할 수 없거나 상호작용하지 않는 경우에도 카드가 포함된 3D 공간을 보여줍니다.

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

브라우저가 0%100%를 요소의 기본 스타일로 기본 설정하므로 rotate-y 애니메이션은 50%의 중간 키프레임만 설정합니다. 이는 번갈아 진행되는 애니메이션의 약칭으로, 동일한 위치에서 시작하고 끝나야 합니다. 이는 무한 번갈아 반복되는 애니메이션을 설명하는 좋은 방법입니다.

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

<li> 요소 스타일 지정

각 목록 항목 (<li>)에는 버튼과 테두리 요소가 포함되어 있습니다. display 스타일이 변경되어 항목에 ::marker가 표시되지 않습니다. position 스타일은 relative로 설정되므로 다음 버튼 유사 요소가 버튼이 사용하는 전체 영역 내에 배치될 수 있습니다.

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

원근을 보여주기 위해 3D 공간에서 회전된 목록의 스크린샷. 각 목록 항목에는 더 이상 글머리기호가 없습니다.

<button> 요소 스타일 지정

버튼 스타일을 지정하는 작업은 까다로울 수 있으며 고려해야 할 상태와 상호작용 유형이 많습니다. 이러한 버튼은 유사 요소, 애니메이션, 상호작용의 균형을 맞추므로 빠르게 복잡해집니다.

초기 <button> 스타일

다음은 다른 상태를 지원하는 기본 스타일입니다.

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

이번에는 스타일이 지정된 버튼이 있는 3D 원근 버튼 목록의 스크린샷

버튼 의사 요소

버튼의 테두리는 기존 테두리가 아니라 테두리가 있는 절대 위치 의사 요소입니다.

::before 및 ::after 요소가 있는 버튼이 표시된 Chrome Devtools Elements 패널의 스크린샷

이러한 요소는 설정된 3D 관점을 표시하는 데 매우 중요합니다. 이러한 유사 요소 중 하나가 버튼에서 푸시되고 하나는 사용자에게 더 가까이 당겨집니다. 그 효과는 상단과 하단 버튼에서 가장 두드러집니다.

.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 변환 스타일

아래 transform-stylepreserve-3d로 설정되므로 하위 요소가 z 축에서 스스로 간격을 둘 수 있습니다. transform--distance 맞춤 속성으로 설정되며 마우스 오버 및 초점을 맞추면 증가합니다.

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

조건부 애니메이션 스타일

사용자가 모션에 문제가 없는 경우 버튼은 변환 속성이 변경될 준비가 되고 transformbackground-color 속성에 대한 전환이 설정되었음을 브라우저에 알립니다. 소요 시간의 차이를 알 수 있습니다. 섬세하게 시차를 가리는 효과가 있는 것 같았습니다.

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

마우스 오버 및 포커스 상호작용 스타일

상호작용 애니메이션의 목표는 평면으로 나타나는 버튼을 구성하는 레이어를 확산하는 것입니다. 이렇게 하려면 --distance 변수를 처음에 1px로 설정합니다. 다음 코드 예에 표시된 선택기는 포커스 표시기가 표시되어야 하지만 활성화되지 않은 기기에서 버튼에 마우스를 가져가거나 포커스가 있는지 확인합니다. 이 경우 CSS를 적용하여 다음을 실행합니다.

  • 마우스 오버 배경 색상을 적용합니다.
  • 거리를 넓힙니다 .
  • 바운스 이징 효과를 추가합니다.
  • 의사 요소 전환을 시차를 두고 진행합니다.
.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 }
    }
  }
}

reduced 모션 환경설정의 3D 관점은 여전히 매우 깔끔했습니다. 상단 및 하단 요소는 효과를 섬세하게 보여줍니다.

JavaScript를 통한 일부 기능 개선

인터페이스는 이미 키보드, 스크린 리더, 게임패드, 터치, 마우스로 사용할 수 있지만, 몇 가지 시나리오를 쉽게 하기 위해 JavaScript에 약간의 터치를 추가할 수 있습니다.

지원 화살표 키

탭 키는 메뉴를 탐색하는 데 좋은 방법이지만 방향 패드나 조이스틱을 사용하여 게임패드에서 포커스를 이동할 것으로 예상됩니다. GUI 챌린지 인터페이스에 자주 사용되는 roving-ux 라이브러리가 화살표 키를 자동으로 처리합니다. 아래 코드는 .threeD-button-set 내에서 포커스를 트랩하고 버튼 하위 요소에 포커스를 전달하도록 라이브러리에 지시합니다.

import {rovingIndex} from 'roving-ux'

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

마우스 시차 상호작용

마우스를 추적하고 메뉴를 기울이면 마우스 대신 가상 포인터가 있을 수 있는 AR 및 VR 비디오 게임 인터페이스를 모방하기 위한 것입니다. 요소가 포인터를 하이퍼 인지하는 것은 재미있을 수 있습니다.

이 기능은 작은 추가 기능이므로 사용자의 모션 환경설정 쿼리 뒤에 상호작용을 배치합니다. 또한 설정의 일부로 querySelector를 사용하여 버튼 목록 구성요소를 메모리에 저장하고 요소의 경계를 menuRect에 캐시합니다. 이 경계를 사용하여 마우스 위치를 기반으로 카드에 적용되는 회전 오프셋을 결정합니다.

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

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

다음으로, 마우스 xy 위치를 허용하고 카드를 회전하는 데 사용할 수 있는 값을 반환하는 함수가 필요합니다. 다음 함수는 마우스 위치를 사용하여 상자의 어느 쪽이 상자 안에 어느 정도 들어 있는지 판단합니다. 델타는 함수에서 반환됩니다.

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

마지막으로 마우스의 움직임을 확인하고 위치를 getAngles() 함수에 전달하고 델타 값을 맞춤 속성 스타일로 사용합니다. 델타를 패딩하고 경련을 줄이기 위해 20으로 나눕니다. 더 나은 방법이 있을 수 있습니다. 처음부터 생각한다면 --x--y 속성을 clamp() 함수의 중간에 배치하여 마우스 위치로 인해 카드가 판독하기 어려운 위치로 위아래로 회전하는 것을 방지합니다.

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

번역 및 경로

다른 쓰기 모드와 언어로 게임 메뉴를 테스트할 때 한 가지 실수가 있었습니다.

<button> 요소에는 사용자 에이전트 스타일시트의 writing-mode에 대한 !important 스타일이 있습니다. 즉, 원하는 디자인을 수용하려면 게임 메뉴 HTML을 변경해야 했습니다. 버튼 목록을 링크 목록으로 변경하면 <a> 요소에 브라우저에서 제공하는 !important 스타일이 없으므로 논리적 속성에서 메뉴 방향을 변경할 수 있습니다.

결론

제가 어떻게 했는지 이해했다면 어떻게 해야 할까요?‽ 보상 휴대전화에 가속도계 상호작용을 추가하여 휴대전화에서 메뉴를 회전할 수 있나요? 모션이 없는 환경을 개선할 수 있을까요?

접근방식을 다각화하고 웹에서 빌드하는 방법을 모두 알아보겠습니다. 데모를 만들고 트윗해 주세요 링크. 아래 커뮤니티 리믹스 섹션에 추가하겠습니다.

커뮤니티 리믹스

표시할 내용이 아직 없습니다.