HTML5 गेम के लिए टीयर गाइड नहीं है

डैनियल एक्स. Moore
डैनियल एक्स. मूर

शुरुआती जानकारी

क्या आपको कैनवस और HTML5 का इस्तेमाल करके गेम बनाना है? इस ट्यूटोरियल के साथ आगे बढ़ें और आप कुछ ही समय में अपना सफ़र पूरा करने के लिए तैयार हो जाएंगे.

ट्यूटोरियल में JavaScript का लेवल कम से कम इंटरमीडिएट लेवल का होना ज़रूरी है.

सबसे पहले, गेम खेलें या सीधे लेख पर जाएं और गेम का सोर्स कोड देखें.

कैनवस बनाना

चीज़ों को बनाने के लिए, हमें एक कैनवस बनाना होगा. यह 'कोई टियर नहीं' गाइड है. इसलिए, हम jQuery का इस्तेमाल करेंगे.

var CANVAS_WIDTH = 480;
var CANVAS_HEIGHT = 320;

var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
                      "' height='" + CANVAS_HEIGHT + "'></canvas>");
var canvas = canvasElement.get(0).getContext("2d");
canvasElement.appendTo('body');

गेम लूप

गेम को आसान और लगातार बनाए रखने वाले गेमप्ले को सिम्युलेट करने के लिए, हम गेम को अपडेट करना चाहते हैं और स्क्रीन को तेज़ी से फिर से तैयार करना चाहते हैं. इससे इंसानों के दिमाग और आंखों के हिसाब से गेम हट जाएगा.

var FPS = 30;
setInterval(function() {
  update();
  draw();
}, 1000/FPS);

फ़िलहाल, हम अपडेट को छोड़ सकते हैं और तरीकों को खाली छोड़ सकते हैं. अहम बात यह है कि setInterval() समय-समय पर उन्हें कॉल करते हैं.

function update() { ... }
function draw() { ... }

नमस्ते दुनिया

अब गेम लूप चल रहा है, तो स्क्रीन पर कुछ टेक्स्ट बनाने के लिए, ड्रॉ करने के अपने तरीके को अपडेट करते हैं.

function draw() {
  canvas.fillStyle = "#000"; // Set color to black
  canvas.fillText("Sup Bro!", 50, 50);
}

स्टेशनरी टेक्स्ट के लिए यह तो बहुत अच्छा है, लेकिन क्योंकि हमारे पास गेम लूप पहले से सेट अप है, इसलिए हम इसे बहुत आसानी से मूव कर देंगे.

var textX = 50;
var textY = 50;

function update() {
  textX += 1;
  textY += 1;
}

function draw() {
  canvas.fillStyle = "#000";
  canvas.fillText("Sup Bro!", textX, textY);
}

ज़रा रुकिए. अगर आपको इसे फ़ॉलो करना है, तो यह चलता रहेगा. साथ ही, यह स्क्रीन पर पिछली बार ड्रॉ किए जाने के समय को भी छोड़ देगा. कुछ समय निकालकर सोचें कि ऐसा क्यों हो सकता है. ऐसा इसलिए है, क्योंकि हम स्क्रीन पर डेटा नहीं मिटा रहे हैं. इसलिए, ड्रॉइंग बनाने के तरीके में स्क्रीन पर मौजूद डेटा मिटाने वाला कुछ कोड जोड़ते हैं.

function draw() {
  canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  canvas.fillStyle = "#000";
  canvas.fillText("Sup Bro!", textX, textY);
}

स्क्रीन पर कुछ टेक्स्ट दिखने शुरू हो गए हैं. इसका मतलब है कि गेम खेलने में कोई मशवरा होने वाला है. बस कंट्रोल ठीक करें, गेमप्ले को बेहतर बनाएं, और ग्राफ़िक को बेहतर बनाएं. ठीक है, असल गेम खेलने का एक/सातवां हिस्सा, लेकिन अच्छी बात यह है कि ट्यूटोरियल में अब बहुत कुछ है.

प्लेयर बनाया जा रहा है

प्लेयर का डेटा रखने के लिए एक ऑब्जेक्ट बनाएं और ड्रॉइंग जैसी चीज़ों के लिए ज़िम्मेदार बनें. यहां हमने एक प्लेयर ऑब्जेक्ट बनाया है. इसमें एक सामान्य ऑब्जेक्ट लिटरल वैल्यू का इस्तेमाल करके, पूरी जानकारी सेव की जाती है.

