تطوير محتوى صوتي للعبة باستخدام Web Audio API

مقدمة

يشكّل الصوت جزءًا كبيرًا مما يجعل تجارب الوسائط المتعدّدة مشوّقة. إذا حاولت مشاهدة فيلم بدون صوت، ربما لاحظت ذلك.

ولا يُستثنى من ذلك الألعاب. من أجمل ذكرياتي حول ألعاب الفيديو هي الموسيقى والتأثيرات الصوتية. بعد مرور عقدين تقريبًا على لعب ألعابي المفضّلة، لا تزال مقطوعات كوجى كوندو المخصّصة لسلسلة ألعاب Zelda ومقطوعات مات أولمِن الموسيقية المميّزة لسلسلة ألعاب Diablo تُخرجني من حالتي الذهنية. وينطبق ذلك أيضًا على المؤثرات الصوتية، مثل ردود النقرات على الوحدات التي يمكن التعرّف عليها على الفور من Warcraft، والعينات من ألعاب Nintendo الكلاسيكية.

تواجه ميزة "الصوت في الألعاب" بعض التحديات المثيرة للاهتمام. لإنشاء موسيقى مقنعة للألعاب، على المصمّمين التكيّف مع حالة اللعبة التي قد تكون غير متوقّعة والتي يواجهها اللاعب. في الممارسة العملية، يمكن أن تستمر أجزاء من اللعبة لمدة غير معروفة، ويمكن أن تتفاعل الأصوات مع البيئة وتتداخل بطرق معقّدة، مثل تأثيرات الغرف وموضع الصوت النسبي. أخيرًا، يمكن أن يتم تشغيل عدد كبير من الأصوات في وقت واحد، ويجب أن تبدو جميعها جيدة معًا وأن يتم عرضها بدون فرض عقوبات على الأداء.

محتوى صوتي من الألعاب على الويب

بالنسبة إلى الألعاب البسيطة، قد يكون استخدام علامة <audio> كافيًا. ومع ذلك، توفّر العديد من المتصفّحات عمليات تنفيذ سيئة، ما يؤدي إلى حدوث أخطاء صوتية ووقت استجابة مرتفع. نأمل أن تكون هذه مشكلة مؤقتة، لأنّ المورّدين يعملون جاهدين لتحسين عمليات التنفيذ الخاصة بهم. للاطّلاع على لمحة عن حالة علامة <audio>، تتوفّر مجموعة اختبارات رائعة على areweplayingyet.org.

ومع ذلك، عند الاطّلاع بشكل أعمق على مواصفات علامة <audio>، يصبح واضحًا أنّ هناك العديد من الإجراءات التي لا يمكن تنفيذها باستخدامها، وهو أمر ليس مفاجئًا، لأنّها مصمّمة لتشغيل الوسائط. تشمل بعض القيود ما يلي:

  • لا يمكن تطبيق فلاتر على إشارة الصوت.
  • لا تتوفّر طريقة للوصول إلى بيانات PCM الأولية
  • لا يتوفّر مفهوم لموضع المصادر والمستمعين واتجاههم
  • لا تتوفّر إمكانية تحديد وقت دقيق.

في بقية المقالة، سأتناول بعض هذه المواضيع في سياق الصوت في الألعاب المكتوب باستخدام Web Audio API. للحصول على مقدمة موجزة عن واجهة برمجة التطبيقات هذه، اطّلِع على الدليل التمهيدي لبدء الاستخدام.

موسيقى الخلفية

غالبًا ما تتضمّن الألعاب موسيقى في الخلفية يتم تشغيلها بشكل متكرّر.

وقد يصبح ذلك مزعجًا جدًا إذا كانت حلقة الفيديو قصيرة ويمكن توقّعها. إذا عُلِق أحد اللاعبين في منطقة أو مستوى معيّنَين، وظلّت العينة نفسها مشغّلة باستمرار في الخلفية، قد يكون من المفيد تخفيف مستوى الصوت تدريجيًا في المقطع الصوتي لتجنّب حدوث المزيد من الإحباط. ويمكنك أيضًا استخدام استراتيجية أخرى تعتمد على دمج أصوات بشدّة مختلفة تتغيّر تدريجيًا لتتداخل مع بعضها، وذلك استنادًا إلى سياق اللعبة.

