केस स्टडी - वेब ऑडियो वाले HTML5 गेम की कहानी

Fieldrunners

Fieldrunners का स्क्रीनशॉट
Fieldrunners का स्क्रीनशॉट

Fieldrunners, टावर की सुरक्षा वाला एक अवॉर्ड-विनिंग गेम है. इसे साल 2008 में, iPhone के लिए रिलीज़ किया गया था. इसके बाद, इसे कई अन्य प्लैटफ़ॉर्म पर पोर्ट किया गया. अक्टूबर 2011 में, Chrome ब्राउज़र को सबसे नए प्लैटफ़ॉर्म के तौर पर लॉन्च किया गया था. Fieldrunners को HTML5 प्लैटफ़ॉर्म पर पोर्ट करने में एक चुनौती यह थी कि साउंड कैसे चलाया जाए.

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

कुछ समस्याएं आईं

Fieldrunners को HTML5 में पोर्ट करते समय, हमें ऑडियो टैग के साथ ऑडियो चलाने में समस्याएं आईं. इसलिए, हमने शुरुआत में ही Web Audio API पर फ़ोकस करने का फ़ैसला लिया. WebAudio का इस्तेमाल करने से, हमें कई समस्याओं को हल करने में मदद मिली. जैसे, Fieldrunners के लिए एक साथ कई इफ़ेक्ट चलाने की सुविधा देना. हालांकि, Fieldrunners HTML5 के लिए ऑडियो सिस्टम डेवलप करते समय, हमें कुछ ऐसी समस्याएं आईं जिनके बारे में अन्य डेवलपर को पता होना चाहिए.

AudioBufferSourceNodes की प्रकृति

WebAudio की मदद से साउंड चलाने के लिए, AudioBufferSourceNodes मुख्य तरीका है. यह समझना बहुत ज़रूरी है कि इनका इस्तेमाल सिर्फ़ एक बार किया जा सकता है. आपको एक AudioBufferSourceNode बनाना होगा, उसे एक बफ़र असाइन करना होगा, उसे ग्राफ़ से कनेक्ट करना होगा, और उसे noteOn या noteGrainOn के साथ चलाना होगा. इसके बाद, प्लेबैक को रोकने के लिए noteOff को कॉल किया जा सकता है. हालांकि, noteOn या noteGrainOn को कॉल करके सोर्स को फिर से नहीं चलाया जा सकेगा. इसके लिए, आपको दूसरा AudioBufferSourceNode बनाना होगा. आपके पास एक ही AudioBuffer ऑब्जेक्ट को फिर से इस्तेमाल करने का विकल्प होता है. हालांकि, आपके पास एक ही AudioBuffer इंस्टेंस पर ले जाने वाले कई चालू AudioBufferSourceNodes भी हो सकते हैं! Give Me a Beat में, Fieldrunners का एक प्लेबैक स्निपेट देखा जा सकता है.

कैश मेमोरी में सेव न किया जाने वाला कॉन्टेंट

Fieldrunners HTML5 सर्वर के रिलीज़ होने पर, म्यूज़िक फ़ाइलों के लिए बड़ी संख्या में अनुरोध मिले. यह नतीजा, Chrome 15 के फ़ाइल को अलग-अलग हिस्सों में डाउनलोड करने और फिर उसे कैश मेमोरी में सेव न करने की वजह से मिला. इसके जवाब में, हमने म्यूज़िक फ़ाइलों को अपनी अन्य ऑडियो फ़ाइलों की तरह लोड करने का फ़ैसला लिया. ऐसा करना सही नहीं है, लेकिन कुछ अन्य ब्राउज़र के कुछ वर्शन अब भी ऐसा करते हैं.

फ़ोकस में न होने पर साइलेंट करना

पहले, यह पता लगाना मुश्किल था कि आपके गेम का टैब कब फ़ोकस से बाहर है. Fieldrunners ने Chrome 13 से पहले ही पोर्ट करना शुरू कर दिया था. इसमें Page Visibility API ने टैब के धुंधले होने का पता लगाने के लिए, हमारे जटिल कोड की ज़रूरत को बदल दिया. हर गेम को विज़िबिलिटी एपीआई का इस्तेमाल करके, एक छोटा स्निपेट लिखना चाहिए. इससे गेम की आवाज़ को म्यूट या रोके जाने की सुविधा मिलती है. भले ही, पूरे गेम को रोकने की सुविधा न हो. Fieldrunners ने requestAnimationFrame एपीआई का इस्तेमाल किया था. इसलिए, गेम को रोकने की सुविधा अपने-आप मैनेज हो गई थी, लेकिन आवाज़ को रोकने की सुविधा नहीं.

