ساخت یک جزء منوی بازی سه بعدی

یک نمای کلی از نحوه ساخت یک منوی بازی سه بعدی پاسخگو، تطبیقی ​​و در دسترس.

در این پست می‌خواهم تفکری را درباره روشی برای ساختن یک جزء منوی بازی سه بعدی به اشتراک بگذارم. نسخه ی نمایشی را امتحان کنید.

نسخه ی نمایشی

اگر ویدیو را ترجیح می دهید، در اینجا یک نسخه YouTube از این پست وجود دارد:

نمای کلی

بازی‌های ویدیویی اغلب یک منوی خلاقانه و غیرمعمول، متحرک و در فضای سه‌بعدی به کاربران ارائه می‌دهند. در بازی‌های جدید 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>

لیستی از دکمه ها به خوبی خود را به فناوری های صفحه خوان معرفی می کند و بدون جاوا اسکریپت یا CSS کار می کند.

یک لیست گلوله ای بسیار عمومی با دکمه های معمولی به عنوان آیتم.

CSS

سبک دادن به لیست دکمه ها به مراحل سطح بالا زیر تقسیم می شود:

  1. تنظیم خواص سفارشی
  2. طرح بندی فلکس باکس
  3. یک دکمه سفارشی با عناصر شبه تزئینی.
  4. قرار دادن عناصر در فضای سه بعدی

مروری بر خواص سفارشی

ویژگی‌های سفارشی با دادن نام‌های معنی‌دار به مقادیر تصادفی، اجتناب از تکرار کد و اشتراک‌گذاری مقادیر در بین کودکان، به ابهام‌زدایی مقادیر کمک می‌کند.

در زیر پرس و جوهای رسانه ذخیره شده به عنوان متغیرهای 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);
  }
}
نمایش تغییر پس زمینه بین ترجیحات رنگ روشن و تیره.

فعال کردن چشم انداز سه بعدی

برای اینکه عناصر در فضای سه بعدی یک صفحه وب وجود داشته باشند، یک viewport با پرسپکتیو باید مقداردهی اولیه شود. من انتخاب کردم که پرسپکتیو را روی عنصر body قرار دهم و از واحدهای دید برای ایجاد سبکی که دوست داشتم استفاده کردم.

body {
  perspective: 40vw;
}

این همان نوع پرسپکتیو تأثیری است که می تواند داشته باشد.

حالت دادن به لیست دکمه <ul>

این عنصر مسئول طرح بندی کلان لیست دکمه ها و همچنین یک کارت شناور تعاملی و سه بعدی است. در اینجا راهی برای رسیدن به آن وجود دارد.

طرح بندی گروهی دکمه

Flexbox می تواند چیدمان کانتینر را مدیریت کند. جهت پیش‌فرض flex را از ردیف‌ها به ستون‌ها با flex-direction تغییر دهید و با تغییر از stretch به start برای 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;
}

سپس، کانتینر را به عنوان یک زمینه فضای سه بعدی ایجاد کنید و توابع CSS clamp() را تنظیم کنید تا مطمئن شوید که کارت بیش از چرخش های خوانا نمی چرخد. توجه داشته باشید که مقدار وسط برای گیره یک ویژگی سفارشی است، این مقادیر --x و --y بعداً با تعامل ماوس از جاوا اسکریپت تنظیم می شوند.

.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 روی تبدیل ها فعال کنید. این انتقال زمانی اتفاق می‌افتد که ماوس با کارت تعامل داشته باشد و امکان انتقال صاف به تغییرات چرخش را فراهم کند. این انیمیشن یک انیمیشن در حال اجرا ثابت است که فضای سه بعدی کارت را نشان می دهد، حتی اگر ماوس نتواند یا در تعامل با مؤلفه نباشد.

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

انیمیشن rotate-y فقط فریم کلیدی میانی را روی 50% تنظیم می‌کند، زیرا مرورگر به‌طور پیش‌فرض 0% و 100% سبک پیش‌فرض عنصر را تنظیم می‌کند. این کوتاه‌نویسی برای انیمیشن‌هایی است که متناوب می‌شوند و نیاز به شروع و پایان در همان موقعیت دارند. این یک راه عالی برای بیان انیمیشن های متناوب بی نهایت است.

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

