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

سلوك NPC الذكي في FiveM: برمجة NPCs أذكى

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

Agency Scripts

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

لماذا تهم ذكاء NPC المخصص

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

تسلسلات المهام: أساس سلوك NPC

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

-- Create a task sequence for a shopkeeper NPC
local function CreateShopkeeperRoutine(ped, counterCoords, counterHeading)
    local seq = 0
    -- Open a new sequence (pass 0 as reference, it returns a handle)
    OpenSequenceTask(seq)

    -- Walk to the counter position
    TaskGoStraightToCoord(0, counterCoords.x, counterCoords.y, counterCoords.z, 1.0, -1, counterHeading, 0.5)

    -- Turn to face customers
    TaskAchieveHeading(0, counterHeading, 2000)

    -- Play idle animation at counter
    TaskStartScenarioInPlace(0, 'PROP_HUMAN_SHOP_INTERACT', 0, true)

    CloseSequenceTask(seq)

    -- Assign sequence to the ped
    TaskPerformSequence(ped, seq)
    ClearSequenceTask(seq)
end

-- Usage
local shopkeeper = CreateNPC('s_m_m_shopkeep_01', vector3(25.7, -1347.3, 29.5))
CreateShopkeeperRoutine(shopkeeper, vector3(25.7, -1347.3, 29.5), 270.0)

نظام السيناريو

السيناريوهات هي حلقات رسوم متحركة محددة مسبقاً تجعل NPCs تبدو وكأنها تؤدي أنشطة حقيقية. تتضمن GTA V مئات السيناريوهات المدمجة تتراوح بين التدخين والشرب إلى استخدام الصراف الآلي، العمل على مكتب، أو القيام بتمارين الضغط في الحديقة. باستخدام TaskStartScenarioInPlace للأنشطة الثابتة أو TaskStartScenarioAtPosition للحالات المحددة بالموقع، يمكنك جعل الشخصيات غير القابلة للعب تؤدي أفعالًا مناسبة سياقيًا. المفتاح لذكاء اصطناعي محيطي مقنع هو مطابقة السيناريوهات مع المواقع. يجب أن يستخدم الساقي WORLD_HUMAN_BARTENDER ، يجب على الميكانيكي في المرآب استخدام WORLD_HUMAN_WELDING، ويجب على NPCs خارج ملهى ليلي استخدام WORLD_HUMAN_SMOKING أو WORLD_HUMAN_STAND_MOBILE.

-- Scenario-based ambient NPCs with location awareness
local AmbientNPCs = {
    {
        model = 'a_m_y_barman_01',
        coords = vector4(-561.2, 286.3, 82.2, 270.0),
        scenario = 'WORLD_HUMAN_BARTENDER',
        invincible = true,
    },
    {
        model = 's_m_y_doorman_01',
        coords = vector4(-560.8, 293.5, 82.2, 180.0),
        scenario = 'WORLD_HUMAN_GUARD_STAND',
        weapon = 'WEAPON_PISTOL',
    },
    {
        model = 'a_f_y_business_01',
        coords = vector4(-555.0, 290.1, 82.2, 90.0),
        scenario = 'WORLD_HUMAN_STAND_MOBILE',
    },
    {
        model = 's_m_y_ammucity_01',
        coords = vector4(22.0, -1105.0, 29.8, 160.0),
        scenario = 'PROP_HUMAN_SHOP_INTERACT',
        invincible = true,
    },
}

local spawnedPeds = {}

local function SpawnAmbientNPCs()
    for _, data in ipairs(AmbientNPCs) do
        local hash = GetHashKey(data.model)
        RequestModel(hash)
        while not HasModelLoaded(hash) do Wait(10) end

        local ped = CreatePed(4, hash, data.coords.x, data.coords.y, data.coords.z, data.coords.w, false, true)
        SetEntityInvincible(ped, data.invincible or false)
        SetBlockingOfNonTemporaryEvents(ped, true)
        FreezeEntityPosition(ped, false)

        if data.weapon then
            GiveWeaponToPed(ped, GetHashKey(data.weapon), 250, false, true)
        end

        TaskStartScenarioInPlace(ped, data.scenario, 0, true)
        SetModelAsNoLongerNeeded(hash)
        table.insert(spawnedPeds, ped)
    end
