ब्लॉग पर वापस
Tutorial12 मिनट पढ़ें

FiveM NPC संवाद सिस्टम: स्क्रिप्ट ब्रांचिंग बातचीत

अपने FiveM NPCs को असली व्यक्तित्व दें। संवाद वृक्ष, वॉइस लाइन्स, क्वेस्ट हुक्स और गहरे कहानी-चालित रोलप्ले सर्वरों के लिए सर्वश्रेष्ठ स्क्रिप्ट्स।

Agency Scripts

Agency Scripts के संस्थापक और प्रमुख डेवलपर

NPC संवाद क्यों महत्वपूर्ण है

NPC संवाद सिस्टम स्थिर पेड्स को जीवित पात्रों में बदल देते हैं जो आपके सर्वर को व्यक्तित्व देते हैं और खिलाड़ियों को गतिविधियों के माध्यम से मार्गदर्शन करते हैं। संवाद के बिना, NPC केवल इंटरैक्शन टारगेट होते हैं जो मेनू खोलते हैं। एक उचित संवाद सिस्टम के साथ, वे क्वेस्ट गिवर्स, कहानीकार, एटीट्यूड वाले दुकान मालिक, और सूचनाकर्ता बन जाते हैं जो खिलाड़ी की प्रतिष्ठा या नौकरी के आधार पर अलग-अलग प्रतिक्रिया देते हैं। एक संवाद सिस्टम सर्वर-विशिष्ट कथा, शाखित कहानियों, और खिलाड़ी विकल्पों के अवसर बनाता है जो भविष्य की बातचीत को प्रभावित करते हैं। सोचिए कि यह कितना अधिक आकर्षक होता है जब एक खिलाड़ी एक मैकेनिक NPC के पास जाता है जो उनका नाम लेकर अभिवादन करता है, उनकी कार की स्थिति पर टिप्पणी करता है, और यह देखकर विभिन्न सेवाएं प्रदान करता है कि वे लौटने वाले ग्राहक हैं या पहली बार आने वाले। यह स्तर की इमर्शन खिलाड़ियों को आपके सर्वर में निवेशित रखती है और उन्हें मानक ग्राइंड लूप से परे खोज करने के कारण देती है।

संवाद वृक्ष डेटा संरचना

किसी भी संवाद प्रणाली की नींव वह डेटा संरचना है जो बातचीत के प्रवाह को परिभाषित करती है। एक संवाद वृक्ष नोड्स से बना होता है, जहां प्रत्येक नोड में NPC का टेक्स्ट और खिलाड़ी प्रतिक्रिया विकल्पों की एक सूची होती है जो अन्य नोड्स से जुड़ी होती है। यह वृक्ष संरचना शाखित बातचीत, खिलाड़ी की स्थिति के आधार पर सशर्त मार्ग, और बातचीत के पहले बिंदुओं पर लौटने वाले लूप का समर्थन करती है। अपने संवाद नोड्स को डेटा-चालित डिज़ाइन करें ताकि सर्वर स्टाफ बिना Lua कोड को छुए नई बातचीत बना सके। यहां एक व्यावहारिक संवाद वृक्ष प्रारूप है:

