केस स्टडी - शुरू हो गया है! एरिना

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

जून 2010 में, हमें पता चला कि स्थानीय तौर पर पब्लिश होने वाली "ज़ीन" बोइंग बोइंग में गेम डेवलपमेंट कॉम्पिटिशन चल रहा था. हमने देखा कि यह JavaScript और <canvas> में एक तेज़, आसान गेम बनाने को एक अच्छा बहाना था, इसलिए हमने काम करना शुरू कर दिया. प्रतियोगिता के बाद भी हमारे पास कई आइडिया थे और हम उस काम को पूरा करना चाहते थे जो हमने शुरू किया था. ये रहे नतीजे की केस स्टडी Onslated! अरीना.

रेट्रो, पिक्सलेट किया हुआ लुक

यह ज़रूरी था कि हमारे गेम का लुक और स्टाइल, पुराने अंदाज़ के Nintendo Entertainment System गेम की तरह हो. चिपच्यून पर आधारित गेम बनाने के लिए, प्रतियोगिता की बुनियाद दी गई थी. ज़्यादातर गेम में यह ज़रूरी शर्त नहीं होती, फिर भी आसानी से ऐसेट बनाने और पुराने गेमर को अपील करने में आसानी होती है. इस वजह से, गेम में आसानी से ऐसेट बनाई जा सकती हैं.

अच्छा लगा! एरिना पिक्सल के साइज़
पिक्सल का साइज़ बढ़ाने से ग्राफ़िक डिज़ाइन कम हो सकता है.

यह देखते हुए कि ये स्प्राइट कितने छोटे हैं, हमने अपने पिक्सल को दो बार बढ़ाने का फ़ैसला लिया है, जिसका मतलब है कि 16x16 स्प्राइट अब 32x32 पिक्सल वगैरह होगा. शुरुआत से ही हम ब्राउज़र से ढेर सारे काम बनाने के बजाय, एसेट बनाने के तरीके को दोगुना कर रहे हैं. इसे आसानी से लागू किया जा सकता था. साथ ही, इसके कुछ फ़ायदे भी हैं.

हमने यहां एक स्थिति के बारे में गौर किया है:

<style>
canvas {
  width: 640px;
  height: 320px;
}
</style>
<canvas width="320" height="240">
  Sorry, your browser is not supported.
</canvas>

इस तरीके में, ऐसेट बनाने वाले हिस्से में उन्हें दोगुना करने के बजाय 1x1 स्प्राइट का इस्तेमाल किया जाएगा. इसके बाद, सीएसएस उस कैनवस का साइज़ बदल देगी. हमारे मानदंडों से पता चला कि यह तरीका बड़ी (डबल-अप) इमेज को रेंडर करने की तुलना में करीब दोगुना तेज़ हो सकता है. हालांकि, सीएसएस के साइज़ में बदलाव करने में एंटी-एलियासिंग शामिल है, जिसे रोकने का कोई तरीका नहीं है.

कैनवस का साइज़ बदलने के विकल्प
बाईं ओर: फ़ोटोशॉप में पिक्सल-परफ़ेक्ट ऐसेट दोगुनी हो जाती हैं. दाएं: सीएसएस का साइज़ बदलने की सुविधा ने धुंधला इफ़ेक्ट जोड़ा है.

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

कैनवस के मज़ेदार ट्रिक

हम सभी जानते हैं कि <canvas> एक नया पसंदीदा विषय है, लेकिन कभी-कभी डेवलपर अब भी DOM का इस्तेमाल करने का सुझाव देते हैं. अगर आप यह तय कर रहे हैं कि किस सुविधा का इस्तेमाल करना है, तो यहां एक उदाहरण देखें कि <canvas> ने हमारे लिए कितना समय और ऊर्जा बचाई.

जब ऑन्सलाट! में दुश्मन को टक्कर हो! अरीना में, उसमें लाल रंग की चमक दिखती है और बस एक "पेन" ऐनिमेशन दिखता है. हम दुश्मनों को सिर्फ़ नीचे की ओर "पेन" में दिखाते हैं, ताकि उन्हें बनाए जाने वाले ग्राफ़िक की संख्या को सीमित किया जा सके. यह गेम में सही लगता है और स्प्राइट बनाने में काफ़ी समय लगता है. हालांकि, बॉस मॉन्स्टर को बड़े स्प्राइट (64x64 पिक्सल या उससे ज़्यादा) की ओर देखना मुश्किल था. दर्द का फ़्रेम देखने के लिए, स्क्रीन बाईं ओर से ऊपर या अचानक नीचे की ओर थी.