आवाज़ें रोकी जा रही हैं

इस लेख के बारे में सुझाव/राय लेते समय, हमें पता चला कि आवाज़ों को रोकने के लिए हम जिस तकनीक का इस्तेमाल कर रहे थे वह सही नहीं थी. हम आवाज़ों के प्लेबैक को रोकने के लिए, वेब ऑडियो के मौजूदा वर्शन में मौजूद एक गड़बड़ी का इस्तेमाल कर रहे थे. आने वाले समय में इसे ठीक कर दिया जाएगा. इसलिए, प्लेबैक को रोकने के लिए, किसी नोड या सबग्राफ़ को डिसकनेक्ट करके आवाज़ को रोका नहीं जा सकता.

एक आसान वेब ऑडियो नोड आर्किटेक्चर

Fieldrunners का ऑडियो मॉडल बहुत आसान है. इस मॉडल में ये सुविधाएं इस्तेमाल की जा सकती हैं:

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

वेब ऑडियो की मदद से ऊपर बताई गई सुविधाएं पाने के लिए, इसने उपलब्ध तीन नोड का इस्तेमाल किया: DestinationNode, GainNode, AudioBufferSourceNode. AudioBufferSourceNodes, आवाज़ें चलाते हैं. GainNodes, AudioBufferSourceNodes को एक साथ कनेक्ट करते हैं. डेस्टिनेशन नोड, वेब ऑडियो कॉन्टेक्स्ट से बनाया जाता है. इसे डेस्टिनेशन कहा जाता है. यह प्लेयर के लिए आवाज़ें चलाता है. वेब ऑडियो में कई और तरह के नोड होते हैं. हालांकि, सिर्फ़ इनकी मदद से हम किसी गेम में साउंड के लिए बहुत आसान ग्राफ़ बना सकते हैं.

नोड ग्राफ़ चार्ट

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

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

function AudioManager() {
  // map for loaded sounds
  this.sounds = {};

  // create our permanent nodes
  this.nodes = {
    destination: this.audioContext.destination,
    masterGain: this.audioContext.createGain(),

    backgroundMusicGain: this.audioContext.createGain(),

    coreEffectsGain: this.audioContext.createGain(),
    effectsGain: this.audioContext.createGain(),
    pausedEffectsGain: this.audioContext.createGain()
  };

  // and setup the graph
  this.nodes.masterGain.connect( this.nodes.destination );

  this.nodes.backgroundMusicGain.connect( this.nodes.masterGain );

  this.nodes.coreEffectsGain.connect( this.nodes.masterGain );
  this.nodes.effectsGain.connect( this.nodes.coreEffectsGain );
  this.nodes.pausedEffectsGain.connect( this.nodes.coreEffectsGain );
}

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

function setArbitraryVolume() {
  var musicGainNode = this.nodes.backgroundMusicGain;

  // set music volume to 50%
  musicGainNode.gain.value = 0.5;
}

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

function arbitraryCrossfade( track1, track2 ) {
  track1.gain.linearRampToValueAtTime( 0, 1 );
  track2.gain.linearRampToValueAtTime( 1, 1 );
}

Fieldrunners ने क्रॉसफ़ेडिंग का खास तौर पर इस्तेमाल नहीं किया. अगर हमें साउंड सिस्टम के ओरिजनल पास के दौरान, WebAudio की वैल्यू सेटिंग की सुविधा के बारे में पता होता, तो हम शायद ऐसा करते.

आवाज़ें रोकी जा रही हैं

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

AudioManager.prototype.pauseEffects = function() {
  this.nodes.effectsGain.disconnect();
}

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

AudioManager.prototype.resumeEffects = function() {
  this.nodes.effectsGain.connect( this.nodes.coreEffectsGain );
}