end

مسارات الدورية والتنقل عبر نقاط الطريق

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

-- Patrol route system with state management
local PatrolRoutes = {
    police_station_exterior = {
        speed = 1.0, -- walking speed
        waypoints = {
            { coords = vector3(441.0, -982.0, 30.7), heading = 90.0,  wait = 5000, scenario = 'WORLD_HUMAN_GUARD_STAND' },
            { coords = vector3(451.0, -982.0, 30.7), heading = 0.0,   wait = 3000 },
            { coords = vector3(451.0, -993.0, 30.7), heading = 270.0, wait = 5000, scenario = 'WORLD_HUMAN_COP_IDLES' },
            { coords = vector3(441.0, -993.0, 30.7), heading = 180.0, wait = 3000 },
        }
    },
}

local function StartPatrol(ped, routeName)
    local route = PatrolRoutes[routeName]
    if not route then return end

    CreateThread(function()
        local waypointIndex = 1
        while DoesEntityExist(ped) and not IsEntityDead(ped) do
            local wp = route.waypoints[waypointIndex]

            -- Walk to waypoint
            TaskGoStraightToCoord(ped, wp.coords.x, wp.coords.y, wp.coords.z, route.speed, -1, wp.heading, 0.5)

            -- Wait until ped reaches destination
            while DoesEntityExist(ped) and not IsEntityDead(ped) do
                local dist = #(GetEntityCoords(ped) - wp.coords)
                if dist < 1.5 then break end
                Wait(500)
            end

            -- Face the correct direction
            TaskAchieveHeading(ped, wp.heading, 1500)
            Wait(1500)

            -- Play scenario at waypoint if defined
            if wp.scenario then
                TaskStartScenarioInPlace(ped, wp.scenario, 0, false)
                Wait(wp.wait or 5000)
                ClearPedTasks(ped)
            else
                Wait(wp.wait or 3000)
            end

            -- Move to next waypoint (loop back to 1)
            waypointIndex = waypointIndex % #route.waypoints + 1
        end
    end)
end

مجموعات العلاقات وسلوك القتال

تحدد مجموعات العلاقات كيف يتفاعل NPCs مع بعضهم البعض ومع اللاعبين. بشكل افتراضي، جميع NPCs في مجموعات علاقات عامة بحالة محايدة، ولهذا السبب لا يقاتل أعضاء العصابات بعضهم البعض ولا يلاحق الضباط المجرمين تلقائيًا. تتيح مجموعات العلاقات المخصصة إنشاء ديناميكيات الفصائل حيث تكون الشرطة عدائية تجاه المجرمين المسلحين، وتهاجم العصابات المتنافسة بعضها البعض عند الرؤية، ويهرب المدنيون من القتال. ال SetRelationshipBetweenGroups native يقبل مستويات العلاقة من 0 (رفيق) حتى 3 (محايد) إلى 5 (كراهية)، مما يتحكم فيما إذا كان الشخصيات ستقاتل، تهرب، أو تتجاهل بعضها البعض. هذا النظام هو العمود الفقري لإنشاء حروب الفصائل الواقعية، النزاعات الإقليمية، واستجابات إنفاذ القانون.

-- Relationship group setup for faction-based AI
local RelGroups = {}