على سبيل المثال، إذا كان اللاعب في منطقة معركة ملحمية مع زعيم، قد يكون لديك عدة مجموعات تختلف في النطاق العاطفي من الجو إلى التلميح إلى القوة إلى الكثافة. غالبًا ما تسمح لك برامج تركيب الموسيقى بتصدير عدّة مجموعات (بالطول نفسه) استنادًا إلى مقطع معيّن من خلال اختيار مجموعة الأغاني التي تريد استخدامها في عملية التصدير. بهذه الطريقة، تحافظ على اتساق داخلي معيّن وتجنّب حدوث انتقالات مفاجئة عند الانتقال من مقطع صوتي إلى آخر.

Garageband

بعد ذلك، يمكنك استخدام Web Audio API لاستيراد كل هذه العيّنات باستخدام شيء مثل فئة BufferLoader عبر XHR (يتم تناول هذه المسألة بالتفصيل في مقالة Web Audio API التمهيدية). يستغرق تحميل الأصوات بعض الوقت، لذا يجب تحميل مواد العرض المستخدَمة في اللعبة عند تحميل الصفحة أو في بداية المستوى أو بشكل تدريجي أثناء تشغيل اللعبة.

بعد ذلك، يمكنك إنشاء مصدر لكل عقدة، وعقدة مكاسب لكل مصدر، وربط الرسم البياني.

بعد إجراء ذلك، يمكنك تشغيل كل هذه المصادر في وقت واحد بشكل متكرّر، وبما أنّها جميعًا متساوية الطول، تضمن لك واجهة برمجة التطبيقات Web Audio API مواصلة التزامن بينها. عندما يقترب البطل من معركة الزعيم النهائي أو يبتعد عنها، يمكن أن تغيّر اللعبة قيم الزيادة في كل عقدة من العقد المعنيّة في السلسلة، باستخدام خوارزمية كمية الزيادة مثل ما يلي:

// Assume gains is an array of AudioGainNode, normVal is the intensity
// between 0 and 1.
var value = normVal - (gains.length - 1);
// First reset gains on all nodes.
for (var i = 0; i < gains.length; i++) {
    gains[i].gain.value = 0;
}
// Decide which two nodes we are currently between, and do an equal
// power crossfade between them.
var leftNode = Math.floor(value);
// Normalize the value between 0 and 1.
var x = value - leftNode;
var gain1 = Math.cos(x - 0.5*Math.PI);
var gain2 = Math.cos((1.0 - x) - 0.5*Math.PI);
// Set the two gains accordingly.
gains[leftNode].gain.value = gain1;
// Check to make sure that there's a right node.
if (leftNode < gains.length - 1) {
    // If there is, adjust its gain.
    gains[leftNode + 1].gain.value = gain2;
}

في النهج أعلاه، يتم تشغيل مصدرَين في الوقت نفسه، ونفلتِح بينهما باستخدام منحنيات متساوية للطاقة (كما هو موضّح في المقدّمة).

يستخدم العديد من مطوّري الألعاب حاليًا علامة <audio> لموسيقى الخلفية، لأنّها مناسبة تمامًا لبث المحتوى. يمكنك الآن جلب المحتوى من علامة <audio> إلى سياق Web Audio.

يمكن أن تكون هذه الطريقة مفيدة لأنّ علامة <audio> يمكن أن تعمل مع المحتوى الذي يتم بثه، ما يتيح لك تشغيل الموسيقى في الخلفية على الفور بدلاً من الانتظار إلى أن يتم تنزيل المحتوى بالكامل. من خلال إدخال مصدر البيانات في Web Audio API، يمكنك التلاعب بمصدر البيانات أو تحليله. يطبّق المثال التالي فلترًا منخفض التردد على الموسيقى التي يتم تشغيلها من خلال علامة <audio>:

var audioElement = document.querySelector('audio');
var mediaSourceNode = context.createMediaElementSource(audioElement);
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
mediaSourceNode.connect(filter);
filter.connect(context.destination);

للحصول على مناقشة أكثر اكتمالاً حول دمج علامة <audio> مع Web Audio API، اطّلِع على هذه المقالة القصيرة.

المؤثرات الصوتية

