Fieldrunners
Fieldrunners เป็นเกมแนวป้องกันหอคอยที่ได้รับรางวัลซึ่งเปิดตัวครั้งแรกสำหรับ iPhone ในปี 2008 หลังจากนั้นเป็นต้นมา เกมดังกล่าวก็ได้รับการพอร์ตไปยังแพลตฟอร์มอื่นๆ อีกมากมาย แพลตฟอร์มล่าสุดแพลตฟอร์มหนึ่งคือเบราว์เซอร์ Chrome ในเดือนตุลาคม 2011 หนึ่งในความท้าทายในการพอร์ต Fieldrunners ไปยังแพลตฟอร์ม HTML5 คือวิธีเล่นเสียง
Fieldrunners ไม่ได้ใช้เอฟเฟกต์เสียงที่ซับซ้อน แต่ก็มีสิ่งที่คาดหวังบางอย่างเกี่ยวกับการโต้ตอบกับเอฟเฟกต์เสียง เกมนี้มีเอฟเฟกต์เสียง 88 รายการ ซึ่งคาดว่าจะมีเอฟเฟกต์เสียงจำนวนมากเล่นพร้อมกัน เสียงเหล่านี้ส่วนใหญ่จะสั้นมากและจำเป็นต้องเล่นอย่างทันท่วงทีที่สุดเพื่อไม่ให้การนำเสนอด้วยภาพไม่สอดคล้องกัน
ภารกิจบางอย่างปรากฏขึ้น
ขณะพอร์ต Fieldrunners ไปยัง HTML5 เราพบปัญหาการเล่นเสียงด้วยแท็กเสียง และตัดสินใจตั้งแต่เนิ่นๆ ที่จะมุ่งเน้นที่ Web Audio API แทน การใช้ WebAudio ช่วยให้เราแก้ปัญหาต่างๆ ได้ เช่น การเล่นเอฟเฟกต์พร้อมกันจำนวนมากที่ Fieldrunners ต้องการ อย่างไรก็ตาม ในระหว่างการพัฒนาระบบเสียงสำหรับ Fieldrunners HTML5 เราพบปัญหาเล็กๆ น้อยๆ ที่นักพัฒนาแอปคนอื่นๆ อาจต้องระวัง
ลักษณะของ AudioBufferSourceNodes
AudioBufferSourceNodes เป็นวิธีหลักในการเล่นเสียงด้วย WebAudio คุณต้องเข้าใจว่าบัตรเหล่านี้เป็นวัตถุแบบใช้ครั้งเดียว คุณสร้าง AudioBufferSourceNode, กําหนดบัฟเฟอร์, เชื่อมต่อกับกราฟ และเล่นด้วย noteOn หรือ noteGrainOn หลังจากนั้นคุณสามารถเรียกใช้ noteOff เพื่อหยุดการเล่น แต่คุณจะเล่นแหล่งที่มาอีกครั้งโดยเรียกใช้ noteOn หรือ noteGrainOn ไม่ได้ คุณต้องสร้าง AudioBufferSourceNode ใหม่ แต่คุณใช้ออบเจ็กต์ AudioBuffer เดียวกันซ้ำได้ (คุณอาจมี AudioBufferSourceNode ที่ใช้งานอยู่หลายรายการซึ่งชี้ไปยังอินสแตนซ์ AudioBuffer เดียวกันได้) คุณจะเห็นตัวอย่างการเล่นจาก Fieldrunners ในส่วน "Give Me a Beat"
เนื้อหาที่ไม่แคช
เมื่อเผยแพร่ เซิร์ฟเวอร์ HTML5 ของ Fieldrunners มีคำขอไฟล์เพลงจำนวนมาก ผลลัพธ์นี้เกิดขึ้นเนื่องจาก Chrome 15 ดำเนินการดาวน์โหลดไฟล์เป็นกลุ่มๆ แล้วไม่ได้แคชไฟล์ เราได้ตัดสินใจโหลดไฟล์เพลงเช่นเดียวกับไฟล์เสียงอื่นๆ ของเรา ซึ่งวิธีนี้ไม่เหมาะที่สุด แต่เบราว์เซอร์บางเวอร์ชันยังคงใช้วิธีนี้อยู่
ปิดเสียงเมื่อหลุดโฟกัส
ก่อนหน้านี้การตรวจจับเมื่อแท็บของเกมไม่ได้อยู่ในโฟกัสนั้นทำได้ยาก Fieldrunners เริ่มพอร์ตก่อน Chrome 13 ซึ่ง Page Visibility API เข้ามาแทนที่โค้ดที่ซับซ้อนของเราในการตรวจหาการเบลอของแท็บ เกมทุกเกมควรใช้ Visibility API เพื่อเขียนข้อมูลโค้ดสั้นๆ เพื่อปิดเสียงหรือหยุดเสียงชั่วคราว หากไม่หยุดทั้งเกมชั่วคราว เนื่องจาก Fieldrunners ใช้ requestAnimationFrame API ระบบจึงจัดการการหยุดเกมไว้โดยปริยาย แต่ไม่ได้จัดการการหยุดเสียง
หยุดเสียงชั่วคราว
เป็นเรื่องแปลกที่ขณะรับความคิดเห็นเกี่ยวกับบทความนี้ เราได้รับแจ้งว่าเทคนิคที่เราใช้หยุดเสียงชั่วคราวไม่เหมาะสม เนื่องจากเราใช้ข้อบกพร่องในการใช้งานปัจจุบันของ Web Audio เพื่อหยุดเล่นเสียงชั่วคราว เนื่องจากปัญหานี้จะได้รับการแก้ไขในอนาคต คุณจึงไม่สามารถหยุดเสียงชั่วคราวได้โดยการตัดการเชื่อมต่อโหนดหรือกราฟย่อยเพื่อหยุดการเล่น
สถาปัตยกรรมโหนด Web Audio แบบง่าย
Fieldrunners มีโมเดลเสียงที่เรียบง่ายมาก โมเดลดังกล่าวรองรับชุดฟีเจอร์ต่อไปนี้
- ควบคุมระดับเสียงของเอฟเฟกต์เสียง
- ควบคุมระดับเสียงของแทร็กเพลงที่เล่นอยู่เบื้องหลัง
- ปิดเสียงทั้งหมด
- ปิดเสียงที่เล่นเมื่อเกมหยุดชั่วคราว
- เปิดเสียงเหล่านั้นอีกครั้งเมื่อเกมกลับมาเล่นต่อ
- ปิดเสียงทั้งหมดเมื่อแท็บของเกมเสียโฟกัส
- เริ่มเล่นอีกครั้งหลังจากเล่นเสียงตามที่จำเป็น
ในการทำให้ Web Audio มีฟีเจอร์ข้างต้น จะใช้โหนดที่เป็นไปได้ 3 รายการ ได้แก่ DestinationNode, GainNode และ AudioBufferSourceNode AudioBufferSourceNodes จะเล่นเสียง GainNodes จะเชื่อมต่อ AudioBufferSourceNodes เข้าด้วยกัน DestinationNode ที่สร้างขึ้นโดยบริบท Web Audio ซึ่งเรียกว่าปลายทางจะเล่นเสียงสำหรับโปรแกรมเล่น Web Audio มีโหนดอีกหลายประเภท แต่เราสามารถสร้างกราฟที่เรียบง่ายมากสำหรับเสียงในเกมได้โดยใช้เพียงโหนดเหล่านี้
กราฟโหนด Web Audio นำทางจากโหนดใบไปยังโหนดปลายทาง Fieldrunners ใช้โหนดการเพิ่มแบบถาวร 6 โหนด แต่ 3 โหนดก็เพียงพอที่จะควบคุมระดับเสียงและเชื่อมต่อโหนดชั่วคราวจํานวนมากขึ้นที่จะเล่นบัฟเฟอร์ได้ ก่อนอื่นคือโหนดการเพิ่มระดับหลักที่แนบโหนดย่อยทุกโหนดกับปลายทาง โหนดเพิ่มระดับหลักจะเชื่อมต่อกับโหนดเพิ่มระดับ 2 โหนดโดยทันที โดยโหนดหนึ่งสำหรับช่องเพลงและอีกโหนดหนึ่งสำหรับลิงก์เอฟเฟกต์เสียงทั้งหมด
Fieldrunners มีโหนดการเพิ่มอีก 3 โหนดเนื่องจากมีการใช้ข้อบกพร่องเป็นฟีเจอร์อย่างไม่ถูกต้อง เราใช้โหนดดังกล่าวเพื่อตัดกลุ่มเสียงที่เล่นออกจากกราฟ ซึ่งจะหยุดความคืบหน้าของเสียงเหล่านั้น เราทำเช่นนี้เพื่อหยุดเสียงชั่วคราว เนื่องจากไม่ถูกต้อง ตอนนี้เราจะใช้โหนดอัตราขยายทั้งหมด 3 โหนดตามที่อธิบายไว้ด้านบนเท่านั้น ตัวอย่างข้อมูลต่อไปนี้จำนวนมากจะมีโหนดที่ไม่ถูกต้อง ซึ่งแสดงสิ่งที่เราทําและวิธีที่เราจะแก้ไขปัญหาในระยะสั้น แต่ในระยะยาว คุณไม่ควรใช้โหนดหลังโหนด 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 );
}
เกมส่วนใหญ่ให้คุณควบคุมเอฟเฟกต์เสียงและเพลงแยกกันได้ ซึ่งทำได้ง่ายๆ ด้วยกราฟด้านบน นอตการเพิ่มแต่ละนอตมีแอตทริบิวต์ "gain" ที่ตั้งค่าเป็นค่าทศนิยมใดก็ได้ระหว่าง 0 ถึง 1 ซึ่งสามารถใช้เพื่อควบคุมระดับเสียงได้ เนื่องจากเราต้องการควบคุมระดับเสียงของช่องเพลงและช่องเอฟเฟกต์เสียงแยกกัน เราจึงมีโหนดการเพิ่มสำหรับแต่ละช่องเพื่อควบคุมระดับเสียง
function setArbitraryVolume() {
var musicGainNode = this.nodes.backgroundMusicGain;
// set music volume to 50%
musicGainNode.gain.value = 0.5;
}
เราใช้ความสามารถเดียวกันนี้เพื่อควบคุมระดับเสียงของทุกอย่าง ไม่ว่าจะเป็นเอฟเฟกต์เสียงและเพลง การตั้งค่าอัตราขยายของโหนดหลักจะส่งผลต่อเสียงทั้งหมดจากเกม หากตั้งค่าอัตราขยายเป็น 0 ระบบจะปิดเสียงและเพลง AudioBufferSourceNodes มีพารามิเตอร์อัตราขยายด้วย คุณสามารถติดตามรายการเสียงที่เล่นอยู่ทั้งหมดและปรับค่าการขยายเสียงแต่ละรายการสำหรับระดับเสียงโดยรวม หากต้องการสร้างเสียงประกอบด้วยแท็กเสียง คุณจะต้องทําเช่นนี้ แต่กราฟโหนดของ Web Audio ช่วยให้ปรับระดับเสียงของเสียงนับไม่ถ้วนได้ง่ายขึ้นมาก การควบคุมระดับเสียงด้วยวิธีนี้ยังช่วยให้คุณควบคุมระดับเสียงได้มากขึ้นอย่างง่ายดาย เราเพียงแนบ AudioBufferSourceNode กับโหนดหลักโดยตรงเพื่อเล่นเพลงและควบคุมอัตราขยายเสียงของโหนดนั้น แต่คุณจะต้องตั้งค่านี้ทุกครั้งที่สร้าง AudioBufferSourceNode เพื่อวัตถุประสงค์ในการเล่นเพลง แต่ให้เปลี่ยนโหนดเดียวเมื่อผู้เล่นเปลี่ยนระดับเสียงเพลงและเมื่อเปิดใช้งานเท่านั้น ตอนนี้เรามีค่าอัตราขยายในแหล่งที่มาของบัฟเฟอร์เพื่อดำเนินการอย่างอื่น สำหรับเพลง การใช้งานที่พบบ่อยอย่างหนึ่งคือการสร้างเสียงที่ค่อยๆ เบาลงจากแทร็กเสียงหนึ่งไปยังอีกแทร็กหนึ่งเมื่อแทร็กหนึ่งจบลงแล้วอีกแทร็กหนึ่งเริ่มเล่น Web Audio มีวิธีการที่ยอดเยี่ยมในการดำเนินการนี้ได้อย่างง่ายดาย
function arbitraryCrossfade( track1, track2 ) {
track1.gain.linearRampToValueAtTime( 0, 1 );
track2.gain.linearRampToValueAtTime( 1, 1 );
}
Fieldrunners ไม่ได้ใช้การเฟดเสียง หากเราทราบฟังก์ชันการตั้งค่าค่าของ WebAudio ในระหว่างการส่งผ่านระบบเสียงครั้งแรก เราอาจใช้ฟังก์ชันดังกล่าว
การหยุดเสียงชั่วคราว
เมื่อผู้เล่นหยุดเกมชั่วคราว เสียงบางรายการจะยังคงเล่นอยู่ เสียงเป็นส่วนสำคัญของความคิดเห็นเกี่ยวกับการกดองค์ประกอบอินเทอร์เฟซผู้ใช้ในเมนูเกม เนื่องจาก Fieldrunners มีอินเทอร์เฟซจำนวนมากที่ผู้ใช้โต้ตอบได้ขณะที่เกมหยุดชั่วคราว เราจึงต้องการให้ผู้ใช้เหล่านั้นเล่นต่อไป อย่างไรก็ตาม เราไม่ต้องการให้เสียงที่ยาวหรือวนซ้ำเล่นต่อไป การหยุดเสียงเหล่านั้นด้วย Web Audio นั้นค่อนข้างง่าย หรืออย่างน้อยเราก็คิดอย่างนั้น
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 ในการเรียกใช้ลูปการอัปเดต แต่บริบทเสียงบนเว็บจะเล่นเอฟเฟกต์แบบวนซ้ำและแทร็กแบ็กกราวด์ต่อไปขณะที่ผู้ใช้อยู่ในแท็บอื่น แต่เราหยุดการดำเนินการดังกล่าวได้โดยใช้ข้อมูลโค้ดขนาดเล็กมากที่รับรู้ Visibility API
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();
}
});
}
ก่อนที่จะเขียนบทความนี้ เราคิดว่าการยกเลิกการเชื่อมต่อกับต้นทางก็เพียงพอที่จะหยุดเสียงทั้งหมดชั่วคราวแทนการปิดเสียง การยกเลิกการเชื่อมต่อโหนดในขณะนั้นทำให้โหนดและโหนดย่อยไม่ประมวลผลและเล่น เมื่อเชื่อมต่ออีกครั้ง เสียงและเพลงทั้งหมดจะเริ่มเล่นต่อจากที่ค้างไว้ เช่นเดียวกับที่เกมจะเล่นต่อจากที่ค้างไว้ แต่นี่ไม่ใช่ลักษณะการทำงานที่คาดไว้ การยกเลิกการเชื่อมต่อเพียงอย่างเดียวนั้นไม่เพียงพอที่จะหยุดการเล่น
API สำหรับระดับการเข้าถึงหน้าเว็บช่วยให้คุณทราบได้ง่ายๆ เมื่อแท็บไม่ได้อยู่ในโฟกัสแล้ว หากมีโค้ดที่มีประสิทธิภาพในการหยุดเสียงชั่วคราวอยู่แล้ว คุณก็เขียนโค้ดการหยุดเสียงชั่วคราวเมื่อซ่อนแท็บเกมได้ภายในไม่กี่บรรทัด
Give Me a Beat
เราได้ตั้งค่าบางอย่างแล้ว เรามีกราฟของโหนด เราหยุดเสียงชั่วคราวได้เมื่อผู้เล่นหยุดเกมชั่วคราว และเล่นเสียงใหม่สำหรับองค์ประกอบต่างๆ เช่น เมนูเกม เราหยุดเสียงและเพลงทั้งหมดชั่วคราวได้เมื่อผู้ใช้เปลี่ยนไปใช้แท็บใหม่ ตอนนี้เราต้องเล่นเสียง
Fieldrunners จะเล่นเสียง 1 รายการเพียงครั้งเดียวตลอดระยะเวลาของเสียงแทนที่จะเล่นเสียงหลายรายการสําหรับเอนทิตีเกมหลายรายการ เช่น ตัวละครที่ตาย หากต้องการเสียงหลังจากที่เล่นจบแล้ว เสียงจะเริ่มต้นใหม่ได้ แต่จะเล่นไม่ได้ขณะที่เล่นอยู่ นี่เป็นการตัดสินใจสำหรับการออกแบบเสียงของ 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 ไม่ได้แคชข้อมูลส่วนที่สตรีมจากไฟล์เพลง ส่งผลให้เบราว์เซอร์ขอแทร็กที่กำลังเล่นทุก 2-3 นาทีเมื่อแทร็กเล่นจบ ในการทดสอบล่าสุด Chrome จะแคชแทร็กที่สตรีม แต่เบราว์เซอร์อื่นๆ อาจยังไม่ได้ดำเนินการนี้ การสตรีมไฟล์เสียงขนาดใหญ่ด้วยแท็กเสียงเพื่อฟังก์ชันการทำงานต่างๆ เช่น การเล่นเพลงนั้นมีประสิทธิภาพสูงสุด แต่สำหรับเบราว์เซอร์บางเวอร์ชัน คุณอาจต้องโหลดเพลงในลักษณะเดียวกับที่โหลดเอฟเฟกต์เสียง
เนื่องจากเอฟเฟกต์เสียงทั้งหมดเล่นผ่าน Web Audio เราจึงย้ายการเล่นเพลงพื้นหลังไปยัง Web Audio ด้วย ซึ่งหมายความว่าเราจะโหลดแทร็กในลักษณะเดียวกับที่โหลดเอฟเฟกต์ทั้งหมดด้วย XMLHttpRequest และประเภทการตอบกลับ 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 เป็นแอบเจ็กต์แบบใช้ครั้งเดียว สร้าง แนบบัฟเฟอร์เสียง เชื่อมต่อกับกราฟ Web Audio แล้วเล่นด้วย noteOn หรือ noteGrainOn ต้องการเล่นเสียงนั้นอีกครั้งไหม จากนั้นสร้าง AudioBufferSourceNode อื่น