بازی دایناسور Chrome را با دسته بازی انجام دهید

با نحوه استفاده از Gamepad API برای ارتقای بازی‌های تحت وب خود به سطح بعدی آشنا شوید.

سورپریز پنهان صفحه آفلاین Chrome یکی از رازهایی است که در تاریخ به‌خوبی حفظ نشده است ([citation needed]، اما این ادعا برای تأثیر دراماتیک مطرح شده است). اگر کلید فاصله را فشار دهید یا در دستگاه‌های همراه روی دایناسور تک‌ضرب بزنید، صفحه آفلاین به بازی آرکید قابل‌بازی تبدیل می‌شود. شاید بدانید که برای بازی کردن واقعاً لازم نیست آفلاین شوید: در Chrome، می‌توانید به about://dino بروید، یا اگر به فناوری علاقه دارید، به about://network-error/-106 بروید. اما آیا می‌دانستید که هر ماه ۲۷۰ میلیون بازی دایناسور Chrome انجام می‌شود؟

صفحه آفلاین Chrome با بازی دایناسور Chrome.
برای بازی کردن، کلید فاصله را فشار دهید!

واقعیت دیگری که احتمالاً دانستن آن مفیدتر است و ممکن است از آن آگاه نباشید این است که در حالت بازی آرکید می‌توانید بازی را با دسته بازی انجام دهید. پشتیبانی از دسته بازی تقریباً یک سال پیش از زمان نگارش این مطلب در یک ثبت توسط Reilly Grant اضافه شد. همان‌طور که می‌بینید، این بازی، درست مثل بقیه پروژه Chromium، کاملاً متن‌باز است. در این پست، می‌خواهم به شما نشان دهم که چگونه از Gamepad API استفاده کنید.

استفاده از Gamepad API

تشخیص ویژگی و پشتیبانی مرورگر

‫Gamepad API در سراسر جهان از پشتیبانی مرورگر عالی در رایانه و تلفن همراه برخوردار است. بااستفاده از قطعه کد زیر می‌توانید تشخیص دهید که Gamepad API پشتیبانی می‌شود یا نه:

if ('getGamepads' in navigator) {
  // The API is supported!
}

نحوه نمایش دسته بازی در مرورگر

مرورگر دسته‌های بازی را به‌صورت اشیاء Gamepad نمایش می‌دهد. Gamepad دارای ویژگی‌های زیر است:

  • id: رشته شناسایی برای دسته بازی. این رشته نمانام یا سبک دسته بازی متصل‌شده را شناسایی می‌کند.
  • displayId: VRDisplay.displayId VRDisplay مرتبط (درصورت وجود).
  • index: نمایه دسته بازی در مرورگر.
  • connected: نشان می‌دهد که آیا دسته بازی هنوز به سیستم متصل است یا نه.
  • hand: یک enum که مشخص می‌کند کنترل‌کننده در کدام دست نگه داشته شده است یا به احتمال زیاد در کدام دست نگه داشته می‌شود.
  • timestamp: آخرین باری که داده‌های این دسته بازی به‌روزرسانی شد.
  • ‫mapping: نگاشت دکمه و محورهای مورداستفاده برای این دستگاه، "standard" یا "xr-standard".
  • pose: شیء GamepadPose که اطلاعات حالت مرتبط با کنترل‌کننده WebVR را نشان می‌دهد.
  • axes: آرایه‌ای از مقادیر برای همه محورهای دسته بازی، که به‌صورت خطی در محدوده -1.0–1.0 نرمال‌سازی شده است.
  • buttons: آرایه‌ای از وضعیت‌های دکمه برای همه دکمه‌های دسته بازی.

