3D गेम मेन्यू कॉम्पोनेंट बनाना

रिस्पॉन्सिव, अडैप्टिव, और ऐक्सेस किया जा सकने वाला 3D गेम मेन्यू बनाने के बारे में बुनियादी जानकारी.

इस पोस्ट में, मुझे 3D गेम मेन्यू कॉम्पोनेंट बनाने के तरीके के बारे में अपनी राय शेयर करनी है. डेमो आज़माएं.

डेमो

अगर आपको वीडियो देखना ज़्यादा पसंद है, तो इस पोस्ट का YouTube वर्शन यहां दिया गया है:

खास जानकारी

वीडियो गेम में अक्सर उपयोगकर्ताओं को क्रिएटिव और अलग तरह का मेन्यू दिखाया जाता है. यह ऐनिमेटेड होता है और 3D स्पेस में दिखता है. यह नई एआर/वीआर गेम में लोकप्रिय है, ताकि मेन्यू को स्पेस में तैरता हुआ दिखाया जा सके. आज हम इस इफ़ेक्ट की ज़रूरी चीज़ों को फिर से बनाएंगे. हालांकि, इसमें हम अडैप्टिव कलर स्कीम और उन लोगों के लिए सुविधाएं जोड़ेंगे जिन्हें कम मोशन पसंद है.

एचटीएमएल

गेम मेन्यू, बटन की एक सूची होती है. इसे एचटीएमएल में इस तरह से दिखाया जा सकता है:

<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 या सीएसएस के बिना काम करेगी.

बुलेट वाली एक सामान्य सूची, जिसमें आइटम के तौर पर सामान्य बटन दिए गए हैं.

सीएसएस

बटन की सूची को स्टाइल करने के लिए, ये चरण पूरे करने होंगे:

  1. कस्टम प्रॉपर्टी सेट अप करना.
  2. फ़्लेक्सबॉक्स लेआउट.
  3. सजावटी सूडो-एलिमेंट वाला कस्टम बटन.
  4. 3D स्पेस में एलिमेंट जोड़ना.

कस्टम प्रॉपर्टी के बारे में खास जानकारी

कस्टम प्रॉपर्टी की मदद से, वैल्यू को अलग-अलग किया जा सकता है. इसके लिए, रैंडम दिखने वाली वैल्यू को काम के नाम दिए जाते हैं. इससे बार-बार इस्तेमाल होने वाले कोड से बचा जा सकता है और बच्चों के बीच वैल्यू शेयर की जा सकती हैं.

यहां सीएसएस वैरिएबल के तौर पर सेव की गई मीडिया क्वेरी दी गई हैं. इन्हें कस्टम मीडिया भी कहा जाता है. ये ग्लोबल हैं और इनका इस्तेमाल अलग-अलग सिलेक्टर में किया जाएगा, ताकि कोड को छोटा और पढ़ने में आसान बनाया जा सके. गेम मेन्यू कॉम्पोनेंट, डिसप्ले की मोशन प्रीफ़रेंस, सिस्टम की कलर स्कीम, और कलर रेंज की सुविधाएं इस्तेमाल करता है.

@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 फ़्लोटिंग कार्ड के लिए भी ज़िम्मेदार होता है. ऐसा करने का तरीका यहां बताया गया है.

बटन ग्रुप का लेआउट

फ़्लेक्सबॉक्स, कंटेनर लेआउट को मैनेज कर सकता है. flex-direction का इस्तेमाल करके, फ़्लेक्स की डिफ़ॉल्ट दिशा को लाइनों से कॉलम में बदलें. साथ ही, यह पक्का करें कि हर आइटम का साइज़ उसके कॉन्टेंट के हिसाब से हो. इसके लिए, align-items के लिए stretch को 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;
  }
}

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

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 पर्सपेक्टिव में बटन की सूची का स्क्रीनशॉट. इस बार, स्टाइल वाले बटन दिखाए गए हैं.

बटन के छद्म एलिमेंट

बटन के बॉर्डर, पारंपरिक बॉर्डर नहीं हैं. ये बॉर्डर वाले ऐब्सलूट पोज़िशन वाले सूडो-एलिमेंट हैं.

Chrome Devtools के Elements पैनल का स्क्रीनशॉट. इसमें एक बटन दिखाया गया है, जिसमें ::before और ::after एलिमेंट मौजूद हैं.

ये एलिमेंट, 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-style को preserve-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));
  }
}

ऐनिमेशन की स्टाइल को शर्तों के हिसाब से लागू करना

