پرش از موانع با Gamepad API

مقدمه

اجازه دهید تازه کارها صفحه کلید خود را برای بازی های ماجراجویی، نوک انگشتان ارزشمند و چندلمسی خود را برای برش میوه، و حسگرهای حرکتی جدید فانتزی خود را برای تظاهر به رقصیدن مانند مایکل جکسون نگه دارند. (اخبار: آنها نمی توانند.) اما شما متفاوت هستید. تو بهتری شما یک حرفه ای هستید. برای شما، بازی ها با یک گیم پد در دستان شما شروع و به پایان می رسد.

اما صبر کن اگر بخواهید از یک گیم پد در برنامه وب خود پشتیبانی کنید، خوش شانس نیستید؟ دیگر نه. API کاملاً جدید Gamepad به کمک می آید و به شما امکان می دهد از جاوا اسکریپت برای خواندن وضعیت هر کنترلر گیم پد متصل به رایانه خود استفاده کنید. آنقدر تازه است که هفته گذشته در کروم 21 قرار گرفت – و همچنین در آستانه پشتیبانی در فایرفاکس است (در حال حاضر در یک نسخه ویژه موجود است).

این زمان بسیار خوبی بود، زیرا اخیراً فرصتی برای استفاده از آن در Hurdles 2012 Google doodle داریم. این مقاله به طور مختصر توضیح می‌دهد که چگونه Gamepad API را به doodle اضافه کرده‌ایم و در طول فرآیند چه چیزهایی یاد گرفتیم.

Doodle Google Hurdles 2012
Doodle Google Hurdles 2012

تستر گیم پد

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

امروزه چه مرورگرهایی از آن پشتیبانی می کنند؟

پشتیبانی مرورگر

  • کروم: 21.
  • لبه: 12.
  • فایرفاکس: 29.
  • سافاری: 10.1.

منبع

از چه گیم پدهایی می توان استفاده کرد؟

به طور کلی، هر گیم پد مدرنی که به صورت بومی توسط سیستم شما پشتیبانی می شود باید کار کند. ما گیم‌پدهای مختلفی را از کنترل‌کننده‌های USB بی‌نام تجاری روی رایانه شخصی، از طریق گیم‌پدهای PlayStation 2 که از طریق دانگل به Mac متصل شده‌اند، تا کنترل‌کننده‌های بلوتوث جفت‌شده با نوت‌بوک Chrome OS آزمایش کردیم.

گیم پدها
گیم پدها

این عکس برخی از کنترل‌کننده‌هایی است که ما برای آزمایش doodle خود استفاده کردیم - "بله، مامان، این واقعاً همان کاری است که من در محل کار انجام می‌دهم." اگر کنترل‌کننده شما کار نمی‌کند، یا اگر کنترل‌ها به اشتباه نگاشت شده‌اند، لطفاً یک اشکال را در Chrome یا Firefox ثبت کنید. (لطفاً در جدیدترین نسخه هر مرورگر تست کنید تا مطمئن شوید که قبلاً رفع نشده است.)

ویژگی تشخیص Gamepad API<

به اندازه کافی آسان در کروم:

var gamepadSupportAvailable = !!navigator.webkitGetGamepads || !!navigator.webkitGamepads;

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

اما ما مطمئن هستیم که موقتی است. مدرنیزر بسیار عالی قبلاً در مورد Gamepad API به شما می گوید، بنابراین ما این را برای تمام نیازهای شناسایی فعلی و آینده شما توصیه می کنیم:

var gamepadSupportAvailable = Modernizr.gamepads;

پیدا کردن گیم پدهای متصل

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

با این حال، هنگامی که آن مانع را برطرف کردید (با عرض پوزش…)، با این حال، موارد بیشتری در انتظار هستند.

نظرسنجی

اجرای کروم از API یک تابع - navigator.webkitGetGamepads() را نشان می دهد - که می توانید از آن برای دریافت لیستی از تمام گیم پدهایی که در حال حاضر به سیستم وصل شده اند، همراه با وضعیت فعلی آنها (دکمه ها + چوب ها) استفاده کنید. اولین گیم پد متصل به عنوان اولین ورودی در آرایه برگردانده می شود و به همین ترتیب.

(این فراخوانی عملکرد اخیراً جایگزین آرایه‌ای شد که می‌توانید مستقیماً به آن دسترسی داشته باشید - navigator.webkitGamepads[] . از اوایل آگوست 2012، دسترسی به این آرایه همچنان در Chrome 21 ضروری است، در حالی که فراخوانی تابع در Chrome 22 و جدیدتر کار می‌کند. فراخوانی تابع روش توصیه شده برای استفاده از API است و به آرامی به همه مرورگرهای Chrome نصب شده می‌رسد.)

