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

مقدمة

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

ولا يُستثنى من ذلك الألعاب. أجمل ذكرياتي عن ألعاب الفيديو هي الموسيقى والتأثيرات الصوتية. والآن، في كثير من الحالات، بعد مرور عقدين تقريبًا على تشغيل أغانيي المفضّلة، ما زلت لا أستطيع إزالة المقطوعات الموسيقية من أغنية Koji Kondo للفنان "كوجي كوندو" وموسيقى "ديابلو" الموسيقية للفنان مات أولمن. وينطبق ذلك أيضًا على المؤثرات الصوتية، مثل ردود النقرات على الوحدات التي يمكن التعرّف عليها على الفور من 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 ، بالإضافة إلى هذا المثال الذي يتيح لك التحكّم في المزج بين الأصوات الجافة (الصوت الأصلي) والأصوات المُعالجة (الصوت الذي تم معالجته عبر أداة تحويل الإشارات) لأغنية جاز رائعة.

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

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

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

اقتصاص

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

اقتصاص

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

رصد المقاطع

من منظور فني، يحدث الاقتصاص عندما تتجاوز قيمة الإشارة في أي قناة النطاق الصالح، أي بين -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 مثالاً ممتازًا على ذلك، لأنّ الأصوات التي يتم تشغيلها تعتمد بالكامل علىك وعلى المشاركين الآخرين. يكون المُكثِّف مفيدًا في معظم الحالات، باستثناء بعض الحالات النادرة التي تتعامل فيها مع أغانٍ تم إتقانها بعناية وتم ضبطها لتبدو "مناسبة تمامًا".

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

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

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

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

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

الخاتمة

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

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

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