Config.Dialogues = {
    ['mechanic_greeting'] = {
        npcName = 'Tony the Mechanic',
        nodes = {
            ['start'] = {
                text = "Hey there! Car giving you trouble, or are you just here for a tune-up?",
                animation = 'WORLD_HUMAN_WELDING',
                options = {
                    {
                        label = "I need repairs",
                        next = 'repairs',
                        condition = function(player)
                            return IsPlayerInVehicle(player)
                        end
                    },
                    {
                        label = "What services do you offer?",
                        next = 'services'
                    },
                    {
                        label = "Just browsing, thanks",
                        next = 'goodbye'
                    },
                }
            },
            ['repairs'] = {
                text = "Let me take a look... Yeah, your engine's seen better days. I can fix it up for $500. What do you say?",
                options = {
                    { label = "Fix it up", next = 'repair_accept', action = 'repair_vehicle' },
                    { label = "Too expensive", next = 'haggle' },
                    { label = "Never mind", next = 'goodbye' },
                }
            },
            ['haggle'] = {
                text = "Look, parts aren't cheap. But since you seem like a decent person, I can do $350. Final offer.",
                options = {
                    { label = "Deal!", next = 'repair_accept', action = 'repair_vehicle_discount' },
                    { label = "I'll pass", next = 'goodbye' },
                }
            },
            ['services'] = {
                text = "I do repairs, custom paint jobs, performance tuning, and tire changes. What catches your eye?",
                options = {
                    { label = "Tell me about tuning", next = 'tuning_info' },
                    { label = "Back to start", next = 'start' },
                }
            },
            ['repair_accept'] = {
                text = "Alright, give me a minute... Done! She's running smooth now. Take care of her out there.",
                options = {
                    { label = "Thanks, Tony!", next = 'end' },
                }
            },
            ['goodbye'] = {
                text = "No worries. Come back anytime you need help with your ride!",
                options = {
                    { label = "See you around", next = 'end' },
                }
            },
        }
    },
}

यह condition function विकल्पों पर आपको खिलाड़ी की वर्तमान स्थिति के आधार पर विकल्पों को गतिशील रूप से दिखाने या छिपाने देता है। मरम्मत विकल्प केवल तब दिखाई देता है जब खिलाड़ी वाहन में आया हो, क्वेस्ट-संबंधित विकल्प केवल तब दिखते हैं जब खिलाड़ी सही चरण पर पहुंचा हो, और VIP विकल्प केवल उन खिलाड़ियों के लिए प्रतिबंधित हो सकते हैं जिनके पास विशेष अनुमतियाँ हों। action फ़ील्ड जब कोई विशिष्ट विकल्प चुना जाता है तो सर्वर-साइड फ़ंक्शन को ट्रिगर करता है, संवाद विकल्पों को गेमप्ले परिणामों से जोड़ता है।

NPCs को स्पॉन करना और प्रबंधित करना

संवाद NPCs को विश्वसनीय रूप से स्पॉन करना आवश्यक है, उन्हें सुसंगत स्थानों पर रखना चाहिए, और GTA की दुनिया के अराजकता से उन्हें सुरक्षित रखना चाहिए। संवाद NPC स्पॉन करते समय, आपको मॉडल का अनुरोध करना होगा, पेड बनाना होगा, इसे मिशन एंटिटी के रूप में सेट करना होगा ताकि GTA की क्लीनअप सिस्टम इसे न हटाए, इसकी स्थिति को फ्रीज करना होगा ताकि यह इधर-उधर न भटके, और इसे अमर बनाना होगा ताकि खिलाड़ी आपके क्वेस्ट देने वाले को मार न सकें। मेमोरी बचाने के लिए एक केंद्रीकृत NPC मैनेजर का उपयोग करें जो खिलाड़ियों के पास होने पर पेड्स को स्पॉन करता है और जब कोई खिलाड़ी सीमा में नहीं होता तो उन्हें डीस्पॉन करता है:

local spawnedNPCs = {}

function SpawnDialogueNPC(npcId, config)
    if spawnedNPCs[npcId] then return end

    local model = GetHashKey(config.model)
    RequestModel(model)
    while not HasModelLoaded(model) do Wait(10) end

    local ped = CreatePed(0, model, config.coords.x, config.coords.y,
        config.coords.z, config.heading, false, true)

    SetEntityInvincible(ped, true)
    SetBlockingOfNonTemporaryEvents(ped, true)
    FreezeEntityPosition(ped, true)
    SetPedFleeAttributes(ped, 0, false)
    SetPedCombatAttributes(ped, 46, true)
    SetPedCanRagdoll(ped, false)
    SetEntityAsMissionEntity(ped, true, true)
    SetModelAsNoLongerNeeded(model)

    -- Play idle animation if configured
    if config.scenario then
        TaskStartScenarioInPlace(ped, config.scenario, 0, true)
    end

    spawnedNPCs[npcId] = { entity = ped, config = config }
    return ped