بخشی از این مشخصات که تاکنون اجرا شده است، از شما می‌خواهد که به‌جای شلیک وقایع هنگام تغییر، وضعیت گیم‌پدهای متصل را به‌طور مداوم بررسی کنید (و در صورت لزوم آن را با حالت قبلی مقایسه کنید). ما به () requestAnimationFrame متکی بودیم تا نظرسنجی را به کارآمدترین و باتری پسندترین راه تنظیم کنیم. برای doodle ما، حتی اگر از قبل یک حلقه requestAnimationFrame() برای پشتیبانی از انیمیشن‌ها داریم، یک حلقه کاملاً مجزا ایجاد کردیم – کدنویسی آن ساده‌تر بود و به هیچ وجه نباید بر عملکرد تأثیر بگذارد.

این کد از تستر است:

/**
 * Starts a polling loop to check for gamepad state.
 */
startPolling: function() {
    // Don't accidentally start a second loop, man.
    if (!gamepadSupport.ticking) {
    gamepadSupport.ticking = true;
    gamepadSupport.tick();
    }
},

/**
 * Stops a polling loop by setting a flag which will prevent the next
 * requestAnimationFrame() from being scheduled.
 */
stopPolling: function() {
    gamepadSupport.ticking = false;
},

/**
 * A function called with each requestAnimationFrame(). Polls the gamepad
 * status and schedules another poll.
 */
tick: function() {
    gamepadSupport.pollStatus();
    gamepadSupport.scheduleNextTick();
},

scheduleNextTick: function() {
    // Only schedule the next frame if we haven't decided to stop via
    // stopPolling() before.
    if (gamepadSupport.ticking) {
    if (window.requestAnimationFrame) {
        window.requestAnimationFrame(gamepadSupport.tick);
    } else if (window.mozRequestAnimationFrame) {
        window.mozRequestAnimationFrame(gamepadSupport.tick);
    } else if (window.webkitRequestAnimationFrame) {
        window.webkitRequestAnimationFrame(gamepadSupport.tick);
    }
    // Note lack of setTimeout since all the browsers that support
    // Gamepad API are already supporting requestAnimationFrame().
    }
},

/**
 * Checks for the gamepad status. Monitors the necessary data and notices
 * the differences from previous state (buttons for Chrome/Firefox,
 * new connects/disconnects for Chrome). If differences are noticed, asks
 * to update the display accordingly. Should run as close to 60 frames per
 * second as possible.
 */
pollStatus: function() {
    // (Code goes here.)
},

اگر فقط به یک گیم پد اهمیت می دهید، دریافت داده های آن ممکن است به همین سادگی باشد:

var gamepad = navigator.webkitGetGamepads && navigator.webkitGetGamepads()[0];

اگر می‌خواهید کمی باهوش‌تر باشید یا از بیش از یک بازیکن به طور همزمان پشتیبانی کنید، باید چند خط کد دیگر اضافه کنید تا به موقعیت‌های پیچیده‌تر واکنش نشان دهید (دو یا چند گیم‌پد متصل هستند، برخی از آنها در اواسط راه قطع می‌شوند. و غیره). می‌توانید به کد منبع تستر ما، تابع pollGamepads() نگاه کنید تا یک رویکرد در مورد نحوه حل این مشکل را پیدا کنید.

رویدادها

فایرفاکس از روش جایگزین و بهتری استفاده می کند که در مشخصات API Gamepad توضیح داده شده است. به جای درخواست از شما برای نظرسنجی، دو رویداد - MozGamepadConnected و MozGamepadDisconnected - را نشان می دهد که هر زمان که یک گیم پد وصل می شود (یا به طور دقیق تر، وصل می شود و با فشار دادن هر یک از دکمه های آن "اعلام می شود") یا قطع می شود، فعال می شوند. شیء گیم پد که همچنان وضعیت آینده خود را منعکس می کند به عنوان پارامتر .gamepad شی رویداد ارسال می شود.

از کد منبع تستر:

/**
 * React to the gamepad being connected. Today, this will only be executed
 * on Firefox.
 */
onGamepadConnect: function(event) {
    // Add the new gamepad on the list of gamepads to look after.
    gamepadSupport.gamepads.push(event.gamepad);

    // Start the polling loop to monitor button changes.
    gamepadSupport.startPolling();

    // Ask the tester to update the screen to show more gamepads.
    tester.updateGamepads(gamepadSupport.gamepads);
},