Fieldrunners को शिप करने के बाद, हमें पता चला कि सिर्फ़ किसी नोड या सबग्राफ़ को डिसकनेक्ट करने से, AudioBufferSourceNodes का प्लेबैक नहीं रुकेगा. हमने WebAudio में मौजूद एक गड़बड़ी का फ़ायदा लिया. इसकी वजह से, ग्राफ़ में डेस्टिनेशन नोड से कनेक्ट नहीं किए गए नोड का प्लेबैक रुक जाता है. इसलिए, यह पक्का करने के लिए कि हम आने वाले समय में इस समस्या को ठीक करने के लिए तैयार हैं, हमें इस तरह के कोड की ज़रूरत होगी:

AudioManager.prototype.pauseEffects = function() {
  this.nodes.effectsGain.disconnect();

  var now = Date.now();
  for ( var name in this.sounds ) {
    var sound = this.sounds[ name ];

    if ( !sound.ignorePause && ( now - sound.source.noteOnAt < sound.buffer.duration * 1000 ) ) {
      sound.pausedAt = now - sound.source.noteOnAt;
      sound.source.noteOff();
    }
  }
}

AudioManager.prototype.resumeEffects = function() {
  this.nodes.effectsGain.connect( this.nodes.coreEffectsGain );

  var now = Date.now();
  for ( var name in this.sounds ) {
    if ( sound.pausedAt ) {
      this.play( sound.name );
      delete sound.pausedAt;
    }
  }
};

अगर हमें पहले से पता होता कि हम किसी गड़बड़ी का गलत इस्तेमाल कर रहे हैं, तो हमारे ऑडियो कोड का स्ट्रक्चर काफ़ी अलग होता. इस वजह से, इस लेख के कई सेक्शन पर असर पड़ा है. इसका सीधा असर यहां दिखता है. साथ ही, Losing Focus और Give Me a Beat में हमारे कोड स्निपेट पर भी इसका असर दिखता है. यह कैसे काम करता है, यह जानने के लिए Fieldrunners नोड ग्राफ़ और अतिरिक्त कोड, दोनों में बदलाव करने होंगे. ऐसा इसलिए, क्योंकि हमने प्लेबैक को शॉर्ट करने के लिए नोड बनाए हैं. साथ ही, अतिरिक्त कोड, रोकी गई स्थितियों को रिकॉर्ड करेगा और उपलब्ध कराएगा. Web Audio ऐसा खुद नहीं करता.

फ़ोकस खोना

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

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

function AudioManager() {
  // map and node setup
  // ...

  // disable all sound when on other tabs
  var self = this;
  window.addEventListener( 'webkitvisibilitychange', function( e ) {
    if ( document.webkitHidden ) {
      self.nodes.masterGain.disconnect();

      // As noted in Pausing Sounds disconnecting isn't enough.
      // For Fieldrunners calling our new pauseEffects method would be
      // enough to accomplish that, though we may still need some logic
      // to not resume if already paused.
      self.pauseEffects();
    } else {
      self.nodes.masterGain.connect( this.nodes.destination );
      self.resumeEffects();
    }
  });
}

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

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

मुझे कोई बीट सुनाओ

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

किसी गेम में एक ही तरह की कई चीज़ों के लिए, एक ही साउंड को बार-बार चलाने के बजाय, Fieldrunners एक साउंड को उसकी अवधि के लिए सिर्फ़ एक बार चलाता है. जैसे, किसी किरदार के मरने पर. अगर साउंड खत्म होने के बाद उसकी ज़रूरत है, तो उसे फिर से चलाया जा सकता है. हालांकि, साउंड के चलने के दौरान उसे फिर से नहीं चलाया जा सकता. यह फ़ील्डरनर्स के ऑडियो डिज़ाइन के लिए एक फ़ैसला है, क्योंकि इसमें ऐसी आवाज़ें हैं जिन्हें तेज़ी से चलाने का अनुरोध किया जाता है. अगर इन्हें फिर से शुरू करने की अनुमति दी जाती है, तो ये रुक-रुककर चलेंगी या कई इंस्टेंस चलाने की अनुमति देने पर, ये शोर-शराबा पैदा करेंगी. AudioBufferSourceNodes को वन-शॉट के तौर पर इस्तेमाल किया जाना चाहिए. एक नोड बनाएं, बफ़र अटैच करें, ज़रूरत पड़ने पर लूप बूलियन वैल्यू सेट करें, ग्राफ़ पर किसी ऐसे नोड से कनेक्ट करें जो डेस्टिनेशन तक ले जाएगा, noteOn या noteGrainOn को कॉल करें, और वैकल्पिक रूप से noteOff को कॉल करें.

