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

مثال addSpherezone من ox_target - كود Lua لـ QBCore و ESX

مثال addSpherezone من ox_target: كود Lua للنسخ واللصق، جاهز للاستخدام. يشمل أيضًا addBoxZone، addLocalEntity و qb-target addGlobalPlayer. QBCore و ESX.

Agency Scripts

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

ما هو نظام الهدف ولماذا تستخدمه؟

نظام استهداف، يُسمى أحيانًا eye-target أو نظام تفاعل، يحل محل الطريقة التقليدية بالضغط على مفتاح أثناء الاقتراب من شيء ما. بدلاً من الاقتراب من NPC والضغط E على أمل أن تكون قريبًا بما فيه الكفاية، يمسك اللاعبون مفتاحًا (عادةً اليسار Alt) للدخول إلى وضع الاستهداف، الذي يعرض علامة تصويب صغيرة في وسط الشاشة. عندما تحوم علامة التصويب فوق كيان، أو جسم، أو منطقة قابلة للاستهداف، يظهر قائمة بالإجراءات المتاحة. هذا النهج أكثر بديهية بشكل كبير لأنه يمنح اللاعبين تحكمًا دقيقًا فيما يتفاعلون معه. في مشهد مزدحم مع عدة NPCs، مركبات، وأجسام، يتيح نظام الاستهداف اختيار العنصر الذي تريده بالضبط دون الصراع مع مطالبات التفاعل المتداخلة. أكثر التنفيذات شعبية هي ox_target (من فريق Overextended) و qb-target (مصمم لـ QBCore). يغطي هذا الدليل كلاهما، مع التركيز على ox_target لأنه مستقل عن الإطار وأكثر تطوراً.

ox_target مقابل qb-target: أيهما تختار

كلا النظامين يحققان نفس الهدف لكن يختلفان في تصميم API والأداء. ox_target مستقل، يعمل مع أي إطار عمل، يستخدم raycasting لاكتشاف الكيانات، وله API أنظف مع دعم أفضل لـ TypeScript. qb-target مُدمج بإحكام مع QBCore، يستخدم نهج raycast مشابه، وكان موجودًا لفترة أطول لذا تدعم المزيد من السكربتات الموجودة ذلك مباشرة. إذا كنت تبدأ خادمًا جديدًا أو تستخدم ESX/standalone، استخدم ox_target. إذا كنت على QBCore ومعظم السكربتات التي اشتريتها تستخدم تصديرات qb-target، فإن الالتزام بـ qb-target يتجنب مشاكل التوافق. الخبر الجيد هو أن ox_target يتضمن طبقة توافق مع qb-target، لذا السكربتات التي تستدعي exports['qb-target'] سيعمل مع ox_target مثبتًا. العكس غير صحيح، لذلك السكربتات المكتوبة لـ ox_target لن تعمل مع qb-target بدون تعديل.

إضافة أهداف لكيانات محددة

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

-- client.lua: Entity targeting with ox_target

-- Create a shop NPC and add target
local model = joaat('a_m_m_business_01')
lib.requestModel(model)

local ped = CreatePed(0, model, 25.7, -1347.3, 29.5, 270.0, false, false)
FreezeEntityPosition(ped, true)
SetEntityInvincible(ped, true)
SetBlockingOfNonTemporaryEvents(ped, true)

-- Add target to this specific ped
exports.ox_target:addLocalEntity(ped, {
    {
        name = 'open_shop',
        icon = 'fas fa-store',
        label = 'Open General Store',
        distance = 2.5,
        onSelect = function(data)
            -- data.entity contains the entity handle
            TriggerEvent('shop:open', 'general')
        end,
    },
    {
        name = 'talk_to_clerk',
        icon = 'fas fa-comment',
        label = 'Talk to Clerk',
        distance = 2.0,
        canInteract = function(entity, distance, coords, name)
            -- Only show if player has completed a quest
            return PlayerData.questComplete == true
        end,
        onSelect = function()
            TriggerEvent('dialogue:start', 'clerk_01')
        end,
    },
})

-- Clean up on resource stop
AddEventHandler('onResourceStop', function(resource)
    if resource == GetCurrentResourceName() then
        exports.ox_target:removeLocalEntity(ped)
        DeleteEntity(ped)
    end
end)

أهداف قائمة على النموذج: استهداف كل النسخ

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

-- client.lua: Model-based targeting

-- ox_target: Target all ATM models
exports.ox_target:addModel({'prop_atm_01', 'prop_atm_02', 'prop_atm_03', 'prop_fleeca_atm'}, {
    {
        name = 'use_atm',
        icon = 'fas fa-credit-card',
        label = 'Use ATM',
        distance = 1.5,
        onSelect = function(data)
            TriggerEvent('banking:openATM')
        end,
    },
})

-- Target all vending machines
exports.ox_target:addModel({
    'prop_vend_coffe_01',
    'prop_vend_soda_01',
    'prop_vend_soda_02',
    'prop_vend_water_01',
}, {
    {
        name = 'buy_drink',
        icon = 'fas fa-mug-hot',
        label = 'Buy Drink ($5)',
        distance = 1.5,
        onSelect = function(data)
            local model = GetEntityModel(data.entity)
            TriggerServerEvent('vending:buy', model)
        end,
    },
})

-- qb-target equivalent for comparison
exports['qb-target']:AddTargetModel({'prop_atm_01', 'prop_atm_02'}, {
    options = {
        {
            type = 'client',
            event = 'banking:openATM',
            icon = 'fas fa-credit-card',
            label = 'Use ATM',
        },
    },
    distance = 1.5,
})