خلاصه

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

/**
 * Initialize support for Gamepad API.
 */
init: function() {
    // As of writing, it seems impossible to detect Gamepad API support
    // in Firefox, hence we need to hardcode it in the third clause.
    // (The preceding two clauses are for Chrome.)
    var gamepadSupportAvailable = !!navigator.webkitGetGamepads ||
        !!navigator.webkitGamepads ||
        (navigator.userAgent.indexOf('Firefox/') != -1);

    if (!gamepadSupportAvailable) {
    // It doesn't seem Gamepad API is available – show a message telling
    // the visitor about it.
    tester.showNotSupported();
    } else {
    // Firefox supports the connect/disconnect event, so we attach event
    // handlers to those.
    window.addEventListener('MozGamepadConnected',
                            gamepadSupport.onGamepadConnect, false);
    window.addEventListener('MozGamepadDisconnected',
                            gamepadSupport.onGamepadDisconnect, false);

    // Since Chrome only supports polling, we initiate polling loop straight
    // away. For Firefox, we will only do it if we get a connect event.
    if (!!navigator.webkitGamepads || !!navigator.webkitGetGamepads) {
        gamepadSupport.startPolling();
    }
    }
},

اطلاعات گیم پد

هر گیم پد متصل به سیستم با یک شی شبیه به این نمایش داده می شود:

id: "PLAYSTATION(R)3 Controller (STANDARD GAMEPAD Vendor: 054c Product: 0268)"
index: 1
timestamp: 18395424738498
buttons: Array[8]
    0: 0
    1: 0
    2: 1
    3: 0
    4: 0
    5: 0
    6: 0.03291
    7: 0
axes: Array[4]
    0: -0.01176
    1: 0.01961
    2: -0.00392
    3: -0.01176

اطلاعات اولیه

چند فیلد برتر متادیتا ساده هستند:

  • id : شرح متنی گیم پد
  • index : یک عدد صحیح مفید برای تشخیص گیم پدهای مختلف متصل به یک کامپیوتر از هم
  • timestamp : مهر زمانی آخرین به‌روزرسانی وضعیت دکمه/محورها (در حال حاضر فقط در Chrome پشتیبانی می‌شود)

دکمه ها و چوب ها

گیم پدهای امروزی دقیقاً همان چیزی نیستند که پدربزرگ شما ممکن است برای نجات شاهزاده خانم در قلعه اشتباهی استفاده کرده باشد – آنها معمولاً حداقل شانزده دکمه جداگانه (بعضی مجزا و برخی آنالوگ) به اضافه دو چوب آنالوگ دارند. Gamepad API به شما در مورد تمام دکمه ها و استیک های آنالوگ که توسط سیستم عامل گزارش می شود، می گوید.

هنگامی که وضعیت فعلی را در شی صفحه بازی به دست آوردید، می توانید از طریق .buttons[] و چوب ها از طریق آرایه های .axes[] به دکمه ها دسترسی داشته باشید. در اینجا یک خلاصه تصویری از آنچه که آنها با آن مطابقت دارند آمده است:

نمودار گیم پد
نمودار گیم پد

این مشخصات از مرورگر می‌خواهد که 16 دکمه و چهار محور اول را به صورت زیر ترسیم کند:

gamepad.BUTTONS = {
    FACE_1: 0, // Face (main) buttons
    FACE_2: 1,
    FACE_3: 2,
    FACE_4: 3,
    LEFT_SHOULDER: 4, // Top shoulder buttons
    RIGHT_SHOULDER: 5,
    LEFT_SHOULDER_BOTTOM: 6, // Bottom shoulder buttons
    RIGHT_SHOULDER_BOTTOM: 7,
    SELECT: 8,
    START: 9,
    LEFT_ANALOGUE_STICK: 10, // Analogue sticks (if depressible)
    RIGHT_ANALOGUE_STICK: 11,
    PAD_TOP: 12, // Directional (discrete) pad
    PAD_BOTTOM: 13,
    PAD_LEFT: 14,
    PAD_RIGHT: 15
};

gamepad.AXES = {
    LEFT_ANALOGUE_HOR: 0,
    LEFT_ANALOGUE_VERT: 1,
    RIGHT_ANALOGUE_HOR: 2,
    RIGHT_ANALOGUE_VERT: 3
};