अगर उपयोगकर्ता को मोशन से कोई समस्या नहीं है, तो बटन ब्राउज़र को यह सूचना देता है कि 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 पर सेट करें. यहां दिए गए कोड के उदाहरण में दिखाया गया सिलेक्टर, यह जांच करता है कि बटन पर ऐसे डिवाइस से कर्सर घुमाया जा रहा है या फ़ोकस किया जा रहा है जिसे फ़ोकस इंडिकेटर दिखना चाहिए. साथ ही, यह भी जांच करता है कि बटन चालू नहीं किया जा रहा है. ऐसा होने पर, सीएसएस इन कामों को पूरा करती है:

  • कर्सर घुमाने पर दिखने वाले बैकग्राउंड का रंग लागू करें.
  • दूरी बढ़ाएं .
  • बाउंस ईज़ इफ़ेक्ट जोड़ें.
  • स्यूडो-एलिमेंट के ट्रांज़िशन को स्टैगर्ड करें.
.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 के कुछ हल्के-फुल्के टच जोड़ सकते हैं.

ऐरो बटन काम करते हैं

टैब कुंजी, मेन्यू को नेविगेट करने का एक अच्छा तरीका है. हालांकि, मुझे उम्मीद है कि डायरेक्शनल पैड या जॉयस्टिक, गेमपैड पर फ़ोकस को मूव करेंगे. roving-ux लाइब्रेरी का इस्तेमाल अक्सर GUI चैलेंज इंटरफ़ेस के लिए किया जाता है. यह हमारे लिए ऐरो की को मैनेज करेगी. नीचे दिए गए कोड से लाइब्रेरी को यह पता चलता है कि फ़ोकस को .threeD-button-set के अंदर ट्रैप करना है और फ़ोकस को बटन के चाइल्ड एलिमेंट पर फ़ॉरवर्ड करना है.

import {rovingIndex} from 'roving-ux'

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

माउस के हिसाब से पैरलैक्स इफ़ेक्ट

माउस को ट्रैक करने और मेन्यू को झुकाने की सुविधा, एआर और वीआर वीडियो गेम इंटरफ़ेस की नकल करने के लिए बनाई गई है. इनमें माउस की जगह वर्चुअल पॉइंटर का इस्तेमाल किया जाता है. जब एलिमेंट, पॉइंटर के बारे में ज़्यादा जानकारी देते हैं, तो यह मज़ेदार हो सकता है.

यह एक छोटी सी अतिरिक्त सुविधा है. इसलिए, हम इंटरैक्शन को उपयोगकर्ता की मोशन की प्राथमिकता की क्वेरी के पीछे रखेंगे. इसके अलावा, सेटअप के दौरान बटन की सूची वाले कॉम्पोनेंट को 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 से भाग दिया है. इसे बेहतर तरीके से किया जा सकता है. अगर आपको याद हो, तो हमने clamp() फ़ंक्शन के बीच में --x और --y प्रॉप्स रखे थे. इससे माउस की पोज़िशन की वजह से कार्ड को ऐसी पोज़िशन में घूमने से रोका जा सकता है जिसे पढ़ा न जा सके.

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 स्टाइल होती है. इसका मतलब है कि गेम मेन्यू के एचटीएमएल को बदलकर, पसंद के मुताबिक डिज़ाइन में बदलना होगा. बटन की सूची को लिंक की सूची में बदलने से, लॉजिकल प्रॉपर्टी मेन्यू की दिशा बदल सकती हैं. ऐसा इसलिए, क्योंकि <a> एलिमेंट में ब्राउज़र से मिली !important स्टाइल नहीं होती.

नतीजा

अब आपको पता चल गया है कि मैंने यह कैसे किया. अब आप इसे कैसे करेंगे‽ 🙂 क्या मेन्यू में ऐक्सिलरोमीटर इंटरैक्शन जोड़ा जा सकता है, ताकि फ़ोन को घुमाने पर मेन्यू भी घूम जाए? क्या हम मोशन न होने की स्थिति में मिलने वाले अनुभव को बेहतर बना सकते हैं?

आइए, हम अपने तरीकों में विविधता लाएं और वेब पर काॅन्टेंट पोस्ट करने के सभी तरीके जानें. डेमो बनाएं और मुझे ट्वीट करें. इसके बाद, मैं इसे यहां कम्यूनिटी रीमिक्स सेक्शन में जोड़ दूंगा!

कम्यूनिटी रीमिक्स

अभी यहां देखने के लिए कुछ नहीं है!