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

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

Flexbox, कंटेनर लेआउट को मैनेज कर सकता है. 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 के एलिमेंट पैनल का स्क्रीनशॉट, जिसमें एक बटन दिख रहा है. इसमें ::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 और 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 के कुछ लाइट टच जोड़ सकते हैं.

ऐरो बटन का इस्तेमाल करना

मेन्यू में नेविगेट करने के लिए, Tab बटन का इस्तेमाल किया जा सकता है. हालांकि, मुझे उम्मीद है कि गेमपैड पर फ़ोकस को डायरेक्शन पैड या जॉयस्टिक से भी ले जाया जा सकता है. आम तौर पर, जीयूआई चैलेंज इंटरफ़ेस के लिए इस्तेमाल की जाने वाली roving-ux लाइब्रेरी, ऐरो बटन को मैनेज करेगी. नीचे दिया गया कोड, लाइब्रेरी को .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 स्टाइल नहीं होती.

नतीजा

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

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

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

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