end

संयोजन SetBlockingOfNonTemporaryEvents और FreezeEntityPosition सुनिश्चित करता है कि आस-पास के विस्फोट, पुलिस पीछा, या आक्रामक खिलाड़ी जैसे परिवेशीय घटनाएं आपके NPC को भागने, लड़ने या ragdoll होने से न रोकें। इन सुरक्षा के बिना, खिलाड़ी एक क्वेस्ट देने वाले को जमीन पर पड़े हुए और हिलते हुए देख सकते हैं, जो एक गुजरती कार से टकराने के बाद पूरी तरह से आपकी बनाई गई immersion को तोड़ देता है।

सिनेमैटिक कैमरा सिस्टम

संवाद वार्तालापों के दौरान कैमरा कार्य मेनू इंटरैक्शन से सिनेमाई क्षण तक अनुभव को बढ़ाता है। जब संवाद शुरू होता है, तो NPC के चेहरे पर थोड़ा ऑफसेट के साथ ध्यान केंद्रित करने वाला कैमरा बनाएं, पृष्ठभूमि को धुंधला करने के लिए डेप्थ ऑफ फील्ड का उपयोग करें और बातचीत पर ध्यान आकर्षित करें। जैसे-जैसे बातचीत आगे बढ़ती है, कैमरा कोणों के बीच स्विच करें, जब खिलाड़ी विकल्प चुनता है तो उसे दिखाएं और जब NPC जवाब देता है तो वापस जाएं। GTA का मूल कैमरा सिस्टम आपको स्थिति, घुमाव, दृश्य क्षेत्र, और डेप्थ ऑफ फील्ड पर पूर्ण नियंत्रण देता है:

local dialogueCam = nil

function StartDialogueCamera(npcPed)
    local npcCoords = GetEntityCoords(npcPed)
    local npcHeading = GetEntityHeading(npcPed)
    local playerPed = PlayerPedId()

    -- Calculate camera position offset from NPC face
    local angleRad = math.rad(npcHeading + 160)
    local camX = npcCoords.x + (math.sin(angleRad) * 1.5)
    local camY = npcCoords.y + (math.cos(angleRad) * 1.5)
    local camZ = npcCoords.z + 0.6

    dialogueCam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
    SetCamCoord(dialogueCam, camX, camY, camZ)
    PointCamAtPedBone(dialogueCam, npcPed, 31086, 0.0, 0.0, 0.1, true)  -- Head bone

    -- Depth of field for cinematic look
    SetCamNearDof(dialogueCam, 0.5)
    SetCamFarDof(dialogueCam, 3.5)
    SetCamDofStrength(dialogueCam, 0.6)
    SetCamUseShallowDofMode(dialogueCam, true)

    SetCamFov(dialogueCam, 40.0)  -- Tighter shot
    SetCamActive(dialogueCam, true)
    RenderScriptCams(true, true, 800, true, false)

    -- Disable player controls during dialogue
    SetPlayerControl(PlayerId(), false, 0)

    -- Make player face NPC
    TaskTurnPedToFaceEntity(playerPed, npcPed, 1000)
end

function StopDialogueCamera()
    if dialogueCam then
        RenderScriptCams(false, true, 500, true, false)
        DestroyCam(dialogueCam, true)
        dialogueCam = nil
        SetPlayerControl(PlayerId(), true, 0)
    end
end

यह PointCamAtPedBone native विशेष रूप से शक्तिशाली है क्योंकि यह NPC के सिर पर कैमरा फोकस लॉक करता है चाहे कोई भी हल्की एनिमेशन मूवमेंट हो, बातचीत के दौरान फ्रेमिंग को सुसंगत रखता है। में संक्रमण अवधि RenderScriptCams चमकदार कट्स के बजाय चिकनी कैमरा फीका प्रभाव बनाएं, और अपने सर्वर की गति के लिए सही अनुभव खोजने के लिए 500ms और 1000ms के बीच मानों के साथ प्रयोग करें।