हर आठ बॉस के लिए एक मुश्किल तरीका तय करना एक मुश्किल तरीका होगा. हालांकि, इसमें बहुत समय लगता होगा. <canvas> का धन्यवाद, हमने कोड में इस समस्या को हल कर लिया है:

Onslate में दर्शकों को नुकसान उठाना पड़ रहा है! एरिना
context.globalCompositeOperation का इस्तेमाल करके, दिलचस्प इफ़ेक्ट बनाए जा सकते हैं.

सबसे पहले हम मॉन्स्टर को एक छिपे हुए "बफ़र" <canvas> पर ले जाते हैं, उसे लाल रंग से ओवरले करते हैं, फिर परिणाम को वापस स्क्रीन पर रेंडर करते हैं. कोड कुछ ऐसा दिखता है:

// Get the "buffer" canvas (that isn't visible to the user)
var bufferCanvas = document.getElementById("buffer");
var buffer = bufferCanvas.getContext("2d");

// Draw your image on the buffer
buffer.drawImage(image, 0, 0);

// Draw a rectangle over the image using a nice translucent overlay
buffer.save();
buffer.globalCompositeOperation = "source-in";
buffer.fillStyle = "rgba(186, 51, 35, 0.6)"; // red
buffer.fillRect(0, 0, image.width, image.height);
buffer.restore();

// Copy the buffer onto the visible canvas
document.getElementById("stage").getContext("2d").drawImage(bufferCanvas, x, y);

गेम लूप

गेम डेवलपमेंट और वेब डेवलपमेंट में कुछ खास अंतर हैं. वेब स्टैक में, इवेंट लिसनर के ज़रिए हुए इवेंट पर प्रतिक्रिया देना सामान्य बात है. इसलिए, शुरू करने का कोड सिर्फ़ इनपुट इवेंट को सुनने के अलावा कुछ और नहीं कर सकता. गेम का लॉजिक अलग होता है, क्योंकि लगातार अपडेट होना ज़रूरी है. उदाहरण के लिए, अगर कोई खिलाड़ी हिल-डुल नहीं रहा है, तो भी उसे गॉब्लिन हासिल करने से नहीं रोका जा सकता!

यहां गेम लूप का एक उदाहरण दिया गया है:

function main () {
  handleInput();
  update();
  render();
};

setInterval(main, 1);

पहला ज़रूरी अंतर यह है कि handleInput फ़ंक्शन, असल में तुरंत कुछ नहीं करता है. अगर कोई उपयोगकर्ता किसी सामान्य वेब ऐप्लिकेशन में कोई कुंजी दबाता है, तो तुरंत कार्रवाई करना सही होता है. हालांकि, किसी गेम में चीज़ों को सही क्रम में चलाने के लिए, क्रम के हिसाब से क्रम में होनी चाहिए.

window.addEventListener("mousedown", function(e) {
  // A mouse click means the players wants to attack.
  // We don't actually do that yet, but instead tell the rest
  // of the program about the request.
  buttonStates[e.button] = true;
}, false);

function handleInput() {
  // Here is where we respond to the click
  if (buttonStates[LEFT_BUTTON]) {
    player.attacking = true;
    delete buttonStates[LEFT_BUTTON];
  }
};

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

function update() {
  // Check for collisions, states, whatever else is needed

  // If after that the player can still attack, do it!
  if (player.attacking && player.canAttack()) {
    player.attack();
  }
};

आखिर में, सभी चीज़ों का हिसाब लगाने के बाद, स्क्रीन फिर से तैयार करने का समय आ गया है! DOM-land में, ब्राउज़र इस हेविंग लिफ़्टिंग को हैंडल करता है. हालांकि, <canvas> का इस्तेमाल करते समय, अगर कुछ होता है, तो मैन्युअल तरीके से फिर से ड्रॉ करना ज़रूरी होता है (आम तौर पर, यह हर एक फ़्रेम के लिए होता है!).

