गेम इनपुट हैंडलिंग
अच्छी इनपुट हैंडलिंग किसी गेम को बना या बिगाड़ सकती है। लैगी कंट्रोल, छूटे हुए इनपुट या उलझाने वाली बटन मैपिंग खिलाड़ियों को आपके गेम का कंटेंट देखने से पहले ही चिढ़ा देती है। यह ट्यूटोरियल बताता है कि कीबोर्ड, माउस, टच और गेमपैड को एक एकीकृत तरीके से कैसे हैंडल करें।
पहले इसे आज़माएँ:
इनपुट स्टेट पैटर्न
सबसे सीधा-सादा तरीका है इवेंट्स पर सीधे प्रतिक्रिया देना: जब यूज़र कोई की दबाए, कुछ करें। इससे समस्याएँ आती हैं। इवेंट्स अप्रत्याशित समय पर फायर होते हैं, जबकि आपका game loop एक तय दर पर चलता है। अगर आप keydown हैंडलर में प्लेयर को मूव करते हैं, तो मूवमेंट की स्पीड की रिपीट रेट पर निर्भर करती है, जो OS के हिसाब से बदलती है।
इसके बजाय, एक इनपुट स्टेट ऑब्जेक्ट बनाकर रखें जिसे आपका game loop पढ़े:
const input = {
left: false,
right: false,
up: false,
down: false,
action: false,
pointer: { x: 0, y: 0, down: false },
}इवेंट हैंडलर ये फ्लैग सेट करते हैं। game loop इन्हें पढ़ता है। यह अलगाव सब कुछ आसान बना देता है: आपके update लॉजिक को इससे कोई फर्क नहीं पड़ता कि इनपुट कीबोर्ड, टच या गेमपैड से आया।
कीबोर्ड
e.key के बजाय e.code इस्तेमाल करें, क्योंकि यह की की भौतिक स्थिति को दर्शाता है। WASD नॉन-QWERTY लेआउट पर भी सही ढंग से काम करता है।
const keyMap = {
ArrowLeft: 'left',
ArrowRight: 'right',
ArrowUp: 'up',
ArrowDown: 'down',
KeyA: 'left',
KeyD: 'right',
KeyW: 'up',
KeyS: 'down',
Space: 'action',
}
window.addEventListener('keydown', (e) => {
const action = keyMap[e.code]
if (action) {
input[action] = true
e.preventDefault()
}
})
window.addEventListener('keyup', (e) => {
const action = keyMap[e.code]
if (action) input[action] = false
})e.preventDefault() ऐरो कीज़ को पेज स्क्रॉल करने से रोकता है। इसे सिर्फ़ उन्हीं कीज़ के लिए कॉल करें जिन्हें आप हैंडल कर रहे हैं।
माउस
माउस के कोऑर्डिनेट स्क्रीन स्पेस में आते हैं। आपको इन्हें कैनवस स्पेस में बदलना होगा:
const canvas = document.getElementById('game')
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect()
input.pointer.x = e.clientX - rect.left
input.pointer.y = e.clientY - rect.top
})
canvas.addEventListener('mousedown', () => {
input.pointer.down = true
input.action = true
})
canvas.addEventListener('mouseup', () => {
input.pointer.down = false
input.action = false
})अगर आपका कैनवस स्केल किया हुआ है (रिस्पॉन्सिव कैनवस ट्यूटोरियल देखें), तो गेम कोऑर्डिनेट पाने के लिए आपको स्केल फैक्टर से भाग देना होगा।
टच
टच माउस की तरह ही काम करता है, लेकिन ब्राउज़र को स्क्रॉल या ज़ूम करने से रोकने के लिए आपको e.preventDefault() की ज़रूरत होती है:
canvas.addEventListener('touchstart', (e) => {
e.preventDefault()
const touch = e.touches[0]
const rect = canvas.getBoundingClientRect()
input.pointer.x = touch.clientX - rect.left
input.pointer.y = touch.clientY - rect.top
input.pointer.down = true
input.action = true
}, { passive: false })
canvas.addEventListener('touchmove', (e) => {
e.preventDefault()
const touch = e.touches[0]
const rect = canvas.getBoundingClientRect()
input.pointer.x = touch.clientX - rect.left
input.pointer.y = touch.clientY - rect.top
}, { passive: false })
canvas.addEventListener('touchend', () => {
input.pointer.down = false
input.action = false
}){ passive: false } ज़रूरी है क्योंकि मॉडर्न ब्राउज़र परफॉर्मेंस के लिए डिफ़ॉल्ट रूप से passive टच लिसनर रखते हैं। इसके बिना preventDefault काम नहीं करेगा।
मोबाइल के लिए वर्चुअल D-Pad
जिन गेम्स को मोबाइल पर दिशात्मक कंट्रोल चाहिए, उनके लिए ऑन-स्क्रीन बटन जोड़ें:
function createVirtualDpad(container) {
const dpad = document.createElement('div')
dpad.className = 'virtual-dpad'
dpad.innerHTML = `
<button data-dir="up">↑</button>
<button data-dir="left">←</button>
<button data-dir="right">→</button>
<button data-dir="down">↓</button>
`
dpad.querySelectorAll('button').forEach(btn => {
const dir = btn.dataset.dir
btn.addEventListener('touchstart', (e) => {
e.preventDefault()
input[dir] = true
})
btn.addEventListener('touchend', () => input[dir] = false)
})
container.appendChild(dpad)
}इन बटनों को इतना बड़ा स्टाइल करें कि अंगूठे के लिए सही रहें (कम से कम 44x44 पिक्सेल) और इन्हें नीचे के कोनों में रखें।
गेमपैड
कीबोर्ड और माउस के उलट, गेमपैड स्टेट इवेंट-ड्रिवन नहीं होती। आपको हर फ्रेम पर इसे पोल करना पड़ता है:
function pollGamepads() {
const gamepads = navigator.getGamepads()
const gp = gamepads[0]
if (gp) {
// D-pad या लेफ़्ट स्टिक
input.left = gp.buttons[14]?.pressed || gp.axes[0] < -0.5
input.right = gp.buttons[15]?.pressed || gp.axes[0] > 0.5
input.up = gp.buttons[12]?.pressed || gp.axes[1] < -0.5
input.down = gp.buttons[13]?.pressed || gp.axes[1] > 0.5
// A बटन (स्टैंडर्ड मैपिंग)
input.action = gp.buttons[0]?.pressed
}
}अपने update फ़ंक्शन की शुरुआत में pollGamepads() कॉल करें। एनालॉग स्टिक पर 0.5 का थ्रेशोल्ड एक डेड ज़ोन बनाता है ताकि स्टिक की छोटी-छोटी हरकतें इनपुट के रूप में रजिस्टर न हों।
आप पता लगा सकते हैं कि गेमपैड कब कनेक्ट और डिस्कनेक्ट होते हैं:
window.addEventListener('gamepadconnected', (e) => {
console.log('Gamepad connected:', e.gamepad.id)
})
window.addEventListener('gamepaddisconnected', (e) => {
console.log('Gamepad disconnected:', e.gamepad.id)
})अपने Game Loop में इनपुट इस्तेमाल करना
अब आपका update फ़ंक्शन बस इनपुट स्टेट पढ़ता है:
function update(dt) {
pollGamepads()
if (input.left) player.x -= player.speed * dt
if (input.right) player.x += player.speed * dt
if (input.up) player.y -= player.speed * dt
if (input.down) player.y += player.speed * dt
if (input.action && player.canShoot) {
player.shoot()
}
}यह तब भी काम करता है जब इनपुट कीबोर्ड, टच या गेमपैड से आया हो। गेम लॉजिक को यह जानने की ज़रूरत नहीं।
अभी दबाई गई बनाम दबाए रखी गई
कभी-कभी आप यह पता लगाना चाहते हैं कि कोई बटन इसी फ्रेम में अभी दबाई गई, न कि यह कि वह फ़िलहाल दबाए रखी गई है। जंप करना इसका अच्छा उदाहरण है: आप हर बार दबाने पर एक जंप चाहते हैं, बटन दबाए रखने पर लगातार जंप नहीं।
const inputPrev = { ...input }
function wasJustPressed(action) {
return input[action] && !inputPrev[action]
}
function endFrame() {
Object.assign(inputPrev, input)
}अपने update loop के अंत में endFrame() कॉल करें। फिर एक बार चलने वाली क्रियाओं के लिए input.action के बजाय wasJustPressed('action') इस्तेमाल करें।
फ़ोकस छूटना
जब ब्राउज़र विंडो का फ़ोकस छूट जाता है, तो कीबोर्ड इवेंट फायर होना बंद हो जाते हैं। अगर प्लेयर कोई की दबाए हुए था, तो वह अटक जाती है:
window.addEventListener('blur', () => {
input.left = input.right = input.up = input.down = input.action = false
})इसके बिना, जब प्लेयर alt-tab करके दूर जाता है तो वह मूव करता रहता है।
बेहतरीन तरीके
WASD और ऐरो कीज़ दोनों को सपोर्ट करें, क्योंकि खिलाड़ियों की अपनी पसंद होती है। रीबाइंडिंग की अनुमति दें और कॉन्फ़िगरेशन को localStorage में स्टोर करें। मोबाइल डिवाइस पर टच कंट्रोल दें। असली गेमपैड के साथ टेस्ट करें, क्योंकि Xbox, PlayStation और जेनेरिक कंट्रोलर सभी थोड़ा अलग व्यवहार करते हैं। और फ़ोकस छूटने को हमेशा सही ढंग से हैंडल करें।
और संसाधन
Gamepad API गहराई से वाइब्रेशन और कई खिलाड़ियों जैसे एडवांस्ड गेमपैड फ़ीचर कवर करता है।
मोबाइल-फ्रेंडली वेब गेम्स टच इनपुट को और विस्तार से कवर करता है।
FPS गेम्स के लिए Pointer Lock फर्स्ट-पर्सन कंट्रोल के लिए माउस को कैप्चर करना दिखाता है।
Canvas 2D game loop एक पूरे game loop में एकीकृत इनपुट स्टेट पैटर्न दिखाता है।
WebSocket मल्टीप्लेयर बेसिक्स नेटवर्क वाले खिलाड़ियों के बीच इनपुट सिंक करना कवर करता है।
बाहरी संसाधन
- MDN: KeyboardEvent.code — इनपुट मैपिंग के लिए भौतिक की वैल्यू
- MDN: Pointer events — एकीकृत माउस और टच इवेंट
- MDN: Gamepad API — ब्राउज़र में कंट्रोलर सपोर्ट
- MDN: Touch events — मोबाइल पर मल्टी-टच हैंडलिंग