قفل المؤشر وعناصر التحكم في الرماية من منظور الشخص الأول

جون ماكاتشان
جون ماكاتشان

مقدمة

تساعد واجهة برمجة التطبيقات Pointer Lock API على تنفيذ عناصر التحكّم في إطلاق النار من منظور البطل في لعبة متصفّح بشكل صحيح. بدون حركة الماوس النسبية، يمكن لمؤشر الماوس مثلاً الاصطدام بالحافة اليمنى من الشاشة وسيتم خصم أي حركات أخرى إلى اليمين - ولن يستمر العرض في التحرك لليمين، ولن يتمكن اللاعب من مطاردة الأشرار ومضايقتهم بسلاحه الآلي. سينزعج اللاعب ويشعر بالإحباط. لا يمكن أن يحدث هذا السلوك دون المستوى المطلوب باستخدام ميزة "قفل المؤشر".

تسمح Pointer Lock API لتطبيقك بإجراء ما يلي:

  • إمكانية الوصول إلى بيانات الماوس الأولية بما في ذلك حركات الماوس النسبية
  • توجيه جميع أحداث الماوس إلى عنصر معيّن

كتأثير جانبي لتفعيل قفل المؤشر، يكون مؤشر الماوس مخفيًا مما يسمح لك باختيار رسم مؤشر خاص بالتطبيق إذا كنت ترغب في ذلك، أو ترك مؤشر الماوس مخفيًا حتى يتمكن المستخدم من تحريك الإطار بالماوس. حركة الماوس النسبية هي دلتا موضع مؤشر الماوس من الإطار السابق بغض النظر عن الموضع المطلق. على سبيل المثال، إذا انتقل مؤشر الماوس من (640، 480) إلى (520، 490)، كانت الحركة النسبية (-120، 10). انظر أدناه للحصول على مثال تفاعلي يوضح دلتا موضع الماوس الأولي.

يتناول هذا البرنامج التعليمي موضوعَين: أساسيات تفعيل أحداث قفل المؤشر ومعالجتها، وتنفيذ نظام التحكّم في الرماية من منظور البطل. هذا صحيح، عند الانتهاء من قراءة هذه المقالة، ستعرف كيفية استخدام قفل المؤشر وتنفيذ عناصر التحكم في نمط الزلزال في لعبة المتصفح التي تستخدمها!

توافُق المتصفح

التوافق مع المتصفح

  • 37
  • 13
  • 50
  • 10.1

المصدر

ميكانيكا قفل المؤشر

رصد الميزات

لتحديد ما إذا كان متصفّح المستخدم يتيح استخدام قفل المؤشر، يجب البحث عن pointerLockElement أو إصدار يبدأه المورّد في كائن المستند. في الرمز:

var havePointerLock = 'pointerLockElement' in document ||
    'mozPointerLockElement' in document ||
    'webkitPointerLockElement' in document;

لا يتوفّر حاليًا قفل المؤشر إلا في Firefox وChrome. لا يمكن استخدام Opera وIE حتى الآن.

جارٍ التفعيل

تفعيل قفل المؤشر هو عملية من خطوتين. أولاً، يجب تفعيل قفل المؤشر لعنصر معيّن، وبعد أن يمنح المستخدم الإذن مباشرةً، يتم تنشيط حدث pointerlockchange. يمكن للمستخدم إلغاء قفل المؤشر في أي وقت عن طريق الضغط على مفتاح Esc. يمكن للتطبيق أيضًا الخروج تلقائيًا من قفل المؤشر. عند إلغاء قفل المؤشر، يتم تنشيط حدث 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".

شريط معلومات "قفل المؤشر" في Chrome
شريط معلومات "قفل المؤشر" في Chrome

التعامل مع الأحداث

هناك حدثان يجب أن يضيف تطبيقك أدوات استماع لهما. الأولى هي 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);

هل يجب ملء الشاشة؟

كان قفل المؤشر في الأصل مرتبطًا بواجهة برمجة التطبيقات ملء الشاشة. يعني ذلك أن العنصر يجب أن يكون في وضع ملء الشاشة قبل أن يتم تثبيت المؤشر عليه. ولم يعد ذلك صحيحًا ويمكن استخدام قفل المؤشر لأي عنصر في تطبيقك بملء الشاشة أو لا.

مثال على عناصر تحكم الرماية من منظور البطل

والآن بعد تفعيل قفل المؤشر وتلقّي الأحداث، حان الوقت للاطّلاع على مثال عملي. هل أردت يومًا أن تعرف كيف تعمل عناصر التحكّم في 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" لتلقّي آخر الأخبار

العروض التوضيحية الخارجية

المراجع