Zurück zum Blog
Tutorial8 Min. Lesezeit

ox_target addSpherezone Beispiel, QBCore- und ESX-Lua-Code

Beispiel für ox_target addSpherezone: kopierfertiger Lua-Code. Mit addBoxZone, addLocalEntity und qb-target addGlobalPlayer. Für QBCore und ESX.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Was ist ein Target-System und warum sollte man es verwenden?

Ein Target-System, manchmal auch Eye-Target- oder Interaktionssystem genannt, ersetzt die herkömmliche Methode, in der Nähe von etwas eine Taste zu drücken. Statt zu einem NPC zu gehen und eine Taste zu drücken E und zu hoffen, dass du nah genug dran bist, halten Spieler eine Taste gedrückt, meist die linke Alt-Taste, um den Targeting-Modus zu aktivieren. Dabei erscheint ein kleines Fadenkreuz in der Bildschirmmitte. Zeigt das Fadenkreuz auf ein anvisierbares Entity, Objekt oder eine Zone, erscheint ein Menü mit verfügbaren Aktionen. Dieser Ansatz ist deutlich intuitiver, weil Spieler genau steuern können, womit sie interagieren. In einer belebten Szene mit mehreren NPCs, Fahrzeugen und Objekten kannst du mit einem Target-System gezielt das gewünschte Element auswählen, ohne mit überlappenden Interaktionshinweisen kämpfen zu müssen. Die beiden beliebtesten Implementierungen sind ox_target (vom Overextended-Team) und qb-target (für QBCore entwickelt). Dieser Guide behandelt beide, legt den Schwerpunkt aber auf ox_target, da es frameworkunabhängig ist und aktiver weiterentwickelt wird.

ox_target oder qb-target: Was solltest du wählen?

Beide Systeme erfüllen denselben Zweck, unterscheiden sich aber bei API-Design und Performance. ox_target ist standalone, funktioniert mit jedem Framework, verwendet Raycasting zur Erkennung von Entities und hat eine übersichtlichere API mit besserer TypeScript-Unterstützung. qb-target ist eng mit QBCore integriert, verwendet einen ähnlichen Raycast-Ansatz und gibt es schon länger, weshalb mehr bestehende Scripts es direkt unterstützen. Wenn du einen neuen Server startest oder ESX/standalone verwendest, nimm ox_target. Wenn du QBCore nutzt und die meisten deiner gekauften Scripts qb-target-Exports verwenden, vermeidest du Kompatibilitätsprobleme, wenn du bei qb-target bleibst. Gut zu wissen: ox_target enthält eine qb-target-Kompatibilitätsschicht. Scripts, die exports['qb-target'] aufrufen, funktionieren also mit installiertem ox_target. Umgekehrt gilt das jedoch nicht. Für ox_target geschriebene Scripts funktionieren ohne Anpassungen nicht mit qb-target.

Targets an bestimmte Entities anfügen

Entity-Targets fügen einem bestimmten gespawnten Entity, etwa einem Ped, Fahrzeug oder Objekt, Interaktionsoptionen hinzu. Das ist nützlich, wenn du für einen Job einen NPC erstellst und Spieler gezielt mit diesem NPC statt mit beliebigen Ped-Modellen in der Welt interagieren sollen. Das Target folgt dem Entity. Läuft der NPC also herum, bewegt sich der Interaktionspunkt mit ihm. Du gibst das Entity-Handle, das Options-Array und optional eine Distanz an, die festlegt, aus welcher Entfernung das Fadenkreuz das Entity erkennen kann. Entferne Entity-Targets immer, wenn das Entity gelöscht wird oder die Ressource stoppt, damit keine verwaisten Targets im Speicher zurückbleiben.

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

Modellbasierte Targets: Alle Instanzen anvisieren

Modell-Targets wenden Interaktionsoptionen auf jede Instanz eines bestimmten Modells in der Spielwelt an. Das ist äußerst praktisch für Dinge wie Geldautomaten, Verkaufsautomaten, Müllcontainer, Mülleimer oder Props, die an mehreren Stellen der Karte vorkommen. Statt Targets an Hunderten Koordinaten einzeln zu registrieren, registrierst du sie einmal anhand des Modellnamens und jedes passende Objekt wird interaktiv. Die Auswirkungen auf die Performance sind minimal, da das Target-System nur Modelle innerhalb der Raycast-Reichweite prüft und nicht jedes Objekt in der gesamten Welt. Sei bei häufig vorkommenden Modellen aber vorsichtig. Wenn du jedes prop_bin_01a auf der Karte als Target festlegst, erhalten Spieler Hunderte Interaktionspunkte. Stelle also sicher, dass die Aktion bei allen Instanzen dieses Modells sinnvoll ist.

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

Zonenbasierte Targets: Unsichtbare Interaktionsbereiche

Zonen-Targets erstellen unsichtbare Interaktionsbereiche an bestimmten Koordinaten. Sie eignen sich ideal für Orte, an denen es kein physisches Objekt gibt, mit dem Spieler aber trotzdem interagieren sollen, zum Beispiel eine Theke in einem Gebäudeinneren, eine bestimmte Stelle am Boden für ein Versteck oder ein Bereich vor einer Tür für eine Dietrich-Option. Du definierst Zonen als Boxen oder Kugeln mit Koordinaten, Größe und Rotation. Die Zone ist nur aktiv, wenn das Fadenkreuz auf ihren Bereich zeigt. Dadurch sind Interaktionen präzise und werden nicht versehentlich ausgelöst. Zonen-Targets sind außerdem die erste Wahl, um Interaktionen zu Teilen von MLOs hinzuzufügen, bei denen Props fest in die Map eingebaut sind und nicht über das Modell anvisiert werden können.

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

Bedingte Sichtbarkeit mit canInteract

Die canInteract Die Funktion macht Target-Systeme erst richtig leistungsstark. Sie wird jedes Mal ausgeführt, wenn das Fadenkreuz über einem Target schwebt, und bestimmt, ob die jeweilige Option angezeigt oder ausgeblendet werden soll. So kannst du kontextabhängige Interaktionen erstellen, bei denen ein Polizist andere Optionen als ein Zivilist sieht, eine Dietrich-Option nur erscheint, wenn der Spieler einen Dietrich besitzt, oder eine Fahrzeugreparatur-Option nur bei einem beschädigten Fahrzeug angezeigt wird. Die Funktion erhält den Entity-Handle, die Entfernung, die Koordinaten und den Optionsnamen als Parameter. Halte die Logik darin canInteract schlank, da sie in jedem Frame ausgeführt wird, während der Spieler über dem Target schwebt. Vermeide Datenbankabfragen oder aufwendige Berechnungen innerhalb der Funktion. Speichere die benötigten Daten stattdessen in einer lokalen Variable, die über Events aktualisiert wird.

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

Globale Targets: Interaktionen mit Spielern und Fahrzeugen

Globale Targets gelten für jeden Spieler-Ped oder jedes Fahrzeug in der Welt, ohne dass du jedes einzeln registrieren musst. addGlobalPlayer fügt Optionen hinzu, die beim Anvisieren eines beliebigen anderen Spielers angezeigt werden. Das eignet sich perfekt für Interaktionen wie Items übergeben, Ausweise kontrollieren, Verdächtige fesseln oder andere Spieler heilen. addGlobalVehicle fügt Optionen für jedes Fahrzeug hinzu, etwa für Mechaniker, Polizeikontrollen oder Tank-Systeme. Verwende canInteract großzügig mit globalen Targets, damit die Optionen nicht überhandnehmen. Ein Zivilist sollte keine Fessel-Option sehen, und ein Mechaniker sollte Reparaturoptionen nur im Dienst sehen. Globale Targets sind die bequemste Möglichkeit, universelle Interaktionen hinzuzufügen, aber auch am einfachsten falsch zu verwenden. Überlege also genau, wann die jeweilige Option tatsächlich sichtbar sein sollte.

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

Performance-Tipps und häufige Fehler

Target-Systeme sind gut optimiert, können bei falscher Verwendung aber trotzdem die Performance beeinträchtigen. Der häufigste Fehler ist, in der Produktion zu viele Zonen-Targets mit aktiviertem Debug-Modus zu registrieren. Die Debug-Ansicht zeichnet für jede Zone Drahtgitterformen, was bei Dutzenden Zonen viel Leistung kostet. Setze immer debug = false bevor du dein Resource bereitstellst. Ein weiterer Fehler ist, aufwendige Logik in canInteract Funktionen zu packen. Da diese in jedem Frame ausgeführt wird, während der Spieler über einem Target schwebt, kann selbst ein einzelner exports.ox_inventory:Search Aufruf ins Gewicht fallen, wenn zehn Optionen gleichzeitig den Inventarstatus prüfen. Speichere diese Werte stattdessen zwischen und aktualisiere sie, wenn sich das Inventar ändert. Räume außerdem immer Targets auf, wenn deine Resource stoppt. Verwende den onResourceStop Event-Handler, um alle Zonen-, Modell- und Entity-Targets zu entfernen. Nicht bereinigte Targets bleiben im Speicher und können Fehler oder doppelte Optionen verursachen, wenn die Resource neu gestartet wird. ox_target übernimmt für lokale Entities einen Teil der Bereinigung automatisch, aber eine explizite Bereinigung ist immer die sicherste Lösung.

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.