local function InitRelationshipGroups()
    -- Create custom groups
    AddRelationshipGroup('GANG_BALLAS', RelGroups)
    AddRelationshipGroup('GANG_FAMILIES', RelGroups)
    AddRelationshipGroup('GANG_VAGOS', RelGroups)
    AddRelationshipGroup('POLICE_CUSTOM', RelGroups)
    AddRelationshipGroup('CIVILIAN', RelGroups)

    -- Gangs hate rival gangs
    SetRelationshipBetweenGroups(5, GetHashKey('GANG_BALLAS'), GetHashKey('GANG_FAMILIES'))
    SetRelationshipBetweenGroups(5, GetHashKey('GANG_FAMILIES'), GetHashKey('GANG_BALLAS'))
    SetRelationshipBetweenGroups(5, GetHashKey('GANG_BALLAS'), GetHashKey('GANG_VAGOS'))
    SetRelationshipBetweenGroups(5, GetHashKey('GANG_VAGOS'), GetHashKey('GANG_BALLAS'))

    -- Police dislike all gangs
    SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_BALLAS'))
    SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_FAMILIES'))
    SetRelationshipBetweenGroups(4, GetHashKey('POLICE_CUSTOM'), GetHashKey('GANG_VAGOS'))

    -- Civilians flee from gangs
    SetRelationshipBetweenGroups(1, GetHashKey('CIVILIAN'), GetHashKey('GANG_BALLAS'))
    SetRelationshipBetweenGroups(1, GetHashKey('CIVILIAN'), GetHashKey('GANG_FAMILIES'))
end

-- Assign a ped to a relationship group
local function SetPedFaction(ped, faction)
    local groupHash = GetHashKey(faction)
    SetPedRelationshipGroupHash(ped, groupHash)
end

سلوك الهروب والقتال

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

-- Combat behavior presets
local CombatPresets = {
    gang_member = function(ped)
        SetPedCombatAttributes(ped, 46, true)   -- Can fight armed peds on foot
        SetPedCombatAttributes(ped, 5, true)    -- Can use cover
        SetPedCombatAttributes(ped, 2, true)    -- Can do drivebys
        SetPedCombatAbility(ped, 1)              -- Average combat ability
        SetPedCombatRange(ped, 1)                -- Medium range
        SetPedAccuracy(ped, 30)                  -- Poor accuracy
        SetPedFleeAttributes(ped, 0, false)      -- Don't flee
        SetPedCombatMovement(ped, 2)             -- Offensive movement
    end,

    security_guard = function(ped)
        SetPedCombatAttributes(ped, 46, true)
        SetPedCombatAttributes(ped, 5, true)    -- Use cover
        SetPedCombatAttributes(ped, 21, true)   -- Investigate dead peds
        SetPedCombatAbility(ped, 2)              -- Professional
        SetPedCombatRange(ped, 2)                -- Long range
        SetPedAccuracy(ped, 60)                  -- Good accuracy
        SetPedCombatMovement(ped, 1)             -- Defensive movement
    end,

    civilian = function(ped)
        SetPedFleeAttributes(ped, 2, true)       -- Flee immediately
        SetPedCombatAttributes(ped, 17, true)    -- Can be scared
        SetPedCombatAttributes(ped, 46, false)   -- Cannot fight
        SetPedCombatAbility(ped, 0)              -- Poor fighter
    end,
}

-- Apply a combat preset
local function ApplyCombatPreset(ped, presetName)
    local preset = CombatPresets[presetName]
    if preset then preset(ped) end
end

إدارة دورة حياة وأداء NPC

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

-- Proximity-based NPC spawner with zone management
local SPAWN_RANGE = 80.0
local DESPAWN_RANGE = 120.0
local activeZones = {}

CreateThread(function()
    while true do
        local playerCoords = GetEntityCoords(PlayerPedId())

        for zoneName, zone in pairs(NPCZones) do
            local dist = #(playerCoords - zone.center)

            if dist < SPAWN_RANGE and not activeZones[zoneName] then
                -- Spawn zone NPCs
                activeZones[zoneName] = SpawnZoneNPCs(zone)
            elseif dist > DESPAWN_RANGE and activeZones[zoneName] then
                -- Despawn zone NPCs
                for _, ped in ipairs(activeZones[zoneName]) do
                    if DoesEntityExist(ped) then
                        DeleteEntity(ped)
                    end
                end
                activeZones[zoneName] = nil
            end
        end

        Wait(2000) -- Check every 2 seconds
    end
end)

جمع كل شيء معًا

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

جاهز للبدء؟

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