العودة إلى المدونة
Tutorial8 دقيقة قراءة

سكربتات مشغل الموسيقى والراديو في FiveM: xSound والمزيد

شغل الموسيقى داخل اللعبة بالطريقة الصحيحة. xSound، تطبيقات مشغل الراديو، نماذج البومبوكس وأفضل سكربتات الصوت في FiveM للحفلات، الحانات والسيارات.

Agency Scripts

المؤسس والمطور الرئيسي في Agency Scripts

لماذا يهم الراديو المخصص في اللعب التمثيلي

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

هندسة الصوت وتكامل NUI

طبقة NUI في FiveM تشغل متصفحًا مبنيًا على Chromium مدمجًا في عميل اللعبة، مما يعني أن لديك وصولًا إلى Web Audio API، عناصر صوت HTML5، وحتى MediaSource API للبث التكيفي. البنية تنقسم إلى ثلاثة مكونات: سكريبت العميل Lua يتعامل مع تفاعلات عالم اللعبة مثل دخول المركبات أو الاقتراب من مكبرات الصوت، طبقة NUI HTML و JavaScript تدير تشغيل الصوت الفعلي والتصور، والخادم ينسق حالات التشغيل المتزامنة عبر عدة عملاء. عندما يدخل اللاعب مركبة، يرسل سكريبت العميل رسالة NUI لبدء تشغيل المحطة الحالية. طبقة NUI تنشئ عنصر صوت، تضبط عنوان المصدر، وتبدأ التشغيل. يتم التحكم في مستوى الصوت بالكامل في طبقة NUI باستخدام GainNode من Web Audio API لتحولات حجم سلسة:

-- Client-side: Vehicle radio controller
local currentStation = nil
local radioActive = false

CreateThread(function()
    while true do
        Wait(500)
        local ped = PlayerPedId()

        if IsPedInAnyVehicle(ped, false) then
            local vehicle = GetVehiclePedIsIn(ped, false)

            if not radioActive then
                radioActive = true
                -- Disable default GTA radio
                SetVehicleRadioEnabled(vehicle, false)

                -- Load saved station preference
                local savedStation = LocalPlayer.state.radioStation or 'station_1'
                SetStation(savedStation)
            end
        elseif radioActive then
            radioActive = false
            SendNUIMessage({action = 'stopRadio'})
            currentStation = nil
        end
    end
end)

function SetStation(stationId)
    local station = Config.Stations[stationId]
    if not station then return end

    currentStation = stationId
    SendNUIMessage({
        action = 'playStation',
        url = station.url,
        name = station.name,
        volume = LocalPlayer.state.radioVolume or 0.5,
    })
end

إعداد مشغل الصوت NUI

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

// NUI JavaScript: Audio engine
class RadioPlayer {
    constructor() {
        this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
        this.gainNode = this.audioContext.createGain();
        this.analyser = this.audioContext.createAnalyser();
        this.analyser.fftSize = 256;

        this.gainNode.connect(this.analyser);
        this.analyser.connect(this.audioContext.destination);

        this.audio = new Audio();
        this.audio.crossOrigin = 'anonymous';
        this.source = null;
        this.isPlaying = false;
    }

    play(url, volume) {
        this.stop();
        this.audio = new Audio(url);
        this.audio.crossOrigin = 'anonymous';
        this.audio.volume = 1.0;

        this.source = this.audioContext.createMediaElementSource(this.audio);
        this.source.connect(this.gainNode);
        this.gainNode.gain.value = volume;

        this.audio.play().then(() => {
            this.isPlaying = true;
        }).catch(err => {
            console.error('Radio playback failed:', err);
            setTimeout(() => this.play(url, volume), 3000);
        });
    }

    stop() {
        if (this.audio) {
            this.audio.pause();
            this.audio.src = '';
            this.isPlaying = false;
        }
    }

    setVolume(vol) {
        this.gainNode.gain.linearRampToValueAtTime(
            vol, this.audioContext.currentTime + 0.1
        );
    }

    getFrequencyData() {
        const data = new Uint8Array(this.analyser.frequencyBinCount);
        this.analyser.getByteFrequencyData(data);
        return data;
    }
}

const radio = new RadioPlayer();

تهيئة المحطة والبث

حدد محطات الراديو الخاصة بك مع روابط البث، البيانات الوصفية، وفئات الأنواع. تستخدم معظم أنظمة راديو FiveM المخصصة بث الإنترنت بصيغ MP3 أو AAC لأنها مدعومة على نطاق واسع وسهلة الإعداد. يمكنك استضافة خادم Icecast أو Shoutcast خاص بك للتحكم الكامل، أو استخدام روابط بث راديو عامة. لكل محطة، خزّن رابط البث، اسم العرض، علامة النوع، وصورة شعار اختيارية لواجهة المستخدم. فكر في إضافة دعم لروابط YouTube وSoundCloud عبر خدمات البروكسي، مع مراعاة شروط الخدمة وقيود حقوق النشر. لوظائف الدي جي المباشرة، دمج مع خدمات توفر تحويل RTMP إلى HTTP حتى يتمكن الدي جي من البث باستخدام OBS أو برامج مماثلة:

Config.Stations = {
    ['station_1'] = {
        name = 'Agency Radio',
        genre = 'Hip Hop',
        url = 'https://stream.example.com/hiphop',
        logo = 'station_hiphop.webp',
        description = 'The freshest beats in Los Santos',
    },
    ['station_2'] = {
        name = 'LS Rock Radio',
        genre = 'Rock',
        url = 'https://stream.example.com/rock',
        logo = 'station_rock.webp',
        description = 'Classic and modern rock anthems',
    },
    ['station_3'] = {
        name = 'Vinewood Lounge',
        genre = 'Jazz / Lofi',
        url = 'https://stream.example.com/lofi',
        logo = 'station_lofi.webp',
        description = 'Smooth vibes for the night drive',
    },
    ['station_live'] = {
        name = 'Live DJ',
        genre = 'Live',
        url = 'https://stream.example.com/live',
        logo = 'station_live.webp',
        description = 'Live DJ sets from the community',
        isLive = true,
    },
}

راديو المركبة المتزامن

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

-- Server-side: Sync radio state via state bags
RegisterNetEvent('radio:server:setStation', function(vehicleNet, stationId)
    local src = source
    local vehicle = NetworkGetEntityFromNetworkId(vehicleNet)
    if not DoesEntityExist(vehicle) then return end

    -- Verify player is the driver
    local ped = GetPlayerPed(src)
    if GetPedInVehicleSeat(vehicle, -1) ~= ped then return end

    -- Set station on vehicle state bag
    Entity(vehicle).state:set('radioStation', stationId, true)
end)

-- Client-side: Listen for state bag changes
AddStateBagChangeHandler('radioStation', nil, function(bagName, key, value)
    if not value then return end
    local entity = GetEntityFromStateBagName(bagName)
    if not DoesEntityExist(entity) then return end

    local ped = PlayerPedId()
    if not IsPedInVehicle(ped, entity, false) then return end

    SetStation(value)
end)

الصوت المكاني وأنظمة البومبوكس

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

تكامل قائمة تشغيل اللاعب والهاتف

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

المصور وتصميم واجهة المستخدم

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

اعتبارات الأداء

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

جاهز للبدء؟

احصل على السكربتات من متجرنا، أو انضم إلى Discord للدعم والتحديثات ونظرة على ما هو قادم.