सबटाइटल डिस्प्ले सिस्टम

एक उपशीर्षक प्रणाली NPC के संवाद पाठ को स्क्रीन के नीचे दृश्यात्मक रूप से आकर्षक तरीके से प्रस्तुत करती है, जैसा कि कहानी-चालित खेल बातचीत दिखाते हैं। पूरे टेक्स्ट ब्लॉक को एक बार में डंप करने के बजाय, एक टाइपराइटर प्रभाव लागू करें जो पात्रों को एक-एक करके प्रकट करता है, जिससे यह भ्रम पैदा होता है कि NPC सक्रिय रूप से बोल रहा है। उपशीर्षक प्रदर्शन के लिए NUI का उपयोग करें क्योंकि यह आपको फोंट, रंग, एनिमेशन, और स्थिति पर पूर्ण CSS नियंत्रण देता है। प्रत्येक संवाद नोड के टेक्स्ट को सक्रिय होने पर NUI फ्रेम को भेजें, साथ ही NPC का नाम और कोई भी भावना टैग जो प्रदर्शन शैली को प्रभावित करना चाहिए:

// Subtitle display JavaScript (html/subtitles.js)
let typewriterTimeout = null;

window.addEventListener('message', (event) => {
    const data = event.data;

    if (data.action === 'showDialogue') {
        clearTimeout(typewriterTimeout);
        const container = document.getElementById('subtitle-container');
        const nameEl = document.getElementById('npc-name');
        const textEl = document.getElementById('dialogue-text');
        const optionsEl = document.getElementById('dialogue-options');

        container.style.display = 'block';
        nameEl.textContent = data.npcName;
        textEl.textContent = '';
        optionsEl.innerHTML = '';

        // Typewriter effect
        let charIndex = 0;
        const fullText = data.text;

        function typeNext() {
            if (charIndex < fullText.length) {
                textEl.textContent += fullText[charIndex];
                charIndex++;
                typewriterTimeout = setTimeout(typeNext, 30);
            } else {
                // Show options after text completes
                showOptions(data.options);
            }
        }
        typeNext();
    }

    if (data.action === 'hideDialogue') {
        document.getElementById('subtitle-container').style.display = 'none';
    }
});

function showOptions(options) {
    const optionsEl = document.getElementById('dialogue-options');
    options.forEach((opt, index) => {
        const btn = document.createElement('button');
        btn.className = 'dialogue-option';
        btn.innerHTML = `${index + 1} ${opt.label}`;
        btn.onclick = () => {
            fetch(`https://${GetParentResourceName()}/selectOption`, {
                method: 'POST',
                body: JSON.stringify({ index: index })
            });
        };
        optionsEl.appendChild(btn);
    });
}

सबटाइटल कंटेनर को एक अर्ध-पारदर्शी अंधेरे पृष्ठभूमि, गोल कोनों, और एक सूक्ष्म ग्रेडिएंट बॉर्डर के साथ स्टाइल करें जो आपके सर्वर के थीम से मेल खाता हो। इसे स्क्रीन के नीचे केंद्र में स्थित करें और इतना पैडिंग दें कि यह मिनीमैप या अन्य HUD तत्वों के साथ ओवरलैप न करे। कीबोर्ड शॉर्टकट जोड़ें ताकि खिलाड़ी बिना क्लिक किए विकल्पों को जल्दी से चुन सकें, जो बातचीत के प्रवाह के दौरान अधिक प्राकृतिक लगता है।

Quest सिस्टम एकीकरण