function render() {
  // First erase everything, something like:
  context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  // Draw the player (and whatever else you need)
  context.drawImage(
    player.getImage(),
    player.x, player.y
  );
};

समय पर आधारित मॉडलिंग

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

समय-आधारित मॉडलिंग का इस्तेमाल करने के लिए, हमें आखिरी फ़्रेम बनाए जाने के बाद से बीते समय को कैप्चर करना होगा. इसे ट्रैक करने के लिए, हमें अपने गेम लूप के update() फ़ंक्शन को बेहतर बनाना होगा.

function update() {

  // NOTE: You'll need to initially seed this.lastUpdate
  // with the current time when your game loop starts
  // this.lastUpdate = Date.now();

  // Calculate elapsed time since last frame
  var now = Date.now();
  var elapsed = (now - this.lastUpdate);
  this.lastUpdate = now;

  // Do stuff with elapsed

};

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

var Sprite = function() {

  // The sprite's position relative to the top left of the game world
  this.position = {x: 0, y: 0};

  // The sprite's direction. A positive x value indicates moving to the right
  this.direction = {x: 1, y: 0};

  // How many pixels the sprite moves per second
  this.speed = 50;
};

इन वैरिएबल को ध्यान में रखते हुए यहां बताया गया है कि हम समय पर आधारित मॉडलिंग का इस्तेमाल करके, ऊपर दिए गए स्प्राइट क्लास के इंस्टेंस को कैसे ट्रांसफ़र करेंगे:

// Determine how far this sprite will move this frame
var distance = (sprite.speed / 1000) * elapsed;

// Apply the movement distance to the sprite's current position
// taking into account its direction
sprite.position.x += (distance * sprite.direction.x);
sprite.position.y += (distance * sprite.direction.y);

ध्यान दें कि direction.x और direction.y की वैल्यू सामान्य होनी चाहिए. इसका मतलब है कि वे हमेशा -1 और 1 के बीच होनी चाहिए.

कंट्रोल

ऑनस्लीट! अरीना. पहले डेमो में सिर्फ़ कीबोर्ड इस्तेमाल हुआ. प्लेयर ने मुख्य किरदार को ऐरो बटन से स्क्रीन पर इधर-उधर रखा और स्पेस बार की ओर देखा. यह गेम थोड़ा आसान और समझने में आसान था, लेकिन इससे ज़्यादा मुश्किल लेवल पर यह गेम करीब-करीब नामुमकिन हो गया. जब खिलाड़ियों पर हर समय हमला करने वाले ढेरों दुश्मनों और गोलियों को उड़ाया जाता है, तब किसी भी दिशा में गोली चलाने के दौरान बदमाशों के बीच लड़ाई करने के लिए यह ज़रूरी है.

इस शैली के मिलते-जुलते गेम से तुलना करने के लिए, हमने टारगेट किए गए रेटिकल को कंट्रोल करने के लिए माउस की सुविधा जोड़ी है. वह किरदार इस गेम का इस्तेमाल अपने हमलों को निशाना बनाने के लिए करेगा. किरदार को अब भी कीबोर्ड से चलाया जा सकता था, लेकिन इस बदलाव के बाद वह एक साथ किसी भी 360-डिग्री में फ़ायर कर सकता था. हार्डकोर (पेशेवर) खिलाड़ियों ने इस सुविधा की बहुत तारीफ़ की, लेकिन इसके बुरे असर का बुरा असर पड़ा, क्योंकि यह सुविधा ट्रैकपैड का इस्तेमाल करने वाले लोगों को बहुत पसंद आई.

अच्छा लगा! अरीना कंट्रोल मोडल (अब सेवा में नहीं है)
ऑन्सलीट में पुराने कंट्रोल या "कैसे खेलें" मोडल! अरीना.

ट्रैकपैड इस्तेमाल करने वालों को शामिल करने के लिए, हमने ऐरो बटन के कंट्रोल फिर से उपलब्ध कराए हैं, ताकि इस बार दबाए गए दिशा(निर्देशों) से फ़ायर करने की अनुमति मिल सके. हालांकि हमें लगा कि हम सभी तरह के खिलाड़ियों को उपलब्ध करा रहे हैं, लेकिन अनजाने में हम अपने गेम को बहुत जटिल बना रहे थे. हमें हैरान करने के लिए, बाद में पता चला कि कुछ खिलाड़ियों को ट्यूटोरियल मोडल के बावजूद, हमला करने के लिए वैकल्पिक माउस (या कीबोर्ड!) कंट्रोल के बारे में जानकारी नहीं थी.

