قفل اشاره گر و کنترل تیرانداز اول شخص

مقدمه

Pointer Lock API به اجرای صحیح کنترل های تیراندازی اول شخص در یک بازی مرورگر کمک می کند. برای مثال، بدون حرکت نسبی ماوس، مکان‌نمای پخش‌کننده می‌تواند به لبه سمت راست صفحه ضربه بزند و هرگونه حرکت بیشتر به سمت راست کاهش می‌یابد - نما به حرکت به سمت راست ادامه نمی‌دهد، و بازیکن نمی‌تواند موارد بد را دنبال کند. بچه ها و با مسلسل خود آنها را به ستوه می آورد. بازیکن شکسته می شود و ناامید می شود. با قفل اشاره گر این رفتار غیربهینه نمی تواند اتفاق بیفتد.

Pointer Lock API به برنامه شما اجازه می دهد کارهای زیر را انجام دهد:

  • به داده های خام ماوس از جمله حرکات نسبی ماوس دسترسی پیدا کنید
  • مسیریابی همه رویدادهای ماوس به یک عنصر خاص

به عنوان یک اثر جانبی فعال کردن قفل نشانگر، مکان‌نمای ماوس پنهان است و به شما امکان می‌دهد در صورت تمایل، یک اشاره‌گر مخصوص برنامه را بکشید یا نشانگر ماوس را مخفی نگه دارید تا کاربر بتواند کادر را با ماوس حرکت دهد. حرکت نسبی ماوس، دلتای موقعیت اشاره گر ماوس از فریم قبلی بدون توجه به موقعیت مطلق است. به عنوان مثال، اگر نشانگر ماوس از (640، 480) به (520، 490) حرکت کند، حرکت نسبی (120-10) خواهد بود. برای مثال تعاملی که دلتاهای موقعیت خام ماوس را نشان می دهد به زیر مراجعه کنید.

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

سازگاری با مرورگر

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

  • کروم: 37.
  • لبه: 13.
  • فایرفاکس: 50.
  • سافاری: 10.1.

منبع

مکانیک قفل اشاره گر

تشخیص ویژگی

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

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

در حال حاضر قفل اشاره گر فقط در فایرفاکس و کروم موجود است. Opera و IE هنوز از آن پشتیبانی نمی کنند.

فعال کردن

فعال کردن قفل اشاره گر یک فرآیند دو مرحله ای است. ابتدا برنامه شما درخواست می کند که قفل اشاره گر برای یک عنصر خاص فعال شود و بلافاصله پس از اجازه کاربر، یک رویداد pointerlockchange فعال می شود. کاربر می تواند در هر زمان با فشار دادن کلید فرار، قفل اشاره گر را لغو کند. برنامه شما همچنین می تواند به صورت برنامه ریزی شده از قفل اشاره گر خارج شود. هنگامی که قفل اشاره گر لغو می شود، یک رویداد 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" آن را لغو کند.

نوار اطلاعات Pointer Lock در کروم.
نوار اطلاعات Pointer Lock در کروم.

مدیریت رویداد

دو رویداد وجود دارد که برنامه شما باید شنوندگان را برای آنها اضافه کند. اولین مورد 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;

داده‌های حرکت نسبی ماوس movementY می‌توان از قسمت‌های movementX و mousemove رویداد استخراج کرد.

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);

تمام صفحه مورد نیاز است؟

قفل اشاره گر در ابتدا به API FullScreen گره خورده بود. به این معنی که یک عنصر باید در حالت تمام صفحه باشد تا بتواند نشانگر را روی آن قفل کند. این دیگر درست نیست و قفل اشاره گر می تواند برای هر عنصری در برنامه شما به صورت تمام صفحه استفاده شود یا خیر.

مثال کنترل تیرانداز اول شخص

اکنون که قفل اشاره گر را فعال کرده ایم و رویدادها را دریافت می کنیم، نوبت به یک مثال عملی می رسد. آیا تا به حال خواسته اید بدانید که کنترل ها در Quake چگونه کار می کنند؟ ببندید چون میخواهم آنها را با کد توضیح دهم!

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

  • حرکت به جلو و عقب در امتداد بردار ظاهر فعلی
  • حرکت به چپ و راست در امتداد بردار strafe فعلی
  • چرخش نمای انحرافی (چپ و راست)
  • چرخش زمین دید (بالا و پایین)

بازی‌ای که این طرح کنترل را اجرا می‌کند تنها به سه داده نیاز دارد: موقعیت دوربین، بردار ظاهر دوربین و یک بردار ثابت بالا. بردار بالا همیشه (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 دارد. جهت strafe را می توان با استفاده از محصول متقاطع محاسبه کرد:

// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);

هنگامی که جهت strafe را دارید، اجرای حرکت strafe مانند حرکت به جلو یا عقب است.

مرحله بعدی چرخش نما است.

یاو

انحراف یا چرخش افقی نمای دوربین فقط یک چرخش حول بردار ثابت بالا است. در زیر کد کلی برای چرخش بردار نگاه دوربین حول یک محور دلخواه آمده است. با ساختن یک کواترنیون که نشان دهنده چرخش رادیان های 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);

زمین

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

خلاصه

Pointer Lock API به شما اجازه می دهد تا کنترل مکان نما ماوس را در دست بگیرید. اگر بازی‌های تحت وب می‌سازید، بازیکنان شما از شکسته شدن آن‌ها لذت خواهند برد، زیرا با هیجان ماوس را از پنجره بیرون می‌آورند و بازی شما به‌روزرسانی‌های ماوس را دریافت نمی‌کند. استفاده ساده است:

  • شنونده رویداد pointerlockchange برای ردیابی وضعیت قفل اشاره گر اضافه کنید
  • درخواست قفل اشاره گر برای یک عنصر خاص
  • برای دریافت به‌روزرسانی، شنونده رویداد mousemove اضافه کنید

دموهای خارجی

مراجع