संवाद प्रणाली वास्तव में शक्तिशाली तब बनती है जब इसे क्वेस्ट फ्रेमवर्क से जोड़ा जाता है। संवाद वृक्ष का action प्रतिक्रिया विकल्पों में फ़ील्ड वह हुक पॉइंट प्रदान करता है जहां क्वेस्ट लॉजिक निष्पादित होता है। जब कोई खिलाड़ी संवाद के माध्यम से मिशन स्वीकार करता है, तो क्रिया हैंडलर को खिलाड़ी के क्वेस्ट लॉग में क्वेस्ट प्रविष्टि बनानी चाहिए, आवश्यक वेपॉइंट या उद्देश्य सेट करना चाहिए, और बाद के संवाद इंटरैक्शन के माध्यम से प्रगति को ट्रैक करना चाहिए। क्वेस्ट प्रगति को डेटाबेस में प्रति खिलाड़ी संग्रहीत करें ताकि वे डिस्कनेक्ट होकर वहीं से फिर से शुरू कर सकें। क्वेस्ट को स्टेट मशीन के रूप में डिज़ाइन करें जहां प्रत्येक स्थिति एक संवाद नोड और उद्देश्यों के सेट के अनुरूप होती है जिन्हें अगले संवाद के उपलब्ध होने से पहले पूरा करना होता है:

-- Server-side quest actions triggered by dialogue choices
local QuestActions = {
    ['accept_delivery_job'] = function(src, npcId)
        local Player = QBCore.Functions.GetPlayer(src)
        local citizenid = Player.PlayerData.citizenid

        -- Create quest entry
        MySQL.insert(
            'INSERT INTO player_quests (citizenid, quest_id, stage, started_at) VALUES (?, ?, ?, NOW())',
            {citizenid, 'tony_delivery_1', 'pickup'}
        )

        -- Set waypoint for pickup location
        TriggerClientEvent('quest:client:setWaypoint', src, {
            coords = vector3(482.5, -1311.2, 29.2),
            blipSprite = 501,
            blipColor = 5,
            label = 'Package Pickup'
        })

        TriggerClientEvent('QBCore:Notify', src, 'Quest started: Special Delivery', 'success')
    end,

    ['complete_delivery'] = function(src, npcId)
        local Player = QBCore.Functions.GetPlayer(src)
        local citizenid = Player.PlayerData.citizenid

        MySQL.update(
            'UPDATE player_quests SET stage = ?, completed_at = NOW() WHERE citizenid = ? AND quest_id = ?',
            {'completed', citizenid, 'tony_delivery_1'}
        )

        Player.Functions.AddMoney('cash', 1500, 'quest-delivery-reward')
        TriggerClientEvent('QBCore:Notify', src, 'Quest complete! Reward: $1,500', 'success')
    end,
}

क्वेस्ट स्टेज का उपयोग करके गतिशील रूप से यह संशोधित करें कि कौन से संवाद नोड उपलब्ध हैं। जब खिलाड़ी डिलीवरी पूरी करने के बाद टोनी के पास लौटता है, तो संवाद प्रणाली क्वेस्ट स्टेज की जांच करती है और प्रारंभिक अभिवादन के बजाय पुरस्कार के साथ पूर्णता संवाद प्रस्तुत करती है। यह एक प्राकृतिक बातचीत प्रवाह बनाता है जहां NPC खिलाड़ी की प्रगति को स्वीकार करते हैं और तदनुसार प्रतिक्रिया करते हैं, जिससे दुनिया उत्तरदायी और जीवंत महसूस होती है।

NPC एनिमेशन और एक्सप्रेशंस