अच्छा लगा! अरीना के कंट्रोल से जुड़ा ट्यूटोरियल
खिलाड़ी, ज़्यादातर ट्यूटोरियल ओवरले को अनदेखा कर देते हैं. वे खेलना और आनंद लेना पसंद करते हैं!

हम खुशकिस्मत हैं कि हमारे पास कुछ यूरोप के प्रशंसक हैं, लेकिन हमें भी उन्हें परेशानी हुई कि शायद उनमें आम तौर पर QWERTY कीबोर्ड नहीं हैं और वे दिशा बदलने के लिए WASD बटन का इस्तेमाल नहीं कर पा रहे हैं. बाएं हाथ के खिलाड़ियों ने इसी तरह की शिकायतें की हैं.

इस जटिल कंट्रोल स्कीम की वजह से, मोबाइल डिवाइसों पर वीडियो चलाने में भी समस्या आती है. हमारे सबसे आम अनुरोधों में से एक है ऑन्सलेट! अरीना, Android, iPad और दूसरे टच डिवाइसों (जहां कोई कीबोर्ड नहीं होता) पर उपलब्ध है. HTML5 की सबसे अहम बात है इसका पोर्टेबिलिटी, इसलिए इन डिवाइसों पर गेम पाना बेहद आसान है. हमें बस कई समस्याओं को हल करना है. इनमें कंट्रोल और परफ़ॉर्मेंस सबसे ज़्यादा ज़रूरी है.

इन समस्याओं को हल करने के लिए, हमने गेम प्ले के सिंगल-इनपुट तरीके से खेलना शुरू किया, जिसमें सिर्फ़ माउस (या टच) इंटरैक्शन शामिल था. प्लेयर स्क्रीन को क्लिक या छूता है और मुख्य किरदार, दबाई गई जगह की ओर अपने-आप हमला करता है और सबसे नज़दीकी बुरे व्यक्ति पर हमला करता है. कोड कुछ ऐसा दिखता है:

// Find the nearest hostile target (if any) to the player
var player = this.getPlayerObject();
var hostile = this.getNearestHostile(player);
if (hostile !== null) {
  // Found one! Shoot in its direction
  var shoot = hostile.boundingBox().center().subtract(
    player.boundingBox().center()
  ).normalize();
}

// Move towards where the player clicked/touched
var move = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).normalize();
var distance = this.targetReticle.position.clone().subtract(
  player.boundingBox().center()
).magnitude();

// Prevent jittering if the character is close enough
if (distance < 3) {
  move.zero();
}

// Move the player
if ((move.x !== 0) || (move.y !== 0)) {
  player.setDirection(move);
}

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

ऑडियो

कंट्रोल और परफ़ॉर्मेंस के मामले में, ऑनस्लीट! अरीना, HTML5 का <audio> टैग था. शायद सबसे खराब पहलू इंतज़ार का समय है: करीब सभी ब्राउज़र में, .play() को कॉल करने और असल में चलने वाली आवाज़ के बीच देरी होती है. ऐसा करने से गेमर का अनुभव खराब हो सकता है. खास तौर पर, जब हम हमारे जैसे तेज़ रफ़्तार वाले गेम को खेल रहे हों.

दूसरी समस्याओं में "प्रोग्रेस" इवेंट फ़ायर न होना शामिल है. इस वजह से गेम का लोडिंग फ़्लो हमेशा रुक सकता है. इन वजहों से, हमने "फ़ॉल-फ़ॉरवर्ड" मेथड अपनाया है. अगर फ़्लैश लोड नहीं हो पाता है, तो हम HTML5 ऑडियो पर स्विच करते हैं. कोड कुछ ऐसा दिखता है:

/*
This example uses the SoundManager 2 library by Scott Schiller:
http://www.schillmania.com/projects/soundmanager2/
*/

// Default to sm2 (Flash)
var api = "sm2";