دکمه ها و محورهای اضافی به موارد بالا اضافه می شوند. لطفاً توجه داشته باشید که نه شانزده دکمه و نه چهار محور تضمین شده نیستند - آماده باشید که برخی از آنها به سادگی تعریف نشده باشند.

دکمه ها می توانند مقادیری از 0.0 (فشار نشده) تا 1.0 (فشار کامل) داشته باشند. محورها از -1.0 (کاملاً چپ یا بالا) از 0.0 (مرکز) به 1.0 (کاملاً راست یا پایین) می روند.

آنالوگ یا گسسته؟

ظاهراً، هر دکمه می‌تواند یک دکمه آنالوگ باشد - برای مثال، برای دکمه‌های شانه تا حدودی رایج است. بنابراین، بهتر است یک آستانه را به جای مقایسه صریح با 1.00 تنظیم کنید (اگر یک دکمه آنالوگ کمی کثیف باشد چه می‌شود؟ ممکن است هرگز به 1.00 نرسد). در doodle ما این کار را به این صورت انجام می دهیم:

gamepad.ANALOGUE_BUTTON_THRESHOLD = .5;

gamepad.buttonPressed_ = function(pad, buttonId) {
    return pad.buttons[buttonId] &&
            (pad.buttons[buttonId] > gamepad.ANALOGUE_BUTTON_THRESHOLD);
};

شما می توانید همین کار را انجام دهید تا استیک های آنالوگ را به جوی استیک دیجیتال تبدیل کنید. مطمئنا، همیشه پد دیجیتال (d-pad) وجود دارد، اما گیم پد شما ممکن است فاقد آن باشد. در اینجا کد ما برای رسیدگی به آن است:

gamepad.AXIS_THRESHOLD = .75;

gamepad.stickMoved_ = function(pad, axisId, negativeDirection) {
    if (typeof pad.axes[axisId] == 'undefined') {
    return false;
    } else if (negativeDirection) {
    return pad.axes[axisId] < -gamepad.AXIS_THRESHOLD;
    } else {
    return pad.axes[axisId] > gamepad.AXIS_THRESHOLD;
    }
};

فشار دادن دکمه ها و حرکات چوب

رویدادها

در برخی موارد، مانند یک بازی شبیه‌ساز پرواز، بررسی و واکنش مداوم به موقعیت‌های چوب یا فشار دادن دکمه‌ها منطقی‌تر است... اما برای چیزهایی مانند Doodle Hurdles 2012؟ ممکن است تعجب کنید: چرا باید دکمه‌ها را در هر فریم بررسی کنم؟ چرا نمی توانم رویدادهایی را مانند صفحه کلید یا ماوس بالا/پایین دریافت کنم؟

خبر خوب این است که شما می توانید. خبر بد - در آینده است. این در مشخصات است، اما هنوز در هیچ مرورگری اجرا نشده است.

نظرسنجی

در این بین، راه خروج شما مقایسه وضعیت فعلی و قبلی و فراخوانی توابع در صورت مشاهده تفاوت است. به عنوان مثال:

if (buttonPressed(pad, 0) != buttonPressed(oldPad, 0)) {
    buttonEvent(0, buttonPressed(pad, 0) ? 'down' : 'up');
}
for (var i in gamepadSupport.gamepads) {
    var gamepad = gamepadSupport.gamepads[i];

    // Don't do anything if the current timestamp is the same as previous
    // one, which means that the state of the gamepad hasn't changed.
    // This is only supported by Chrome right now, so the first check
    // makes sure we're not doing anything if the timestamps are empty
    // or undefined.
    if (gamepadSupport.prevTimestamps[i] &&
        (gamepad.timestamp == gamepadSupport.prevTimestamps[i])) {
    continue;
    }
    gamepadSupport.prevTimestamps[i] = gamepad.timestamp;

    gamepadSupport.updateDisplay(i);
}

اولین رویکرد صفحه کلید در Doodle Hurdles 2012