Fieldrunners के लिए, यह कुछ ऐसा दिखता है:

AudioManager.prototype.play = function( options ) {
  var now = Date.now(),
    // pull from a map of loaded audio buffers
    sound = this.sounds[ options.name ],
    channel,
    source,
    resumeSource;

  if ( !sound ) {
    return;
  }

  if ( sound.source ) {
    var source = sound.source;
    if ( !options.loop && now - source.noteOnAt > sound.buffer.duration * 1000 ) {
      // discard the previous source node
      source.stop( 0 );
      source.disconnect();
    } else {
      return;
    }
  }

  source = this.audioContext.createBufferSource();
  sound.source = source;
  // track when the source is started to know if it should still be playing
  source.noteOnAt = now;

  // help with pausing
  sound.ignorePause = !!options.ignorePause;

  if ( options.ignorePause ) {
    channel = this.nodes.pausedEffectsGain;
  } else {
    channel = this.nodes.effectsGain;
  }

  source.buffer = sound.buffer;
  source.connect( channel );
  source.loop = options.loop || false;

  // Fieldrunners' current code doesn't consider sound.pausedAt.
  // This is an added section to assist the new pausing code.
  if ( sound.pausedAt ) {
    source.start( ( sound.buffer.duration * 1000 - sound.pausedAt ) / 1000 );
    source.noteOnAt = now + sound.buffer.duration * 1000 - sound.pausedAt;

    // if you needed to precisely stop sounds, you'd want to store this
    resumeSource = this.audioContext.createBufferSource();
    resumeSource.buffer = sound.buffer;
    resumeSource.connect( channel );
    resumeSource.start(
      0,
      sound.pausedAt,
      sound.buffer.duration - sound.pausedAt / 1000
    );
  } else {
    // start play immediately with a value of 0 or less
    source.start( 0 );
  }
}

बहुत ज़्यादा स्ट्रीमिंग

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

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

AudioManager.prototype.load = function( options ) {
  var xhr,
      // pull from a map of name, object pairs
      sound = this.sounds[ options.name ];

  if ( sound ) {
    // this is a great spot to add success methods to a list or use promises
    // for handling the load event or call success if already loaded
    if ( sound.buffer && options.success ) {
      options.success( options.name );
    } else if ( options.success ) {
      sound.success.push( options.success );
    }

    // one buffer is enough so shortcut here
    return;
  }

  sound = {
    name: options.name,
    buffer: null,
    source: null,
    success: ( options.success ? [ options.success ] : [] )
  };
  this.sounds[ options.name ] = sound;

  xhr = new XMLHttpRequest();
  xhr.open( 'GET', options.path, true );
  xhr.responseType = 'arraybuffer';
  xhr.onload = function( e ) {
    sound.buffer = self._context.createBuffer( xhr.response, false );

    // call all waiting handlers
    sound.success.forEach( function( success ) {
      success( sound.name );
    });
    delete sound.success;
  };
  xhr.onerror = function( e ) {

    // failures are uncommon but you want to do deal with them

  };
  xhr.send();
}

खास जानकारी

Fieldrunners को Chrome और HTML5 पर लाने में बहुत मज़ा आया. C++ की हज़ारों लाइनों को JavaScript में बदलने के अलावा, HTML5 से जुड़ी कुछ दिलचस्प दुविधाएं और फ़ैसले भी सामने आए. अगर कोई और नहीं, तो AudioBufferSourceNodes एक बार इस्तेमाल किए जाने वाले ऑब्जेक्ट हैं. उन्हें बनाएं, ऑडियो बफ़र अटैच करें, उसे वेब ऑडियो ग्राफ़ से कनेक्ट करें, और noteOn या noteGrainOn के साथ चलाएं. क्या आपको वह आवाज़ फिर से सुननी है? इसके बाद, दूसरा AudioBufferSourceNode बनाएं.