function initAudio (callback) {
  switch (api) {
    case "sm2":
      soundManager.onerror = (function (init) {
        return function () {
          api = "html5";
          init(callback);
        };
      }(arguments.callee));
      break;
    case "html5":
      var audio = document.createElement("audio");

      if (
        audio
        && audio.canPlayType
        && audio.canPlayType("audio/mpeg;")
      ) {
        callback();
      } else {
        // No audio support :(
      }
      break;
  }
};

किसी गेम के लिए उन ब्राउज़र पर काम करना भी ज़रूरी हो सकता है जिनमें MP3 फ़ाइलें नहीं चलें (जैसे कि Mozilla Firefox). अगर ऐसा है, तो इस तरह के कोड के साथ, सहायता की पहचान की जा सकती है और उसे Ogg Vorbis जैसे कोड पर स्विच किया जा सकता है:

/*
Note: you could instead use "new Audio()" here,
but the client will throw an error if it doesn't support Audio,
which makes using "document.createElement" a safer approach.
*/

var audio = document.createElement("audio");

if (audio && audio.canPlayType) {
  if (!audio.canPlayType("audio/mpeg;")) {
    // Here you know you CANNOT use .mp3 files
    if (audio.canPlayType("audio/ogg; codecs=vorbis")) {
      // Here you know you CAN use .ogg files
    }
  }
}

डेटा सेव किया जा रहा है

ज़्यादा स्कोर के बिना, आर्केड-स्टाइल वाले शूटिंग गेम नहीं किए जा सकते! हम जानते थे कि हमें अपने कुछ गेम डेटा को बने रहने की ज़रूरत होगी और हम कुकी जैसी पुरानी चीज़ों का इस्तेमाल कर सकते थे, लेकिन हम नई HTML5 टेक्नोलॉजी के बारे में ज़्यादा गहराई से जानना चाहते थे. लोकल स्टोरेज, सेशन स्टोरेज, और वेब एसक्यूएल डेटाबेस जैसे विकल्पों की कमी नहीं है.

ALT_TEXT_HERE
सबसे ज़्यादा स्कोर सेव किए जाते हैं. साथ ही, हर बॉस को हराने के बाद गेम में आपकी जगह बनी रहती है.

हमने localStorage इस्तेमाल करने का फ़ैसला किया, क्योंकि यह नया, शानदार, और इस्तेमाल में आसान है. इससे बुनियादी की/वैल्यू पेयर को सेव किया जा सकता है, जो हमारे सभी आसान गेम की ज़रूरत है. यहां इसे इस्तेमाल करने का आसान उदाहरण दिया गया है:

if (typeof localStorage == "object") {
  localStorage.setItem("foo", "bar");
  localStorage.getItem("foo"); // Value is "bar"
  localStorage.removeItem("foo");
  localStorage.getItem("foo"); // Value is now null
}

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

localStorage.setItem("foo", false);
typeof localStorage.getItem("foo"); // Value is "false" (a string literal)
if (localStorage.getItem("foo")) {
  // It's true!
}

// Don't pass objects into setItem
localStorage.setItem("bar", {"key": "value"});
localStorage.getItem("bar"); // Value is "[object Object]" (a string literal)

// JSON stringify and parse when dealing with localStorage
localStorage.setItem("json", JSON.stringify({"key": "value"}));
typeof localStorage.getItem("json"); // string
JSON.parse(localStorage.getItem("json")); // {"key": "value"}

खास जानकारी

HTML5 के साथ काम करना शानदार है. ज़्यादातर मामलों में, गेम डेवलपर की ज़रूरत के मुताबिक ग्राफ़िक और गेम की स्थिति सेव करने जैसी हर चीज़ काम करती है. हालांकि, कुछ समस्याएं बढ़ रही हैं (जैसे कि <audio> टैग की समस्या), ब्राउज़र डेवलपर तेज़ी से आगे बढ़ रहे हैं और चीज़ें पहले से ही बेहतर हैं. इसलिए, HTML5 पर बने गेम के लिए, आने वाला समय बेहतर हो सकता है.

अच्छा लगा! छिपे हुए HTML5 लोगो वाला अरीना
Onslutter गेम खेलते समय "html5" टाइप करके, आपको HTML5 शील्ड मिल सकती है! अरीना.