غالبًا ما تشغّل الألعاب مؤثرات صوتية استجابةً لإدخال المستخدم أو التغيُّرات في حالة اللعبة. ومع ذلك، يمكن أن تصبح التأثيرات الصوتية مزعجة بسرعة كبيرة، تمامًا مثل الموسيقى في الخلفية. لتجنُّب ذلك، من المفيد غالبًا إنشاء مجموعة من الأصوات المتشابهة ولكن المختلفة لتشغيلها. ويمكن أن يتراوح ذلك من اختلافات بسيطة في عيّنات الخطوات إلى اختلافات كبيرة، كما هو الحال في سلسلة Warcraft استجابةً للنقر على الوحدات.

من الميزات الرئيسية الأخرى للتأثيرات الصوتية في الألعاب أنّه يمكن أن يكون هناك العديد منها في الوقت نفسه. تخيل أنّك في خضمّ معركة بالأسلحة النارية مع ممثلين متعددين يطلقون النار من مدافع رشاشة. يطلق كل مدفع رشاش النار عدة مرات في الثانية، ما يؤدي إلى تشغيل عشرات المؤثرات الصوتية في الوقت نفسه. إنّ تشغيل الصوت من مصادر متعدّدة مُوقَّتة بدقة في الوقت نفسه هو أحد المجالات التي تُبرز واجهة برمجة التطبيقات Web Audio API بشكلٍ رائع.

ينشئ المثال التالي طلقة مدفع رشاش من عدة عيّنات فردية من الرصاص عن طريق إنشاء مصادر صوت متعددة يتم تشغيلها بشكل متقطّع في الوقت.

var time = context.currentTime;
for (var i = 0; i < rounds; i++) {
    var source = this.makeSource(this.buffers[M4A1]);
    source.noteOn(time + i - interval);
}

الآن، إذا كانت كلّ مدافع رشاشة في لعبتك تُصدر صوتًا بالضبط مثل هذا، سيكون ذلك مملًّا جدًا. بطبيعة الحال، ستختلف هذه الأصوات حسب نوعها استنادًا إلى المسافة من الهدف والموقع النسبي (مزيد من المعلومات حول هذا الموضوع لاحقًا)، ولكن قد لا يكون ذلك كافيًا. لحسن الحظ، توفّر واجهة برمجة التطبيقات Web Audio API طريقة لتعديل المثال أعلاه بسهولة بطريقتَين:

  1. مع تغيير طفيف في الوقت بين إطلاق الرصاصات
  2. من خلال تغيير playbackRate لكل عيّنة (تغيير درجة الصوت أيضًا) لمحاكاة العشوائية في العالم الواقعي بشكل أفضل.

للحصول على مثال أكثر واقعية على استخدام هذه الأساليب، يمكنك الاطّلاع على العرض التجريبي لجدول ألعاب البلياردو الذي يستخدم تحليلًا عشوائيًا للصوت ويغيّر معدّل التشغيل للحصول على صوت اصطدام الكرة أكثر تشويقًا.

الصوت المحدد الموقع الجغرافي بتقنية 3D

غالبًا ما يتمّ وضع الألعاب في عالم يتضمّن بعض الخصائص الهندسية، إمّا في البعدَين أو في ثلاثة أبعاد. في هذه الحالة، يمكن أن يؤدي الصوت الاستيريو إلى تعزيز تجربة المشاهدة بشكل كبير. لحسن الحظ، تأتي Web Audio API مزوّدة بميزات صوتية مدمجة مزوّدة بتقنية تسريع الأجهزة والتي يسهل استخدامها. يُرجى التأكّد من توفّر مكبّرات صوت استيريو (سماعات رأس يُفضّل استخدامها) لكي يكون المثال التالي verständlichًا.

في المثال أعلاه، هناك مستمع (رمز شخص) في منتصف اللوحة، ويؤثر الماوس في موضع المصدر (رمز المتحدّث). ما سبق هو مثال بسيط على استخدام AudioPannerNode لتطبيق هذا النوع من التأثيرات. الفكرة الأساسية في المثال أعلاه هي الاستجابة لحركة الماوس من خلال ضبط موضع مصدر الصوت، على النحو التالي:

PositionSample.prototype.changePosition = function(position) {
    // Position coordinates are in normalized canvas coordinates
    // with -0.5 < x, y < 0.5
    if (position) {
    if (!this.isPlaying) {
        this.play();
    }
    var mul = 2;
    var x = position.x / this.size.width;
    var y = -position.y / this.size.height;
    this.panner.setPosition(x - mul, y - mul, -0.5);
    } else {
    this.stop();
    }
};

في ما يلي بعض المعلومات التي يجب معرفتها حول كيفية تعامل Web Audio مع المعالجة المكانية:

  • يكون المستمع في نقطة الأصل (0, 0, 0) تلقائيًا.
  • واجهات برمجة التطبيقات لتحديد الموقع في Web Audio لا تستخدم وحدات، لذلك أضفت مُضاعِفًا لتحسين جودة الصوت في العرض الترويجي.
  • يستخدم Web Audio الإحداثيات الكرتيزية التي يكون فيها محور y للأعلى (عكس معظم أنظمة الرسومات الحاسوبية). لهذا السبب، أريد تبديل المحور الصادي في المقتطف أعلاه.

خيارات متقدّمة: أعمدة الصوت

إنّ نموذج تحديد الموقع الجغرافي فعّال جدًا ومتقدّم جدًا، ويستند إلى حدٍ كبير إلى OpenAL. لمزيد من التفاصيل، يُرجى الاطّلاع على القسمَين 3 و4 من المَواصفات المُدرَجة أعلاه.

نموذج الموضع

هناك عنصر AudioListener واحد مرتبط بسياق Web Audio API الذي يمكن ضبطه في الفضاء من خلال الموضع والاتجاه. يمكن تمرير كل مصدر من خلال AudioPannerNode، الذي يُنشئ صوتًا مكانيًا للصوت المُدخل. تحتوي عقدة أداة التمرير على موضع و اتجاه، بالإضافة إلى نموذج المسافة والاتجاه.

يحدِّد نموذج المسافة مقدار الكسب استنادًا إلى مدى القرب من المصدر، في حين يمكن ضبط النموذج الاتجاهي من خلال تحديد مخروط داخلي وخارجي يحدِّدان مقدار الكسب (الذي يكون عادةً سلبيًا) إذا كان المستمع داخل المخروط الداخلي أو بين المخروط الداخلي والمخروط الخارجي أو خارج المخروط الخارجي.

var panner = context.createPanner();
panner.coneOuterGain = 0.5;
panner.coneOuterAngle = 180;
panner.coneInnerAngle = 0;

على الرغم من أنّ مثالي ثنائي الأبعاد، يمكن تعميم هذا النموذج بسهولة على السمة الثالثة. للاطّلاع على مثال على الصوت المُعدّ للعرض في 3D، يمكنك الاطّلاع على هذا المثال على الصوت المكاني. بالإضافة إلى الموضع، يتضمّن نموذج ملفّات الوسائط المتوافقة مع Web Audio السرعة اختياريًا لتأثيرات دوبلر. يوضّح هذا المثال تأثير دوبلر بمزيد من التفاصيل.

لمزيد من المعلومات حول هذا الموضوع، يمكنك الاطّلاع على هذا الدليل التعليمي المفصّل حول [مزج الصوت المكاني وWebGL][webgl].

تأثيرات الغرف والفلاتر

في الواقع، تعتمد طريقة سماع الصوت بشكل كبير على الغرفة التي يتم فيها سماع هذا الصوت. سيبدو صوت الباب الذي يصدر صريرًا مختلفًا جدًا في الطابق السفلي مقارنةً بقاعة كبيرة مفتوحة. إنّ الألعاب التي تتطلب تكلفة إنتاج عالية ستحتاج إلى تقليد هذه التأثيرات، لأنّ إنشاء مجموعة منفصلة من العيّنات لكل بيئة هو أمر باهظ التكلفة، ويؤدي إلى زيادة عدد مواد العرض وكمية أكبر من بيانات الألعاب.

بشكل عام، يُطلق على الفرق بين استجابة الدافع وطريقة سماع الصوت في الواقع مصطلح "الصوت". يمكن تسجيل ملفّات استجابة النبضات هذه بعناية، وفي الواقع هناك مواقع إلكترونية تستضيف العديد منملفّات استجابة النبضات المسجّلة مسبقًا (المخزّنة كصوت) لتسهيل استخدامك.