اسکرین شات لیست در فضای سه بعدی چرخانده شد تا پرسپکتیو نشان داده شود و هر مورد لیست دیگر گلوله ای ندارد.

استایل دادن به عناصر <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;
}

اسکرین شات از لیست دکمه ها در پرسپکتیو سه بعدی، این بار با دکمه های مدل دار.

عناصر شبه دکمه

حاشیه های دکمه حاشیه های سنتی نیستند، آنها شبه عناصر موقعیت مطلق با حاشیه هستند.

نماگرفت پانل عناصر ابزار توسعه دهنده کروم با دکمه ای که دارای عناصر ::قبل و ::بعد است.

این عناصر در نمایش چشم انداز سه بعدی ایجاد شده بسیار مهم هستند. یکی از این شبه عناصر از دکمه دور می‌شود و یکی به کاربر نزدیک‌تر می‌شود. این اثر بیشتر در دکمه های بالا و پایین قابل مشاهده است.

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

سبک های تبدیل سه بعدی

transform-style زیر روی preserve-3d تنظیم شده است تا کودکان بتوانند خود را روی محور z قرار دهند. transform روی ویژگی --distance custom تنظیم شده است که با شناور و فوکوس افزایش می یابد.

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

سبک های انیمیشن شرطی

اگر کاربر با حرکت خوب باشد، دکمه به مرورگر اشاره می کند که ویژگی transform باید برای تغییر آماده باشد و یک انتقال برای ویژگی های transform و background-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 واقعاً منظم بود. عناصر بالا و پایین جلوه را به شیوه ای ظریف و زیبا نشان می دهند.

پیشرفت های کوچک با جاوا اسکریپت

این رابط در حال حاضر از صفحه کلید، صفحه‌خوان‌ها، گیم‌پدها، لمسی و ماوس قابل استفاده است، اما می‌توانیم برخی از لمس‌های سبک جاوا اسکریپت را برای سهولت در چند حالت اضافه کنیم.

پشتیبانی از کلیدهای جهت دار

کلید تب روش خوبی برای پیمایش در منو است، اما من انتظار دارم که صفحه جهت یا جوی استیک ها فوکوس را بر روی گیم پد حرکت دهند. کتابخانه Roving-ux که اغلب برای رابط‌های GUI Challenge استفاده می‌شود، کلیدهای جهت‌نما را برای ما مدیریت می‌کند. کد زیر به کتابخانه می گوید که فوکوس را در .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)'
)

در مرحله بعد، ما به تابعی نیاز داریم که موقعیت‌های x و y ماوس را بپذیرد و مقداری را برگرداند که بتوانیم از آن برای چرخاندن کارت استفاده کنیم. تابع زیر از موقعیت ماوس برای تعیین اینکه در کدام سمت جعبه قرار دارد و به چه میزان است استفاده می کند. دلتا از تابع برگردانده می شود.

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 تقسیم کردم تا دلتا را صاف کنم و پیچش کمتری داشته باشد، ممکن است راه بهتری برای این کار وجود داشته باشد. اگر از ابتدا به خاطر داشته باشید، ما props --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> دارای یک سبک !important برای writing-mode در شیوه نامه عامل کاربر هستند. این بدان معناست که HTML منوی بازی باید تغییر کند تا طرح مورد نظر را در خود جای دهد. تغییر فهرست دکمه‌ها به فهرستی از پیوندها، ویژگی‌های منطقی را قادر می‌سازد تا جهت منو را تغییر دهند، زیرا عناصر <a> دارای سبک !important مرورگر نیستند.

نتیجه گیری

اکنون که می دانید من چگونه این کار را انجام دادم، چگونه می خواهید‽🙂 آیا می توانید تعامل شتاب سنج را به منو اضافه کنید، بنابراین کاشی کاری تلفن شما منو را می چرخاند؟ آیا می توانیم تجربه بدون حرکت را بهبود ببخشیم؟

بیایید رویکردهایمان را متنوع کنیم و همه راه‌های ساخت در وب را بیاموزیم. یک نسخه نمایشی ایجاد کنید، پیوندها را برای من توییت کنید ، و من آن را به بخش ریمیکس های انجمن در زیر اضافه می کنم!

ریمیکس های انجمن

هنوز چیزی برای دیدن اینجا وجود ندارد!