स्थिर NPC जो बात करते समय पूरी तरह से स्थिर रहते हैं, रोबोटिक लगते हैं और इमर्शन को तोड़ते हैं। अपने संवाद प्रणाली में एनिमेशन समर्थन जोड़ें ताकि NPC बातचीत के दौरान इशारे करें, भाव व्यक्त करें, और प्रतिक्रिया दें। GTA V में इशारों, चेहरे के भावों, और शरीर की भाषा को कवर करने वाली विशाल एनिमेशन डिक्शनरीज़ हैं जिन्हें आप संवाद के विशिष्ट बिंदुओं पर ट्रिगर कर सकते हैं। नोड स्तर पर एनिमेशन असाइन करें ताकि प्रत्येक संवाद पंक्ति के साथ उसका अपना इशारा हो। जब NPC अच्छी खबर देता है, तो एक खुशमिजाज हाथ हिलाने वाली एनिमेशन चलाएं। जब वे कुछ गंभीर चर्चा करते हैं, तो एक सख्त क्रॉस-आर्म्स पोज़ का उपयोग करें। खिलाड़ी प्रतिक्रियाओं के बीच खाली क्षणों के लिए, सोचने या प्रतीक्षा करने वाली एनिमेशन लूप करें। आप चेहरे की एनिमेशन नेटिव्स जैसे भी उपयोग कर सकते हैं SetFacialIdleAnimOverride NPC के आरामदायक भाव को बातचीत के मूड के अनुसार बदलने के लिए, जिससे वे खुश, गुस्से में, डरे हुए, या भ्रमित दिखें। सबसे विश्वसनीय प्रदर्शन के लिए शरीर और चेहरे की एनिमेशन संयोजित करें, और हमेशा गेम में एनिमेशन का परीक्षण करें क्योंकि कुछ एनिमेशन डिक्शनरी विभिन्न पेड मॉडलों पर अलग दिखती हैं, और जो एक पुरुष पेड पर काम करता है वह महिला पेड पर क्लिप या अजीब लग सकता है।

प्रदर्शन और सर्वोत्तम प्रथाएँ

एक संवाद प्रणाली जो मानचित्र पर दर्जनों NPC उत्पन्न करती है उसे सावधानीपूर्वक प्रदर्शन प्रबंधन की आवश्यकता होती है। केवल तब NPC उत्पन्न करें जब खिलाड़ी रेंडरिंग दूरी के भीतर हों, आमतौर पर 50-100 मीटर, और जब कोई खिलाड़ी आसपास न हो तो उन्हें हटाएं। सभी NPC स्पॉन दूरी प्रबंधन के लिए एकल थ्रेड का उपयोग करें बजाय प्रत्येक NPC के लिए अलग थ्रेड बनाने के, क्योंकि यह क्लाइंट साइड पर ओवरहेड को काफी कम करता है। संवाद वृक्ष डेटा को संसाधन प्रारंभ में कैश करें बजाय बातचीत के दौरान फ़ाइलें पढ़ने के। अपने संवाद कैमरा संक्रमणों को चिकना रखें लेकिन अत्यधिक कैमरा निर्माण और विनाश न करें क्योंकि कैमरा संचालन का मापन योग्य प्रदर्शन लागत होती है। जब कई खिलाड़ी एक ही NPC के साथ एक साथ बातचीत करते हैं, तो प्रत्येक खिलाड़ी को अपनी स्वतंत्र संवाद उदाहरण मिलनी चाहिए, जिसका अर्थ है कि संवाद स्थिति NPC इकाई पर नहीं बल्कि प्रति खिलाड़ी संग्रहीत होनी चाहिए। जब खिलाड़ी डिस्कनेक्ट हो या बातचीत के बीच में दूर चले जाएं तो सभी संवाद संसाधनों को साफ करें, कैमरा नष्ट करें, NUI फोकस रिलीज़ करें, और खिलाड़ी नियंत्रण पुनर्स्थापित करें। अपने संवाद सिस्टम का परीक्षण सबसे खराब स्थिति को ध्यान में रखकर करें: खिलाड़ी का गेम संवाद के बीच क्रैश हो जाए, या वे कैमरा सक्रिय रहते हुए alt-F4 दबाएं। आपका सिस्टम इन मामलों का पता लगाना चाहिए और शेष कैमरों या लॉक नियंत्रणों को पुनः कनेक्ट पर रोकने के लिए सुंदरता से साफ़ करना चाहिए।

शुरू करने के लिए तैयार?

हमारी शॉप से स्क्रिप्ट लें, या सपोर्ट, अपडेट और आगे आने वाली चीज़ों की झलक के लिए Discord से जुड़ें।