مقدمة
ولنترك للمبتدئين استخدام لوحات المفاتيح في ألعاب المغامرات، وأطراف أصابعهم الحسّاسة في ألعاب تقطيع الفواكه، وأجهزة استشعار الحركة المبتكرة للتظاهر بأنهم يرقصون مثل مايكل جاكسون. (خبر سار: لا يمكنهم ذلك). ولكنّك مختلف. أنت بحالة أفضل. أحسنت. تبدأ الألعاب وتنتهي مع استخدام وحدة تحكّم في يديك.
ولكن انتظر. هل لا يمكنك استخدام وحدة تحكّم في ألعاب الفيديو في تطبيق الويب؟ لم يعُد ذلك صحيحًا. تأتي واجهة برمجة التطبيقات Gamepad API الجديدة تمامًا لإنقاذك، إذ تتيح لك استخدام JavaScript لقراءة حالة أي جهاز تحكّم في ألعاب متصل بالكمبيوتر. إنّ هذه الميزة جديدة جدًا، حيث تم طرحها في الإصدار 21 من Chrome الأسبوع الماضي فقط، كما أنّها على وشك أن تصبح متاحة في Firefox (تتوفّر حاليًا في إصدار خاص).
لقد كان هذا التوقيت مناسبًا جدًا، لأنّنا حصلنا على فرصة لاستخدامه مؤخرًا في رسم Google المتحرك الخاص ببطولة حواجز 2012. ستوضّح هذه المقالة بأسلوب موجز كيفية إضافة Gamepad API إلى الصورة المتحرّكة، وما تعلمناه خلال هذه العملية.
مختبِر أجهزة التحكّم في الألعاب
على الرغم من أنّها مؤقتة، تميل الرموز المرسومة التفاعلية إلى أن تكون معقدة جدًا من حيث الأداء. لتسهيل توضيح ما نتحدث عنه، أخذنا رمز جهاز التحكّم في الألعاب من الصورة المتحرّكة، وأعددنا أداة اختبار بسيطة لجهاز التحكّم في الألعاب. يمكنك استخدامها لمعرفة ما إذا كانت وحدة تحكّم USB تعمل بشكل صحيح، ويمكنك أيضًا الاطّلاع على التفاصيل لمعرفة كيفية عملها.
ما هي المتصفّحات المتوافقة حاليًا؟
ما هي أجهزة التحكّم بالألعاب التي يمكن استخدامها؟
بشكل عام، من المفترض أن تعمل أي وحدة تحكم حديثة متوافقة مع نظامك بشكل أصلي. لقد اختبرنا وحدات تحكّم متنوعة في الألعاب من وحدات تحكّم USB غير التابعة لعلامة تجارية معيّنة على جهاز كمبيوتر شخصي، بالإضافة إلى وحدات تحكّم في ألعاب PlayStation 2 متصلة عبر جهاز ربط بجهاز Mac، وصولاً إلى وحدات تحكّم بلوتوث المقترنة بجهاز كمبيوتر محمول يعمل بنظام التشغيل ChromeOS.
هذه صورة لبعض أجهزة التحكّم التي استخدمناها لاختبار رسمنا المتحرك. إذا لم يعمل جهاز التحكّم أو إذا تم ربط عناصر التحكّم بشكل غير صحيح، يُرجى إبلاغ فريق Chrome أو Firefox بخطأ . (يُرجى إجراء الاختبار باستخدام أحدث إصدار من كل متصفّح للتأكّد من أنّه تم حلّ المشكلة).
ميزة رصد واجهة برمجة تطبيقات لوحة الألعاب<
يمكنك إجراء ذلك بسهولة في Chrome:
var gamepadSupportAvailable = !!navigator.webkitGetGamepads || !!navigator.webkitGamepads;
لا يبدو أنّه من الممكن رصد ذلك في Firefox حتى الآن، لأنّ كل شيء يستند إلى الأحداث، ويجب إرفاق جميع معالجات الأحداث بالنافذة، ما يمنع استخدام أسلوب نموذجي لرصد معالجات الأحداث.
ولكننا متأكدون من أنّ هذا الوضع مؤقت. يُطلعك إطار العمل الرائع Modernizr على Gamepad API، لذا ننصحك باستخدامه لتلبية جميع احتياجاتك الحالية والمستقبلية في ما يتعلّق برصد الأجهزة:
var gamepadSupportAvailable = Modernizr.gamepads;
التعرّف على لوحات الألعاب المتصلة
حتى في حال ربط وحدة التحكّم في الألعاب، لن تظهر بأي شكل من الأشكال ما لم يضغط المستخدم على أي من أزرارها أولاً. يهدف ذلك إلى منع ميزة "التعرّف على الجهاز"، على الرغم من أنّه يشكّل تحديًا لتجربة المستخدم: لا يمكنك أن تطلب من المستخدم الضغط على الزر أو تقديم تعليمات خاصة بوحدة التحكّم لأنّك لا تعرف ما إذا كان قد ربط وحدة التحكّم.
بعد اجتياز هذه العقبة (نعتذر عن ذلك)، ستواجه المزيد من التحديات.
الاستطلاعات
يعرِض تطبيق Chrome لواجهة برمجة التطبيقات وظيفة – navigator.webkitGetGamepads()
– يمكنك استخدامها للحصول على قائمة بجميع أجهزة التحكّم في الألعاب الموصولة حاليًا بالنظام، بالإضافة إلى حالتها الحالية (الأزرار + العصي). سيتم عرض أول جهاز تحكّم متّصل كأول إدخال في المصفوفة، وهكذا.
(استبدلت هذه الدعوة للدالة مؤخرًا صفيفًا يمكنك الوصول إليه مباشرةً: navigator.webkitGamepads[]
. اعتبارًا من أوائل آب (أغسطس) 2012، لا يزال الوصول إلى هذه الصفيف ضروريًا في الإصدار 21 من Chrome، في حين تعمل طلب الدالة في الإصدار 22 من Chrome والإصدارات الأحدث. من الآن فصاعدًا، ستكون طريقة استدعاء الدالة هي الطريقة المُقترَحة لاستخدام واجهة برمجة التطبيقات، وستتوفّر تدريجيًا لجميع متصفّحات Chrome المثبّتة).
يتطلّب الجزء الذي تم تنفيذه حتى الآن من المواصفات التحقّق باستمرار من حالة أجهزة التحكّم في الألعاب المتصلة (ومقارنتها بالحالة السابقة إذا لزم الأمر)، بدلاً من بدء الأحداث عند تغيُّر أيّ من الإعدادات، لقد اعتمدنا على requestAnimationFrame() لإعداد الاستطلاع بالطريقة الأكثر فعالية وملاءمةً للبطارية. بالنسبة إلى رسم Google 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()
، لمعرفة طريقة واحدة لحلّ هذه المشكلة.
الفعاليات
يستخدم Firefox طريقة بديلة أفضل موضّحة في مواصفات Gamepad API. بدلاً من أن يطلب منك إجراء استطلاع، يعرض Firefox حدثَين، 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[]
. في ما يلي ملخّص مرئي لما يقابلها:
تطلب المواصفة من المتصفّح ربط أول ستة عشر زرًا وأربعة محاور بما يلي:
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). في رسمنا، ننفّذ ذلك على النحو التالي:
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;
}
};
الضغط على الأزرار وحركات العصا
الفعاليات
في بعض الحالات، مثل ألعاب محاكاة الطيران، يكون التحقّق باستمرار من مواضع العصا أو الضغط على الأزرار والتفاعل معها أكثر منطقية، ولكن ماذا عن ألعاب مثل رسم خربشية 2012 Hurdles؟ قد تتساءل: لماذا أحتاج إلى التحقّق من الأزرار في كل إطار فردي؟ لماذا لا يمكنني الحصول على أحداث مثل لوحة المفاتيح أو الماوس للأعلى/للأسفل؟
والخبر السار هو أنّه يمكنك إجراء ذلك. الخبر السيئ هو أنّه في المستقبل. وهي متوفّرة في المواصفات، ولكن لم يتم تنفيذها في أي متصفّح بعد.
الاستطلاعات
في الوقت الحالي، يمكنك الخروج من المشكلة من خلال مقارنة الحالة الحالية بالحالة السابقة واستدعاء الدوالّ إذا لاحظت أي اختلاف. على سبيل المثال:
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);
}
التركيز على لوحة المفاتيح في رسم الشعار لعام 2012
بما أنّ لوحة المفاتيح هي أسلوب الإدخال المفضّل لرسم اليوم بدون وحدة تحكّم، قرّرنا أن تحاكي وحدة التحكّم لوحة المفاتيح بشكل وثيق. وقد نتج عن ذلك ثلاثة قرارات:
لا يحتاج الرموز التعبيرية إلا إلى ثلاثة أزرار، اثنان للركض وواحد للقفز، ولكن من المرجّح أن يتضمّن جهاز التحكّم في الألعاب المزيد من الأزرار. لذلك، ربطنا جميع الأزرار الستة عشر المعروفة والعصاتَين المعروفتَين بهذه الوظائف المنطقية الثلاث بطريقة كنا نعتقد أنّها الأكثر منطقية، حتى يتمكّن المستخدمون من الركض من خلال: تبديل زرَّي A/B أو تبديل زرَّي الكتف أو الضغط على يمين/يسار لوحة التوجيه أو تحريك أي من العصاتَين بعنف إلى اليمين واليسار (بطبيعة الحال، ستكون بعض هذه الطرق أكثر فعالية من غيرها). على سبيل المثال:
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),
لقد تعاملنا مع كل مدخل رقمي تمثيلي على أنّه مدخل رقمي منتظم، باستخدام دوالّ الحدود الدنيا الموضّحة سابقًا.
لقد بذلنا قصارى جهدنا لربط رسوم الشعار المبتكرة بوحدة تحكّم في الألعاب، بدلاً من دمجها في الشعار. فحلقة الاستطلاع لدينا تجمع أحداث keydown وkeyup الضرورية (باستخدام keyCode مناسب) وتعيد إرسالها إلى 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 والإصدارات المشابهة من المتصفّحات الأخرى للتأكّد من الحصول على أفضل دعم، ثم اتّخِذ الإجراءات المناسبة إذا لاحظت اختلافًا في سلوك الإصدارات القديمة.
المستقبل
نأمل أن يساعدك ذلك في التعرّف على واجهة برمجة التطبيقات الجديدة هذه التي لا تزال غير متوفّرة بشكل كامل، ولكنّها ممتعة جدًا.
بالإضافة إلى الأجزاء غير المتوفّرة من واجهة برمجة التطبيقات (مثل الأحداث) وإمكانية استخدام المتصفحات بشكل أوسع، نأمل أيضًا أن نوفّر في النهاية ميزات مثل التحكّم في الاهتزاز والوصول إلى أجهزة الاستشعار المدمجة وما إلى ذلك، بالإضافة إلى مزيد من التوافق مع الأنواع المختلفة من أجهزة التحكّم في الألعاب. يُرجى إبلاغنا بأي أخطاء في Chrome و/أو إبلاغنا بأي أخطاء في Firefox إذا واجهت أي أخطاء أو لم تعمل الميزة على الإطلاق.
قبل ذلك، يمكنك تشغيل شعار Hurdles 2012 المُبتكَر والتعرّف على مدى روعة اللعب على لوحة الألعاب. هل قلت للتو أنّه يمكنك تحقيق أداء أفضل من 10.7 ثانية؟ حسنًا.