مقدمة
تساعد واجهة برمجة التطبيقات Pointer Lock API في تنفيذ عناصر التحكّم في ألعاب الرماية من منظور الشخص الأول بشكل صحيح في لعبة على المتصفّح. بدون حركة نسبية للماوس، قد يصل مؤشر اللاعب، على سبيل المثال، إلى الحافة اليمنى من الشاشة، ولن يتم احتساب أي حركات أخرى إلى اليمين، ولن يستمر العرض في التمرير لليسار، ولن يتمكّن اللاعب من ملاحقة الأشرار وإطلاق النار عليهم بمدفعه الرشاش. سيشعر اللاعب بالضيق. لا يمكن حدوث هذا السلوك غير الأمثل عند قفل المؤشر.
تسمح واجهة برمجة التطبيقات Pointer Lock API لتطبيقك بتنفيذ ما يلي:
- الوصول إلى بيانات الماوس الأولية، بما في ذلك حركات الماوس النسبية
- توجيه جميع أحداث الماوس إلى عنصر معيّن
كأثر جانبي لتفعيل ميزة قفل المؤشر، يتم إخفاء مؤشر الماوس ما يتيح لك اختيار رسم مؤشر خاص بالتطبيق إذا أردت، أو ترك مؤشر الماوس مخفيًا حتى يتمكّن المستخدم من تحريك الإطار باستخدام الماوس. حركة الماوس النسبية هي فرق موضع مؤشر الماوس عن الإطار السابق بغض النظر عن الموضع المطلق. على سبيل المثال، إذا تم نقل مؤشر الماوس من (640، 480) إلى (520، 490)، كانت الحركة النسبية هي (-120، 10). يمكنك الاطّلاع أدناه على مثال تفاعلي يعرض قيم تغيُّر موضع الماوس الأوّلية.
يتناول هذا الدليل التعليمي موضوعَين: أساسيات تفعيل أحداث قفل المؤشر ومعالجتها، وتنفيذ مخطط التحكّم في ألعاب إطلاق النار من منظور الشخص الأول. بعد الانتهاء من قراءة هذه المقالة، ستتعرّف على كيفية استخدام ميزة قفل المؤشر وتنفيذ عناصر التحكّم بأسلوب Quake في لعبتك على المتصفّح.
توافُق المتصفح
آلية قفل المؤشر
ميزة "اكتشاف الأشياء"
لتحديد ما إذا كان متصفّح المستخدم يتيح قفل المؤشر، عليك البحث عن pointerLockElement
أو إصدار مسبوق ببادئة من المورّد في عنصر المستند. في الرمز:
var havePointerLock = 'pointerLockElement' in document ||
'mozPointerLockElement' in document ||
'webkitPointerLockElement' in document;
لا تتوفّر ميزة قفل المؤشر حاليًا إلا في Firefox وChrome. لا يتيح كلّ من Opera وIE استخدام هذه الميزة بعد.
جارٍ التفعيل
تتألّف عملية تفعيل قفل المؤشر من خطوتَين. يطلب تطبيقك أولاً تفعيل قفل المؤشر لعنصر معيّن، وبعد أن يمنح المستخدم الإذن، يتم تشغيل حدث pointerlockchange
على الفور. يمكن للمستخدم إلغاء قفل المؤشر في أي وقت من خلال الضغط على مفتاح Escape. يمكن لتطبيقك أيضًا الخروج من قفل المؤشر آليًا. عند إلغاء قفل المؤشر، يتم تشغيل الحدث pointerlockchange
.
element.requestPointerLock = element.requestPointerLock ||
element.mozRequestPointerLock ||
element.webkitRequestPointerLock;
// Ask the browser to lock the pointer
element.requestPointerLock();
// Ask the browser to release the pointer
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
ما عليك سوى استخدام الرمز أعلاه. عندما يحظر المتصفّح المؤشر، ستظهر فقاعة لإعلام المستخدم بأنّ تطبيقك قد حظر المؤشر وإرشاده إلى أنّه يمكنه إلغاء ذلك من خلال الضغط على مفتاح Esc.
معالجة الأحداث
هناك حدثان يجب أن يضيف تطبيقك مستمعَين لهما. الأول هو pointerlockchange
، الذي يتم تشغيله عند حدوث تغيير في حالة قفل المؤشر. والثاني هو mousemove
الذي يتم تشغيله كلما تم تحريك الماوس.
// Hook pointer lock state change events
document.addEventListener('pointerlockchange', changeCallback, false);
document.addEventListener('mozpointerlockchange', changeCallback, false);
document.addEventListener('webkitpointerlockchange', changeCallback, false);
// Hook mouse move events
document.addEventListener("mousemove", this.moveCallback, false);
في ردّ الاتصال الذي يخصّ pointerlockchange
، عليك التحقّق مما إذا تم قفل المؤشر أو فتح قفله للتو. من السهل تحديد ما إذا كان قد تم تفعيل قفل المؤشر: تحقّق مما إذا كان document.pointerLockElement يساوي العنصر الذي تم طلب قفل المؤشر له. إذا كان الأمر كذلك، يعني ذلك أنّ تطبيقك قد قفل المؤشر بنجاح، وإذا لم يكن الأمر كذلك، يعني ذلك أنّ المستخدم أو الرمز البرمجي الخاص بك قد فتح قفل المؤشر.
if (document.pointerLockElement === requestedElement ||
document.mozPointerLockElement === requestedElement ||
document.webkitPointerLockElement === requestedElement) {
// Pointer was just locked
// Enable the mousemove listener
document.addEventListener("mousemove", this.moveCallback, false);
} else {
// Pointer was just unlocked
// Disable the mousemove listener
document.removeEventListener("mousemove", this.moveCallback, false);
this.unlockHook(this.element);
}
عند تفعيل قفل المؤشر، تظلّ القيم clientX
وclientY
وscreenX
وscreenY
ثابتة. يتم تعديل movementX
وmovementY
بعدد البكسلات التي كان من المفترض أن يتحرك إليها المؤشر منذ إرسال آخر حدث. في الرمز البرمجي الزائف:
event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;
داخل دالة الاستدعاء mousemove
، يمكن استخراج بيانات حركة الماوس النسبية من حقلَي movementX
وmovementY
للحدث.
function moveCallback(e) {
var movementX = e.movementX ||
e.mozMovementX ||
e.webkitMovementX ||
0,
movementY = e.movementY ||
e.mozMovementY ||
e.webkitMovementY ||
0;
}
رصد الأخطاء
إذا تمّ رصد خطأ عند الدخول إلى قفل المؤشر أو الخروج منه، يتمّ تشغيل الحدث pointerlockerror
. لا تتوفّر بيانات مرتبطة بهذا الحدث.
document.addEventListener('pointerlockerror', errorCallback, false);
document.addEventListener('mozpointerlockerror', errorCallback, false);
document.addEventListener('webkitpointerlockerror', errorCallback, false);
هل يجب عرض الفيديو بملء الشاشة؟
كان قفل المؤشر مرتبطًا في الأصل بواجهة برمجة التطبيقات FullScreen API. ويعني ذلك أنّه يجب أن يكون العنصر في وضع ملء الشاشة قبل أن يتم قفل المؤشر عليه. لم يعُد ذلك صحيحًا، ويمكن استخدام قفل المؤشر لأي عنصر في تطبيقك سواء كان بملء الشاشة أو لا.
مثال على عناصر التحكّم في ألعاب إطلاق النار من منظور البطل
بعد تفعيل قفل المؤشر وتلقّي الأحداث، حان وقت تقديم مثال عملي. هل أردت يومًا معرفة آلية عمل عناصر التحكّم في لعبة Quake؟ سأشرح لك هذه الأنواع باستخدام الرموز البرمجية.
تستند عناصر التحكّم في ألعاب إطلاق النار من منظور البطل إلى أربع آليات أساسية:
- التنقّل للأمام والخلف على طول متجه العرض الحالي
- التحرك يسارًا ويمينًا على طول متجه الانحراف الحالي
- تدوير العرض (اليسار واليمين)
- تدوير درجة ميل العرض (للأعلى وللأسفل)
لا تحتاج اللعبة التي تستخدم مخطّط التحكّم هذا إلا إلى ثلاث قطع من البيانات: موضع الكاميرا، واتجاه نظر الكاميرا، واتجاه ثابت للأعلى. يكون متجه الارتفاع دائمًا (0, 1, 0). إنّ جميع الآليات الأربعة المذكورة أعلاه لا تفعل سوى التلاعب بموقع الكاميرا واتجاه نظرها بطرق مختلفة.
حركة
أولاً، الحركة. في العرض التوضيحي أدناه، يتم ربط التنقّل بالمفاتيح العادية W وA وS وD. تؤدي مفاتيح W وS إلى تحريك الكاميرا للأمام والخلف. بينما يحرِّك مفتاحا A وD الكاميرا لليسار واليمين. يمكنك تحريك الكاميرا للأمام والخلف بسهولة:
// Forward direction
var forwardDirection = vec3.create(cameraLookVector);
// Speed
var forwardSpeed = dt * cameraSpeed;
// Forward or backward depending on keys held
var forwardScale = 0.0;
forwardScale += keyState.W ? 1.0 : 0.0;
forwardScale -= keyState.S ? 1.0 : 0.0;
// Scale movement
vec3.scale(forwardDirection, forwardScale * forwardSpeed);
// Add scaled movement to camera position
vec3.add(cameraPosition, forwardDirection);
يتطلب الانحراف لليسار واليمين تحديد اتجاه الانحراف. يمكن احتساب اتجاه الانحراف باستخدام المنتج المتقاطع:
// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);
بعد تحديد اتجاه الانحراف، يكون تنفيذ حركة الانحراف مماثلاً للانتقال إلى الأمام أو الخلف.
الخطوة التالية هي تدوير العرض.
الانحراف
إنّ الانحراف أو التدوير الأفقي لعرض الكاميرا هو مجرد دوران حول متجه الارتفاع الثابت. في ما يلي رمز عام لتدوير متجه نظر الكاميرا حول محور عشوائي. ويعمل هذا الإجراء من خلال إنشاء رباعي الأبعاد يمثّل دوران deltaAngle
راديان حول axis
، ثم يستخدم الرباعي الأبعاد لتدوير متجه نظر الكاميرا:
// Extract camera look vector
var frontDirection = vec3.create();
vec3.subtract(this.lookAtPoint, this.eyePoint, frontDirection);
vec3.normalize(frontDirection);
var q = quat4.create();
// Construct quaternion
quat4.fromAngleAxis(deltaAngle, axis, q);
// Rotate camera look vector
quat4.multiplyVec3(q, frontDirection);
// Update camera look vector
this.lookAtPoint = vec3.create(this.eyePoint);
vec3.add(this.lookAtPoint, frontDirection);
الاقتراح
إنّ تنفيذ الانحراف أو الدوران العمودي لعرض الكاميرا مشابه، ولكن بدلاً من الدوران حول متجه الارتفاع، يمكنك تطبيق دوران حول متجه الانحراف. الخطوة الأولى هي احتساب متجه الانحراف ثم تدوير متجه نظرة الكاميرا حول هذا المحور.
ملخّص
تتيح لك واجهة برمجة التطبيقات Pointer Lock API التحكّم في مؤشر الماوس. إذا كنت بصدد إنشاء ألعاب ويب، سيحب اللاعبون التوقف عن التعرض للقتل لأنّهم حركوا مؤشر الماوس خارج النافذة بسعادة وتوقفت لعبتك عن تلقّي إشارات الماوس. إليك كيفية استخدام هذه الميزة:
- إضافة أداة معالجة حدث
pointerlockchange
لتتبُّع حالة قفل المؤشر - طلب قفل المؤشر لعنصر معيّن
- إضافة أداة معالجة الحدث
mousemove
لتلقّي آخر الأخبار