للحصول على المزيد من المعلومات حول كيفية إنشاء استجابات النبضات من بيئة معيّنة، اطّلِع على قسم "إعداد التسجيل" في جزء التفاف من مواصفات Web Audio API.

والأهم من ذلك بالنسبة إلى أغراضنا، توفّر Web Audio API طريقة سهلة لتطبيق استجابات النبضات هذه على الأصوات باستخدام ConvolverNode.

// Make a source node for the sample.
var source = context.createBufferSource();
source.buffer = this.buffer;
// Make a convolver node for the impulse response.
var convolver = context.createConvolver();
convolver.buffer = this.impulseResponseBuffer;
// Connect the graph.
source.connect(convolver);
convolver.connect(context.destination);

يمكنك أيضًا الاطّلاع على هذا العرض التوضيحي لتأثيرات الغرفة في صفحة مواصفات Web Audio API ، بالإضافة إلى هذا المثال الذي يتيح لك التحكّم في المزج بين الأصوات الجافة (الصوت الأصلي) والأصوات الرطبة (الصوت الذي تمت معالجته عبر أداة تحويل الإشارات الصوتية) لأغنية جاز رائعة.

العد التنازلي النهائي

لقد أنشأت لعبة وضبطت الصوت المكاني، وأصبح لديك الآن عدد كبير من عناصر AudioNode في الرسم البياني، ويتم تشغيلها جميعًا بالتوازي. رائع، ولكن هناك شيء آخر يجب مراعاته:

بما أنّ الأصوات المتعددة تتراكم فوق بعضها بدون تسويتها، قد تواجهك حالة تؤدي فيها إلى تجاوز الحدّ الأقصى لقدرة مكبّر الصوت. تمامًا مثل الصور التي تتعدى حدود اللوحة، يمكن أيضًا اقتصاص الأصوات إذا تجاوز منحنى الطول الموجي الحد الأقصى المسموح به، ما يؤدي إلى حدوث تشويه واضح. يبدو شكل الموجة على النحو التالي:

الاقتصاص

في ما يلي مثال عملي على الاقتصاص. يبدو شكل الموجة سيئًا:

الاقتصاص

من المهم الاستماع إلى التشوهات الحادة مثل التشوه أعلاه، أو على العكس، المحتوى الذي يضمّ أصواتًا منخفضة جدًا تجبر المستمعين على رفع مستوى الصوت. إذا كنت تواجه هذه المشكلة، عليك حلّها.

رصد المقاطع

من منظور فني، يحدث الاقتصاص عندما تتجاوز قيمة الإشارة في أي قناة النطاق الصالح، أي بين -1 و1. بعد رصد ذلك، من المفيد تقديم ملاحظات مرئية تفيد بأنّه يحدث ذلك. لإجراء ذلك بشكل موثوق، ضَع JavaScriptAudioNode في الرسم البياني. سيتم إعداد الرسم البياني الصوتي على النحو التالي:

// Assume entire sound output is being piped through the mix node.
var meter = context.createJavaScriptNode(2048, 1, 1);
meter.onaudioprocess = processAudio;
mix.connect(meter);
meter.connect(context.destination);

ويمكن رصد الاقتصاص في معالِج processAudio التالي:

function processAudio(e) {
    var buffer = e.inputBuffer.getChannelData(0);

    var isClipping = false;
    // Iterate through buffer to check if any of the |values| exceeds 1.
    for (var i = 0; i < buffer.length; i++) {
    var absValue = Math.abs(buffer[i]);
    if (absValue >= 1) {
        isClipping = true;
        break;
    }
    }
}

بشكل عام، احرِص على عدم الإفراط في استخدام JavaScriptAudioNode لأسباب متعلقة بالأداء. في هذه الحالة، يمكن أن يؤدي تنفيذ بديل لقياس إلى فحص RealtimeAnalyserNode في الرسم البياني الصوتي لقياس getByteFrequencyData في وقت العرض، كما تحدّده requestAnimationFrame. هذا النهج أكثر فعالية، ولكنه لا يرصد معظم الإشارة (بما في ذلك الأماكن التي يُحتمل أن يتم فيها اقتطاعها)، لأنّه تتم عملية المعالجة بمعدل 60 مرة في الثانية كحد أقصى، في حين يتغيّر إشارة الصوت بسرعة أكبر بكثير.