از آنجایی که بدون گیم‌پد، روش ورودی ترجیحی doodle امروزی ما صفحه‌کلید است، تصمیم گرفتیم که گیم‌پد نسبتاً از آن تقلید کند. این به معنای سه تصمیم بود:

  1. Doodle فقط به سه دکمه نیاز دارد – دو دکمه برای دویدن و یکی برای پریدن – اما گیم پد احتمالاً تعداد بیشتری از آن ها را خواهد داشت. بنابراین، ما تمام شانزده دکمه شناخته شده و دو چوب شناخته شده را به روشی که فکر می‌کردیم منطقی‌تر بود، بر روی آن سه عملکرد منطقی ترسیم کردیم، به طوری که افراد بتوانند با استفاده از: دکمه‌های A/B متناوب، دکمه‌های شانه‌ای متناوب، فشار دادن چپ/راست روی d. -پد، یا تاب خوردن به شدت به چپ و راست می‌چسبد (البته برخی از آن‌ها کارآمدتر از بقیه خواهند بود). به عنوان مثال:

    newState[gamepad.STATES.LEFT] =
        gamepad.buttonPressed_(pad, gamepad.BUTTONS.PAD_LEFT) ||
        gamepad.stickMoved_(pad, gamepad.AXES.LEFT_ANALOGUE_HOR, true) ||
        gamepad.stickMoved_(pad, gamepad.AXES.RIGHT_ANALOGUE_HOR, true),
    
    newState[gamepad.STATES.PRIMARY_BUTTON] =
        gamepad.buttonPressed_(pad, gamepad.BUTTONS.FACE_1) ||
        gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_SHOULDER) ||
        gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_SHOULDER_BOTTOM) ||
        gamepad.buttonPressed_(pad, gamepad.BUTTONS.SELECT) ||
        gamepad.buttonPressed_(pad, gamepad.BUTTONS.START) ||
        gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_ANALOGUE_STICK),
    
  2. با استفاده از توابع آستانه که قبلاً توضیح داده شد، هر ورودی آنالوگ را به عنوان یک ورودی مجزا در نظر گرفتیم.

  3. ما تا آنجا پیش رفتیم که ورودی گیم‌پد را روی ابله پیچ‌کردیم، به‌جای اینکه آن را آماده کنیم - حلقه نظرسنجی ما در واقع رویدادهای کلیدی و کلیدی لازم را (با یک کد کلید مناسب) ترکیب می‌کند و آنها را به DOM باز می‌فرستد:

    // Create and dispatch a corresponding key event.
    var event = document.createEvent('Event');
    var eventName = down ? 'keydown' : 'keyup';
    event.initEvent(eventName, true, true);
    event.keyCode = gamepad.stateToKeyCodeMap_[state];
    gamepad.containerElement_.dispatchEvent(event);

و این تمام چیزی است که در آن وجود دارد!

نکات و ترفندها

  • به یاد داشته باشید که قبل از فشار دادن یک دکمه، گیم پد به هیچ وجه در مرورگر شما قابل مشاهده نخواهد بود.
  • اگر همزمان در حال تست گیم پد در مرورگرهای مختلف هستید، توجه داشته باشید که فقط یکی از آنها می تواند کنترلر را حس کند. اگر هیچ رویدادی دریافت نمی‌کنید، مطمئن شوید که صفحات دیگری را که ممکن است از آن استفاده می‌کنند ببندید. به علاوه، بر اساس تجربه ما، گاهی اوقات یک مرورگر می‌تواند گیم‌پد را نگه دارد، حتی اگر تب را ببندید یا از خود مرورگر خارج شوید. راه اندازی مجدد سیستم گاهی اوقات تنها راه برای رفع مشکلات است.
  • مثل همیشه، از Chrome Canary و معادل‌های آن برای مرورگرهای دیگر استفاده کنید تا مطمئن شوید که بهترین پشتیبانی را تجربه می‌کنید – و سپس اگر مشاهده کردید که نسخه‌های قدیمی‌تر رفتار متفاوتی دارند، به درستی عمل کنید.

آینده

ما امیدواریم که این به روشن کردن این API جدید کمک کند - هنوز کمی مخاطره آمیز، اما در حال حاضر بسیار سرگرم کننده است.

علاوه بر قطعات گمشده API (به عنوان مثال رویدادها) و پشتیبانی گسترده تر مرورگر، ما همچنین امیدواریم که در نهایت شاهد مواردی مانند کنترل rumble، دسترسی به ژیروسکوپ های داخلی و غیره باشیم. و پشتیبانی بیشتر از انواع مختلف گیم پدها – لطفاً یک باگ را در Chrome و/یا در صورت مشاهده اشکالی که اشتباه کار می کند یا اصلاً کار نمی کند ، در مورد فایرفاکس ارسال کنید.

اما قبل از آن، بروید و با Doodle Hurdles 2012 ما بازی کنید و ببینید که چقدر سرگرم کننده تر در گیم پد است. اوه، فقط گفتی می توانی بهتر از 10.7 ثانیه انجام بدهی؟ بیاور.

در ادامه مطلب