var player = {
  color: "#00A",
  x: 220,
  y: 270,
  width: 32,
  height: 32,
  draw: function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  }
};

फ़िलहाल, हम प्लेयर को दिखाने के लिए एक साधारण रंग वाले रेक्टैंगल का इस्तेमाल कर रहे हैं. गेम की ड्रॉ करने पर, हम कैनवस को साफ़ कर देंगे और प्लेयर को ड्रॉ कर देंगे.

function draw() {
  canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  player.draw();
}

कीबोर्ड नियंत्रण

jQuery हॉटकी का इस्तेमाल करना

jQuery Hotkeys प्लगिन की मदद से, सभी ब्राउज़र पर कुंजियों को मैनेज करना बहुत आसान हो जाता है. क्रॉस-ब्राउज़र keyCode और charCode की ऐसी समस्याओं के लिए शिकायत करने के बजाय जिन्हें समझा नहीं जा सकता. हम इन समस्याओं को इस तरह से बाध्य कर सकते हैं:

$(document).bind("keydown", "left", function() { ... });

आपको इस बात की फ़िक्र नहीं करनी पड़ती कि कौनसे कोड कौनसे कोड मौजूद हैं. हम बस इतना कहना चाहते हैं कि "जब प्लेयर ऊपर का बटन दबाएं, तो कुछ करें." jQuery Hotkeys ठीक से ऐसा कर सकता है.

खिलाड़ियों की गतिविधि

JavaScript, कीबोर्ड इवेंट को जिस तरह से हैंडल करता है वह पूरी तरह से इवेंट पर आधारित होता है. इसका मतलब है कि ऐसी कोई क्वेरी नहीं है जिससे यह पता किया जा सके कि कोई कुंजी काम कर रही है या नहीं. इसलिए, हमें अपनी कुंजी का इस्तेमाल करना होगा.

आपके मन में यह सवाल आ सकता है, "क्यों न सिर्फ़ इवेंट के आधार पर कुंजियों को मैनेज करने के तरीके का इस्तेमाल किया जाए?" इसकी वजह यह है कि कीबोर्ड को दोहराने की दर, सिस्टम के हिसाब से अलग-अलग होती है और गेम लूप के समय के हिसाब से नहीं होती. इसलिए, हर सिस्टम के हिसाब से गेमप्ले में बहुत ज़्यादा अंतर हो सकता है. एक जैसा अनुभव देने के लिए, यह ज़रूरी है कि कीबोर्ड इवेंट की पहचान करने की सुविधा, गेम लूप के साथ अच्छी तरह से इंटिग्रेट हो.

अच्छी खबर यह है कि मैंने 16-लाइन वाला JS रैपर शामिल किया है, जिससे इवेंट क्वेरी उपलब्ध हो पाएगी. इसे key_status.js कहा जाता है. साथ ही, keydown.left वगैरह पर क्लिक करके, किसी भी समय कुंजी की स्थिति के बारे में क्वेरी की जा सकती है.

अब हमारे पास यह क्वेरी करने की सुविधा है कि बटन काम नहीं कर रहे हैं या नहीं, अब हम प्लेयर को इधर-उधर ले जाने के लिए अपडेट करने के इस आसान तरीके का इस्तेमाल कर सकते हैं.

function update() {
  if (keydown.left) {
    player.x -= 2;
  }

  if (keydown.right) {
    player.x += 2;
  }
}

आगे बढ़ें और इसे आज़माएं.

ऐसा हो सकता है कि प्लेयर को स्क्रीन से हटाया जा सके. खिलाड़ी को सीमा में बनाए रखने के लिए, उसकी पोज़िशन तय करें. इसके अलावा, प्लेयर धीमा लग रहा है, इसलिए स्पीड भी बढ़ा दें.

function update() {
  if (keydown.left) {
    player.x -= 5;
  }

  if (keydown.right) {
    player.x += 5;
  }

  player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}

और इनपुट जोड़ना भी उतना ही आसान होगा, इसलिए आइए कुछ प्रोजेक्टाइल जोड़ें.