ولأنّ رصد المقاطع مهم جدًا، من المرجّح أن نرى في المستقبل node مضمّنة في MeterNode Web Audio API.

منع الاقتصاص

من خلال ضبط الكسب في AudioGainNode الرئيسية، يمكنك تخفيف الصوت إلى مستوى يمنع القطع. ومع ذلك، من الناحية العملية، قد يكون من الصعب تحديد قيمة الكسب الرئيسية التي تمنع القص في جميع الحالات، لأنّه قد يعتمد مستوى الصوت في لعبتك على مجموعة كبيرة من العوامل. بشكل عام، عليك تعديل المكاسب لتجنّب أسوأ الحالات، ولكن هذا الأمر يعتمد على الفن أكثر من العلم.

إضافة القليل من السكر

تُستخدَم برامج الضغط عادةً في إنتاج الموسيقى والألعاب لتنعيم الإشارة والتحكّم في الارتفاعات في الإشارة العامة. تتوفّر هذه الميزة في عالم Web Audio من خلال العنصر DynamicsCompressorNode، والذي يمكن إدراجه في الرسم البياني الصوتي لمنح صوت أعلى وأكثر ثراءً وامتلاءً، كما يساعد في إزالة المقاطع غير المرغوب فيها. نقلاً عن المواصفات مباشرةً، هذه العقدة

من المستحسن بشكل عام استخدام ميزة "ضغط المحتوى الديناميكي"، خاصةً في إعدادات الألعاب، حيث لا يمكنك تحديد بدقة الأصوات التي سيتم تشغيلها والوقت الذي سيتم تشغيلها فيه، كما سبق أن ناقشنا. يُعدّ تطبيق Plink من DinahMoe labs مثالاً رائعًا على ذلك، لأنّ الأصوات التي يتم تشغيلها تعتمد بالكامل علىك وعلى المشاركين الآخرين. يكون المُكثِّف مفيدًا في معظم الحالات، باستثناء بعض الحالات النادرة التي تتعامل فيها مع أغانٍ تم إتقانها بعناية وتم ضبطها لتبدو "مناسبة تمامًا".

لتنفيذ ذلك، ما عليك سوى تضمين DynamicsCompressorNode في الرسم البياني الصوتي، وعادةً ما تكون العقدة الأخيرة قبل الوجهة:

// Assume the output is all going through the mix node.
var compressor = context.createDynamicsCompressor();
mix.connect(compressor);
compressor.connect(context.destination);

للحصول على مزيد من التفاصيل حول ضغط المحتوى الديناميكي، يمكنك الاطّلاع على هذه المقالة على ويكيبيديا.

باختصار، عليك الاستماع بعناية إلى المحتوى بحثًا عن أيّ تشويش وتجنُّبه عن طريق إدراج node تحكم في مستوى الصوت. بعد ذلك، يمكنك تضييق المزيج بالكامل باستخدام عقدة ملفتة للصوت. قد يبدو الرسم البياني للصوت على النحو التالي:

النتيجة النهائية

الخاتمة

يتناول هذا القسم الجوانب الأكثر أهمية في تطوير ملفّات صوت الألعاب باستخدام واجهة برمجة التطبيقات Web Audio API. باستخدام هذه الأساليب، يمكنك إنشاء تجارب صوتية رائعة مباشرةً في المتصفّح. قبل مغادرة المحادثة، إليك نصيحة خاصة بالمتصفّح: احرص على إيقاف الصوت مؤقتًا إذا انتقلت علامتك التبويب إلى الخلفية باستخدام واجهة برمجة التطبيقات page visibility API، وإلا قد تتسبّب في تقديم تجربة مزعجة للمستخدم.

للحصول على معلومات إضافية عن Web Audio، يمكنك الاطّلاع على مقالة البدء التي تقدّم المزيد من المعلومات التمهيدية، وإذا كان لديك سؤال، يمكنك البحث عن إجابة له في الأسئلة الشائعة حول Web Audio. أخيرًا، إذا كانت لديك أسئلة إضافية، يمكنك طرحها على Stack Overflow باستخدام العلامة web-audio.

قبل إنهاء هذه المحادثة، إليك بعض الاستخدامات الرائعة لواجهة برمجة التطبيقات Web Audio API في الألعاب الحالية: