Tutoriel 2026-04-24

Guide du système de ciblage FiveM

OntelMonke

OntelMonke

Administrateur et développeur chez Agency Scripts

Qu'est-ce qu'un système cible et pourquoi en utiliser un ?

Un système de cible, parfois appelé système de cible oculaire ou système d'interaction, remplace l'approche traditionnelle consistant à appuyer sur une touche à proximité de quelque chose. Au lieu de marcher vers un PNJ et d'appuyer sur E en espérant que tu es suffisamment proche, les joueurs maintiennent une touche (généralement alt gauche) pour accéder au mode de ciblage, qui affiche un petit réticule au centre de l'écran. Lorsque le réticule survole une entité, un objet ou une zone pouvant être ciblé, un menu d'actions disponibles apparaît. Cette approche est considérablement plus intuitive car elle donne aux joueurs un contrôle précis sur ce avec quoi ils interagissent. Dans une scène bondée avec plusieurs PNJ, véhicules et objets, un système de cible tu permet de choisir exactement celui que tu veux sans avoir à tu battre avec des invites d'interaction qui se chevauchent. Les deux implémentations les plus populaires sont ox_target (de l'équipe Overextended) et qb-cible (construit pour QBCore). Ce guide couvre les deux, en mettant l'accent sur ox_target car il est indépendant du framework et développé plus activement.

ox_target vs qb-target : lequel choisir

Les deux systèmes atteignent le même objectif mais diffèrent par la conception et les performances du API. ox_target est autonome, fonctionne avec n'importe quel framework, utilise le raycasting pour la détection d'entités et dispose d'un API plus propre avec une meilleure prise en charge de TypeScript. qb-cible est étroitement intégré à QBCore, utilise une approche raycast similaire et existe depuis plus longtemps, donc davantage de scripts existants le prennent en charge dès le départ. Si tu démarres un nouveau serveur ou utilisez ESX/standalone, utilisez ox_target. Si tu utilises QBCore et que la plupart de tes scripts achetés utilisent les exportations qb-target, rester avec qb-target évite les problèmes de compatibilité. La bonne nouvelle est que ox_target inclut une couche de compatibilité qb-target, donc les scripts qui appellent exports['qb-target'] fonctionnera avec ox_target installé. L'inverse n'est cependant pas vrai, donc les scripts écrits pour ox_target ne fonctionneront pas avec qb-target sans modification.

Ajout de cibles à des entités spécifiques

Les cibles d'entité attachent des options d'interaction à une entité spécifique générée comme un véhicule, un véhicule ou un objet. Ceci est utile lorsque tu crées un PNJ pour un travail et que tu souhaites que les joueurs interagissent spécifiquement avec ce PNJ plutôt qu'avec n'importe quel modèle pédagogique dans le monde. La cible suit l'entité, donc si le PNJ se déplace, le point d'interaction se déplace avec lui. Tu fournissez le handle de l'entité, le tableau d'options et une distance facultative qui contrôle la distance à laquelle le réticule peut détecter l'entité. Nettoyez toujours les cibles d'entité lorsque l'entité est supprimée ou que la ressource s'arrête pour éviter que les cibles orphelines ne restent en mémoire.

-- 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)

Cibles basées sur un modèle : ciblage de toutes les instances

Les cibles de modèle appliquent des options d'interaction à chaque instance d'un modèle spécifique dans le monde du jeu. Ceci est extrêmement puissant pour des choses comme les guichets automatiques, les distributeurs automatiques, les bennes à ordures, les poubelles ou tout autre accessoire existant à plusieurs endroits sur la carte. Au lieu d'enregistrer manuellement des cibles à des centaines de coordonnées, tu enregistres une fois par nom de modèle et chaque objet correspondant devient interactif. L'impact sur les performances est minime car le système cible vérifie uniquement les modèles dans la plage raycast, et non tous les objets du monde entier. Soyez cependant prudent avec les modèles courants. Cibler chaque prop_bin_01a sur la carte donne aux joueurs des centaines de points d'interaction, alors assurez-tu que l'action a du sens pour toutes les instances de ce modèle.

-- 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,
})

Cibles basées sur des zones : zones d'interaction invisibles

Les cibles de zone créent des zones d'interaction invisibles à des coordonnées spécifiques. Ceux-ci sont idéaux pour les endroits où aucun objet physique n'existe mais où tu souhaites quand même que les joueurs interagissent, comme un comptoir à l'intérieur d'un bâtiment, un endroit spécifique au sol pour une cachette ou une zone devant une porte pour une option de crochetage. Tu définissez des zones sous forme de boîtes ou de sphères avec des coordonnées, une taille et une rotation. La zone n'est active que lorsque le réticule pointe vers la zone de la zone, ce qui la rend précise et évite les interactions accidentelles. Les cibles de zone sont également la solution idéale pour ajouter des interactions à des parties de MLO de carte où les accessoires sont intégrés à la carte et ne peuvent pas être ciblés par le modèle.

-- 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)

Visibilité conditionnelle avec canInteract

Le canInteract La fonction est ce qui rend les systèmes cibles vraiment puissants. Il s'exécute chaque fois que le réticule survole une cible et détermine si chaque option doit être affichée ou masquée. Cela tu permet de créer des interactions contextuelles où un policier voit des options différentes de celles d'un civil, où une option de crochetage n'apparaît que si le joueur possède un objet de crochetage, ou où une option de réparation de véhicule ne s'affiche que lorsque le véhicule est endommagé. La fonction reçoit le descripteur d'entité, la distance, les coordonnées et le nom de l'option comme paramètres. Gardez la logique canInteract léger car il fonctionne à chaque image pendant que le joueur survole la cible. Évitez les requêtes de base de données ou les calculs lourds à l’intérieur. Au lieu de cela, mettez en cache les données requises dans une variable locale qui est mise à jour via des événements.

-- 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,
    },
})

Cibles mondiales : interactions entre les joueurs et les véhicules

Les objectifs mondiaux s'appliquent à chaque joueur ou à chaque véhicule dans le monde sans qu'il soit nécessaire de les enregistrer individuellement. addGlobalPlayer ajoute des options qui apparaissent lorsque tu cibles un autre joueur, ce qui est parfait pour les interactions telles que donner des objets, vérifier les pièces d'identité, menotter des suspects ou soigner d'autres joueurs. addGlobalVehicle ajoute des options pour chaque véhicule, utiles pour les mécaniciens, les recherches de police ou les systèmes de ravitaillement. Utiliser canInteract libéralement avec des objectifs mondiaux pour éviter une surcharge d’options. Un civil ne devrait pas voir d’option de brassard, et un mécanicien ne devrait voir les options de réparation que lorsqu’il est en service. Les cibles globales constituent le moyen le plus pratique d’ajouter des interactions universelles, mais aussi le plus facile à utiliser à mauvais escient. Réfléchissez donc bien au moment où chaque option doit réellement être visible.

-- 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,
    },
})

Conseils de performance et erreurs courantes

Les systèmes cibles sont bien optimisés mais peuvent toujours avoir un impact sur les performances s'ils sont mal utilisés. L'erreur la plus courante consiste à enregistrer trop de cibles de zone avec le mode débogage activé en production. Le rendu de débogage dessine des formes filaires pour chaque zone, ce qui coûte cher lorsque tu en avez des dizaines. Toujours réglé debug = false avant de déployer. Une autre erreur est de mettre une logique coûteuse à l'intérieur canInteract fonctions. Puisque cela s'exécute sur chaque image en survol, même une seule exports.ox_inventory:Search l'appel peut s'additionner si tu disposes de dix options exécutant toutes des vérifications d'inventaire simultanément. Mettez plutôt ces valeurs en cache et mettez-les à jour lorsque l’inventaire change. Enfin, nettoyez toujours les cibles lorsque ton ressource s'arrête. Utilisez le onResourceStop gestionnaire d'événements pour supprimer toutes les cibles de zone, de modèle et d'entité. Les cibles divulguées persistent en mémoire et peuvent provoquer des erreurs ou des options en double si la ressource est redémarrée. ox_target gère automatiquement certains nettoyages pour les entités locales, mais le nettoyage explicite constitue toujours l'approche la plus sûre.

Partager cet article

Prêt à améliorer votre serveur ?

Découvrez nos scripts FiveM premium dans la boutique Agency Scripts ou rejoignez notre communauté Discord pour le support et les mises à jour.