أهداف قائمة على المناطق: مناطق تفاعل غير مرئية

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

-- client.lua: Zone-based targeting

-- ox_target: Box zone for a reception desk
exports.ox_target:addBoxZone({
    coords = vec3(441.8, -981.0, 30.7),
    size = vec3(2.0, 1.0, 1.5),
    rotation = 0,
    debug = true,  -- Shows the box in-game, set false for production
    options = {
        {
            name = 'check_in',
            icon = 'fas fa-clipboard-check',
            label = 'Check In at Reception',
            onSelect = function()
                TriggerEvent('police:checkIn')
            end,
            canInteract = function()
                return PlayerData.job == 'police'
            end,
        },
    },
})

-- Sphere zone for a ground stash
local stashZone = exports.ox_target:addSphereZone({
    coords = vec3(128.4, -1280.5, 29.0),
    radius = 0.5,
    debug = false,
    options = {
        {
            name = 'open_stash',
            icon = 'fas fa-box-open',
            label = 'Open Stash',
            distance = 1.5,
            onSelect = function()
                TriggerServerEvent('stash:open', 'ground_stash_01')
            end,
        },
    },
})

-- Remove zone later if needed
exports.ox_target:removeZone(stashZone)

الرؤية الشرطية مع canInteract

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

-- client.lua: Advanced canInteract examples

-- Vehicle interaction with multiple conditional options
exports.ox_target:addGlobalVehicle({
    {
        name = 'repair_vehicle',
        icon = 'fas fa-wrench',
        label = 'Repair Vehicle',
        distance = 3.0,
        bones = {'engine'},  -- Only when targeting the engine area
        canInteract = function(entity)
            local health = GetVehicleEngineHealth(entity)
            return health < 900.0  -- Only show if damaged
        end,
        onSelect = function(data)
            TriggerEvent('mechanic:repair', data.entity)
        end,
    },
    {
        name = 'lockpick_vehicle',
        icon = 'fas fa-key',
        label = 'Lockpick Door',
        distance = 2.0,
        bones = {'door_dside_f', 'door_pside_f'},
        canInteract = function(entity)
            local locked = GetVehicleDoorLockStatus(entity)
            local hasItem = exports.ox_inventory:Search('count', 'lockpick') > 0
            return locked == 2 and hasItem
        end,
        onSelect = function(data)
            TriggerEvent('lockpick:start', data.entity)
        end,
    },
    {
        name = 'open_trunk',
        icon = 'fas fa-box',
        label = 'Open Trunk',
        distance = 2.5,
        bones = {'boot'},
        canInteract = function(entity)
            return not IsVehicleDoorFullyOpen(entity, 5)
        end,
        onSelect = function(data)
            SetVehicleDoorOpen(data.entity, 5, false, false)
            TriggerEvent('inventory:openTrunk', data.entity)
        end,
    },
})

الأهداف العالمية: تفاعلات اللاعب والمركبة

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

-- client.lua: Global player and vehicle targets

-- Target any player for EMS interactions
exports.ox_target:addGlobalPlayer({
    {
        name = 'revive_player',
        icon = 'fas fa-heartbeat',
        label = 'Revive Player',
        distance = 2.0,
        canInteract = function(entity)
            -- Only show if player is EMS and target is downed
            local isEms = PlayerData.job == 'ambulance'
            local targetState = Entity(entity).state.isDead
            return isEms and targetState == true
        end,
        onSelect = function(data)
            local targetServerId = GetPlayerServerId(NetworkGetPlayerIndexFromPed(data.entity))
            TriggerServerEvent('ems:revive', targetServerId)
        end,
    },
    {
        name = 'give_item',
        icon = 'fas fa-hand-holding',
        label = 'Give Item',
        distance = 2.0,
        onSelect = function(data)
            local targetServerId = GetPlayerServerId(NetworkGetPlayerIndexFromPed(data.entity))
            TriggerEvent('inventory:giveItem', targetServerId)
        end,
    },
})

نصائح الأداء والأخطاء الشائعة

أنظمة الاستهداف محسنة جيدًا لكنها قد تؤثر على الأداء إذا أسيء استخدامها. الخطأ الأكثر شيوعًا هو تسجيل العديد من مناطق الاستهداف مع تمكين وضع التصحيح في الإنتاج. يرسم التصحيح أشكالًا سلكية لكل منطقة، وهو مكلف عندما يكون لديك العشرات منها. دائمًا قم بضبط debug = false قبل النشر. خطأ آخر هو وضع منطق مكلف داخل canInteract functions. بما أن هذا يعمل في كل إطار أثناء التحويم، حتى exports.ox_inventory:Search يمكن أن تتراكم المكالمات إذا كان لديك عشرة خيارات جميعها تجري فحوصات الجرد في نفس الوقت. خزّن هذه القيم بدلاً من ذلك وقم بتحديثها عند تغير الجرد. وأخيرًا، قم دائمًا بتنظيف الأهداف عند توقف المورد الخاص بك. استخدم onResourceStop معالج حدث لإزالة جميع أهداف المناطق، النماذج، والكيانات. الأهداف المتسربة تبقى في الذاكرة وقد تسبب أخطاء أو خيارات مكررة إذا تم إعادة تشغيل المورد. ox_target يتعامل مع بعض التنظيف تلقائيًا للكيانات المحلية، لكن التنظيف الصريح هو دائمًا النهج الأكثر أمانًا.

جاهز للبدء؟

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