توجه داشته باشید که دکمه‌ها می‌توانند دیجیتال (فشرده‌شده یا فشرده‌نشده) یا آنالوگ (برای مثال، ۷۸٪ فشرده‌شده) باشند. به‌همین دلیل است که دکمه‌ها به‌عنوان اشیاء GamepadButton با ویژگی‌های زیر گزارش می‌شوند:

  • pressed: وضعیت فشرده شدن دکمه (true اگر دکمه فشرده شده باشد، و false اگر فشرده نشده باشد.
  • touched: وضعیت لمس‌شده دکمه. اگر دکمه قادر به تشخیص لمس باشد، این ویژگی درصورت لمس شدن دکمه true و درغیراین‌صورت false است.
  • value: برای دکمه‌هایی که حسگر آنالوگ دارند، این ویژگی نشان‌دهنده میزان فشار دادن دکمه است که به‌صورت خطی در محدوده 0.0 تا 1.0 نرمال‌سازی شده است.
  • hapticActuators: آرایه‌ای حاوی GamepadHapticActuator شیء که هرکدام نشان‌دهنده سخت‌افزار بازخورد لمسی موجود در کنترل‌کننده است.

مورد دیگری که ممکن است با آن مواجه شوید، بسته به مرورگر و دسته بازی‌ای که دارید، ویژگی vibrationActuator است. این ویژگی دو نوع جلوه لرزش را امکان‌پذیر می‌کند:

  • Dual-Rumble: جلوه بازخورد لمسی که توسط دو محرک جرم دوار گریز از مرکز تولید می‌شود، یکی در هر دسته از دسته بازی.
  • Trigger-Rumble: جلوه بازخورد لمسی که توسط دو موتور مستقل تولید می‌شود و هر موتور در یکی از ماشه‌های دسته بازی قرار دارد.

نمای کلی شماتیک زیر که مستقیماً از مشخصات گرفته شده است، نگاشت و چیدمان دکمه‌ها و محورها را در یک دسته بازی عمومی نشان می‌دهد.

نمای کلی شماتیک از نگاشت دکمه‌ها و محورهای یک دسته بازی معمولی.
نمایش تصویری چیدمان دسته بازی استاندارد (منبع).

اعلان هنگام متصل شدن دسته بازی

برای اینکه بدانید دسته بازی چه زمانی متصل می‌شود، به رویداد gamepadconnected که در شیء window راه‌اندازی می‌شود گوش دهید. وقتی کاربر دسته بازی را متصل می‌کند، که می‌تواند ازطریق USB یا بلوتوث انجام شود، GamepadEvent فعال می‌شود که جزئیات دسته بازی را در ویژگی gamepad با نام مناسب دارد. در زیر، می‌توانید نمونه‌ای از کنترلر Xbox 360 را ببینید که در اطرافم بود (بله، من به بازی‌های قدیمی علاقه دارم).

window.addEventListener('gamepadconnected', (event) => {
  console.log('✅ 🎮 A gamepad was connected:', event.gamepad);
  /*
    gamepad: Gamepad
    axes: (4) [0, 0, 0, 0]
    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
    connected: true
    id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
    index: 0
    mapping: "standard"
    timestamp: 6563054.284999998
    vibrationActuator: GamepadHapticActuator {type: "dual-rumble"}
  */
});

اعلان هنگام قطع شدن اتصال دسته بازی

اطلاع‌رسانی درباره قطع اتصال دسته بازی مشابه روش شناسایی اتصال‌ها انجام می‌شود. این بار برنامه به رویداد gamepaddisconnected گوش می‌کند. توجه کنید که در مثال زیر وقتی کنترلر Xbox 360 را از برق می‌کشم، connected به false تبدیل می‌شود.

window.addEventListener('gamepaddisconnected', (event) => {
  console.log('❌ 🎮 A gamepad was disconnected:', event.gamepad);
  /*
    gamepad: Gamepad
    axes: (4) [0, 0, 0, 0]
    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
    connected: false
    id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
    index: 0
    mapping: "standard"
    timestamp: 6563054.284999998
    vibrationActuator: null
  */
});

دسته بازی در حلقه بازی شما

دسترسی به دسته بازی با فراخوانی navigator.getGamepads() شروع می‌شود که آرایه‌ای با Gamepad مورد را برمی‌گرداند. آرایه در Chrome همیشه طول ثابتی از چهار مورد دارد. اگر هیچ دسته بازی‌ای متصل نباشد یا تعداد دسته‌های بازی متصل کمتر از چهار عدد باشد، ممکن است موردی فقط null باشد. همیشه مطمئن شوید که همه موارد آرایه را بررسی می‌کنید و توجه داشته باشید که دسته‌های بازی جایگاه خود را «به‌خاطر می‌آورند» و ممکن است همیشه در اولین جایگاه دردسترس حضور نداشته باشند.

// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]

اگر یک یا چند دسته بازی متصل است، اما navigator.getGamepads() همچنان null مورد را گزارش می‌کند، ممکن است لازم باشد هر دسته بازی را با فشار دادن یکی از دکمه‌های آن «بیدار» کنید. سپس می‌توانید وضعیت‌های دسته بازی را در حلقه بازی‌تان نظرسنجی کنید، همان‌طور که در کد زیر نشان داده شده است.

const pollGamepads = () => {
  // Always call `navigator.getGamepads()` inside of
  // the game loop, not outside.
  const gamepads = navigator.getGamepads();
  for (const gamepad of gamepads) {
    // Disregard empty slots.
    if (!gamepad) {
      continue;
    }
    // Process the gamepad state.
    console.log(gamepad);
  }
  // Call yourself upon the next animation frame.
  // (Typically this happens every 60 times per second.)
  window.requestAnimationFrame(pollGamepads);
};
// Kick off the initial game loop iteration.
pollGamepads();

محرک لرزش

ویژگی vibrationActuator یک شیء GamepadHapticActuator را برمی‌گرداند که با پیکربندی موتورها یا سایر محرک‌هایی که می‌توانند نیرویی را برای بازخورد لمسی اعمال کنند مطابقت دارد. جلوه‌های لمسی را می‌توان با فراخوانی Gamepad.vibrationActuator.playEffect() پخش کرد. تنها انواع جلوه معتبر 'dual-rumble' و 'trigger-rumble' هستند.

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

if (gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
  // Trigger rumble supported.
} else if (gamepad.vibrationActuator.effects.includes('dual-rumble')) {
  // Dual rumble supported.
} else {
  // Rumble effects aren't supported.
}

لرزش دوگانه

لرزش دوگانه پیکربندی لمسی‌ای را توصیف می‌کند که در آن یک موتور لرزشی با جرم چرخان گریز از مرکز در هر دسته از دسته بازی استاندارد وجود دارد. در این پیکربندی، هریک از موتورها می‌تواند کل دسته بازی را بلرزاند. این دو جرم نابرابر هستند تا بتوان اثرات هرکدام را با هم ترکیب کرد و اثرات لمسی پیچیده‌تری ایجاد کرد. جلوه‌های لرزش دوگانه با چهار پارامتر تعریف می‌شوند:

  • ‫duration: مدت‌زمان اثر لرزش را برحسب میلی‌ثانیه تنظیم می‌کند.
  • startDelay: مدت زمان تأخیر تا شروع لرزش را تنظیم می‌کند.
  • strongMagnitude و weakMagnitude: سطح شدت لرزش را برای موتورهای جرم دوار گریز از مرکز سنگین‌تر و سبک‌تر تنظیم می‌کند، که به محدوده 0.0–1.0 نرمال‌سازی شده است.
// This assumes a `Gamepad` as the value of the `gamepad` variable.
const dualRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
  if (!('vibrationActuator' in gamepad)) {
    return;
  }
  gamepad.vibrationActuator.playEffect('dual-rumble', {
    // Start delay in ms.
    startDelay: delay,
    // Duration in ms.
    duration: duration,
    // The magnitude of the weak actuator (between 0 and 1).
    weakMagnitude: weak,
    // The magnitude of the strong actuator (between 0 and 1).
    strongMagnitude: strong,
  });
};