function update() {
  if (keydown.space) {
    player.shoot();
  }

  if (keydown.left) {
    player.x -= 5;
  }

  if (keydown.right) {
    player.x += 5;
  }

  player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}

player.shoot = function() {
  console.log("Pew pew");
  // :) Well at least adding the key binding was easy...
};

ज़्यादा गेम ऑब्जेक्ट जोड़ना

प्रोजेक्टाइल

आइए, अब प्रोजेक्टाइल को असल में जोड़ देते हैं. पहले हमें एक संग्रह की ज़रूरत होगी, ताकि उन सभी को यहां सेव किया जा सके:

var playerBullets = [];

इसके बाद, बुलेट इंस्टेंस बनाने के लिए हमें एक कंस्ट्रक्टर की ज़रूरत होगी.

function Bullet(I) {
  I.active = true;

  I.xVelocity = 0;
  I.yVelocity = -I.speed;
  I.width = 3;
  I.height = 3;
  I.color = "#000";

  I.inBounds = function() {
    return I.x >= 0 && I.x <= CANVAS_WIDTH &&
      I.y >= 0 && I.y <= CANVAS_HEIGHT;
  };

  I.draw = function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  };

  I.update = function() {
    I.x += I.xVelocity;
    I.y += I.yVelocity;

    I.active = I.active && I.inBounds();
  };

  return I;
}

जब प्लेयर शूट करता है, तब हमें एक बुलेट इंस्टेंस बनाना चाहिए और उसे बुलेट के कलेक्शन में जोड़ना चाहिए.

player.shoot = function() {
  var bulletPosition = this.midpoint();

  playerBullets.push(Bullet({
    speed: 5,
    x: bulletPosition.x,
    y: bulletPosition.y
  }));
};

player.midpoint = function() {
  return {
    x: this.x + this.width/2,
    y: this.y + this.height/2
  };
};

अब हमें स्टेप फ़ंक्शन को अपडेट करने के लिए, बुलेट पॉइंट को अपडेट करना होगा. बुलेट को इकट्ठा होने से रोकने के लिए, हम बुलेट की सूची को फ़िल्टर करते हैं, ताकि सिर्फ़ चालू बुलेट को शामिल किया जा सके. इससे हम उन गोलियों को भी हटा पाते हैं जो किसी दुश्मन से टकरा गई थीं.

function update() {
  ...
  playerBullets.forEach(function(bullet) {
    bullet.update();
  });

  playerBullets = playerBullets.filter(function(bullet) {
    return bullet.active;
  });
}

आखिरी चरण है बुलेट बनाना:

function draw() {
  ...
  playerBullets.forEach(function(bullet) {
    bullet.draw();
  });
}

शत्रु

अब समय आ गया है कि दुश्मनों को वैसे ही जोड़ा जाए जैसे हमने बुलेट जोड़ा था.

  enemies = [];

function Enemy(I) {
  I = I || {};

  I.active = true;
  I.age = Math.floor(Math.random() * 128);

  I.color = "#A2B";

  I.x = CANVAS_WIDTH / 4 + Math.random() * CANVAS_WIDTH / 2;
  I.y = 0;
  I.xVelocity = 0
  I.yVelocity = 2;

  I.width = 32;
  I.height = 32;

  I.inBounds = function() {
    return I.x >= 0 && I.x <= CANVAS_WIDTH &&
      I.y >= 0 && I.y <= CANVAS_HEIGHT;
  };

  I.draw = function() {
    canvas.fillStyle = this.color;
    canvas.fillRect(this.x, this.y, this.width, this.height);
  };

  I.update = function() {
    I.x += I.xVelocity;
    I.y += I.yVelocity;

    I.xVelocity = 3 * Math.sin(I.age * Math.PI / 64);

    I.age++;

    I.active = I.active && I.inBounds();
  };

  return I;
};

function update() {
  ...

  enemies.forEach(function(enemy) {
    enemy.update();
  });

  enemies = enemies.filter(function(enemy) {
    return enemy.active;
  });

  if(Math.random() < 0.1) {
    enemies.push(Enemy());
  }
};

function draw() {
  ...

  enemies.forEach(function(enemy) {
    enemy.draw();
  });
}

इमेज लोड करना और ड्रॉइंग करना