لرزش ماشه

لرزش ماشه اثر بازخورد لمسی است که توسط دو موتور مستقل تولید می‌شود و هر موتور در یکی از ماشه‌های دسته بازی قرار دارد.

// This assumes a `Gamepad` as the value of the `gamepad` variable.
const triggerRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
  if (!('vibrationActuator' in gamepad)) {
    return;
  }
  // Feature detection.
  if (!('effects' in gamepad.vibrationActuator) || !gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
    return;
  }
  gamepad.vibrationActuator.playEffect('trigger-rumble', {
    // Duration in ms.
    duration: duration,
    // The left trigger (between 0 and 1).
    leftTrigger: leftTrigger,
    // The right trigger (between 0 and 1).
    rightTrigger: rightTrigger,
  });
};

ادغام با خط‌مشی اجازه‌ها

مشخصات Gamepad API یک ویژگی تحت‌کنترل خط‌مشی را تعریف می‌کند که با رشته "gamepad" شناسایی می‌شود. allowlist پیش‌فرض آن "self" است. خط‌مشی اجازه‌های سند تعیین می‌کند آیا محتوای موجود در آن سند اجازه دسترسی به navigator.getGamepads() را دارد یا نه. اگر در سندی غیرفعال شود، هیچ محتوایی در سند اجازه استفاده از navigator.getGamepads() را نخواهد داشت، و رویدادهای gamepadconnected و gamepaddisconnected نیز اجرا نخواهند شد.

<iframe src="index.html" allow="gamepad"></iframe>

پخش نمایشی

نسخه نمایشی آزمایشگر دسته بازی در مثال زیر جاسازی شده است. کد منبع در GitHub دردسترس است. با متصل کردن دسته بازی ازطریق USB یا بلوتوث و فشار دادن هرکدام از دکمه‌ها یا حرکت دادن هرکدام از محورهای آن، نسخه نمایشی را امتحان کنید.

جایزه: بازی کردن دایناسور Chrome در web.dev

می‌توانید دایناسور Chrome را با دسته بازی در همین سایت بازی کنید. کد منبع در GitHub دردسترس است. پیاده‌سازی نظرسنجی دسته بازی را در trex-runner.js بررسی کنید و ببینید چگونه فشردن کلیدها را شبیه‌سازی می‌کند.

برای اینکه نسخه نمایشی دسته بازی دایناسور Chrome کار کند، بازی دایناسور Chrome را از پروژه اصلی Chromium جدا کردم (با به‌روزرسانی تلاش قبلی Arnelle Ballane)، آن را در یک سایت مستقل قرار دادم، پیاده‌سازی میانای برنامه‌سازی کاربردی دسته بازی موجود را با افزودن جلوه‌های جاخالی دادن و لرزش گسترش دادم، حالت تمام‌صفحه ایجاد کردم، و Mehul Satardekar در پیاده‌سازی حالت تاریک مشارکت کرد. بازی خوش بگذرد!

تصدیق‌نامه‌ها

این سند را فرانسوا بوفور و جو مدلی بازبینی کرده‌اند. مشخصات Gamepad API توسط استیو آگوستون، جیمز هولیر، و مت رینولدز ویرایش شده است. ویراستاران مشخصات فنی قبلی عبارتند از: براندون جونز، اسکات گراهام، و تد میلچارک. مشخصات «افزونه‌های دسته بازی» توسط براندون جونز ویرایش شده است.