उन सभी बक्सों को इधर-उधर उड़ते हुए देखना अच्छा लगता है, लेकिन उनके लिए इमेज होना और भी बेहतर होगा. कैनवस पर इमेज लोड करने और बनाने का काम आम तौर पर आंसू बहाने वाला अनुभव होता है. उस दर्द और दुख से बचने के लिए, हम एक सामान्य उपयोगिता क्लास इस्तेमाल कर सकते हैं.

player.sprite = Sprite("player");

player.draw = function() {
  this.sprite.draw(canvas, this.x, this.y);
};

function Enemy(I) {
  ...

  I.sprite = Sprite("enemy");

  I.draw = function() {
    this.sprite.draw(canvas, this.x, this.y);
  };

  ...
}

टकराने का पता लगाने की सुविधा

हमें ये सभी डील स्क्रीन पर मिली हैं, लेकिन वे एक-दूसरे से इंटरैक्ट नहीं कर रही हैं. सभी चीज़ों के बारे में जानने के लिए, हमें टक्कर का पता लगाने वाली सुविधा जोड़नी होगी.

आइए, आयताकार टकराव का पता लगाने वाले एक आसान एल्गोरिदम का इस्तेमाल करते हैं:

function collides(a, b) {
  return a.x < b.x + b.width &&
         a.x + a.width > b.x &&
         a.y < b.y + b.height &&
         a.y + a.height > b.y;
}

हमें कुछ टकरावों की जांच करनी है:

  1. खिलाड़ी बुलेट => दुश्मन के जहाज़
  2. खिलाड़ी => दुश्मन के जहाज़

आइए, टकरावों को मैनेज करने का एक तरीका बनाएं. इसे अपडेट करने के तरीके से कॉल किया जा सकता है.

function handleCollisions() {
  playerBullets.forEach(function(bullet) {
    enemies.forEach(function(enemy) {
      if (collides(bullet, enemy)) {
        enemy.explode();
        bullet.active = false;
      }
    });
  });

  enemies.forEach(function(enemy) {
    if (collides(enemy, player)) {
      enemy.explode();
      player.explode();
    }
  });
}

function update() {
  ...
  handleCollisions();
}

अब हमें खिलाड़ी और दुश्मनों के लिए विस्फोट करने वाले तरीके जोड़ने होंगे. इससे उन्हें हटाने के लिए फ़्लैग किया जाएगा और धमाका होगा.

function Enemy(I) {
  ...

  I.explode = function() {
    this.active = false;
    // Extra Credit: Add an explosion graphic
  };

  return I;
};

player.explode = function() {
  this.active = false;
  // Extra Credit: Add an explosion graphic and then end the game
};

आवाज़

इस अनुभव को बेहतर बनाने के लिए, हम उसमें कुछ मधुर साउंड इफ़ेक्ट जोड़ रहे हैं. इमेज की तरह ही आवाज़ को HTML5 में इस्तेमाल करना थोड़ा मुश्किल हो सकता है, लेकिन हमारे जादू के तौर पर बिना आंसू वाले फ़ॉर्मूला Sound.js की मदद से, आवाज़ को बेहद आसान बनाया जा सकता है.

player.shoot = function() {
  Sound.play("shoot");
  ...
}

function Enemy(I) {
  ...

  I.explode = function() {
    Sound.play("explode");
    ...
  }
}

हालांकि, इस एपीआई में अब कोई रुकावट नहीं है, फिर भी आवाज़ जोड़ना, ऐप्लिकेशन को क्रैश करने का सबसे तेज़ तरीका है. ब्राउज़र का पूरा टैब बंद हो जाता है या बंद हो जाता है, इसलिए अपने टिशू तैयार कर लें.

विदाई

फिर से, यहां गेम का पूरा डेमो दिया गया है. सोर्स कोड को ज़िप के तौर पर भी डाउनलोड किया जा सकता है.

उम्मीद है कि आपको JavaScript और HTML5 में आसान गेम बनाने की बुनियादी बातें सीखने में आनंद आया होगा. ऐब्स्ट्रैक्टेशन के सही लेवल पर प्रोग्रामिंग करके, हम एपीआई के मुश्किल हिस्सों से खुद को दूर कर सकते हैं. साथ ही, आने वाले समय में होने वाले बदलावों के हिसाब से खुद को तैयार कर सकते हैं.

References