Wróć do bloga
Tutorial8 min czytania

Przykład ox_target addSpherezone: kod Lua dla QBCore & ESX

Przykład ox_target addSpherezone: gotowy do użycia kod Lua do skopiowania. Obejmuje także addBoxZone, addLocalEntity & qb-target addGlobalPlayer. QBCore & ESX.

Agency Scripts

Założyciel i główny deweloper w Agency Scripts

Czym jest Target System i dlaczego warto go używać?

System target, nazywany czasem eye-target lub systemem interakcji, zastępuje tradycyjne podejście polegające na naciskaniu klawisza w pobliżu obiektu. Zamiast podchodzić do NPC i naciskać E licząc na to, że jesteś wystarczająco blisko, gracze przytrzymują klawisz (zwykle lewy alt), aby przejść w tryb targetowania, który wyświetla mały celownik na środku ekranu. Gdy celownik najeżdża na encję, obiekt lub strefę objętą interakcją, pojawia się menu dostępnych akcji. To podejście jest zdecydowanie bardziej intuicyjne, ponieważ daje graczom precyzyjną kontrolę nad tym, z czym wchodzą w interakcję. W zatłoczonym otoczeniu z wieloma NPC, pojazdami i obiektami system targetowania pozwala wybrać dokładnie ten pożądany element, bez problemu nakładających się komunikatów interakcji. Dwie najpopularniejsze implementacje to ox_target (od zespołu Overextended) oraz qb-target (stworzony dla QBCore). Ten poradnik obejmuje oba rozwiązania, ze szczególnym naciskiem na ox_target, ponieważ jest on niezależny od frameworka i aktywniej rozwijany.

ox_target vs qb-target: co wybrać

Oba systemy realizują ten sam cel, ale różnią się strukturą API i wydajnością. ox_target jest standalone, działa z każdym frameworkiem, wykorzystuje raycasting do wykrywania bytów i oferuje czystsze API z lepszym wsparciem TypeScript. qb-target jest ściśle zintegrowany z QBCore, korzysta z podobnego podejścia raycast i istnieje dłużej, więc więcej gotowych skryptów obsługuje go od ręki. Jeśli stawiasz nowy serwer lub używasz ESX/standalone, wybierz ox_target. Jeśli korzystasz z QBCore, a większość twoich kupionych skryptów używa eksportów qb-target, pozostanie przy qb-target pozwoli uniknąć problemów z kompatybilnością. Dobra wiadomość jest taka, że ox_target zawiera warstwę kompatybilności z qb-target, więc skrypty wywołujące exports['qb-target'] będą działać z zainstalowanym ox_target. Odwrotna sytuacja jednak nie ma miejsca, więc skrypty napisane dla ox_target nie zadziałają z qb-target bez modyfikacji.

Dodawanie targetów do określonych bytów

Entity targets przypisują opcje interakcji do konkretnej zrespawnowanej encji, takiej jak ped, pojazd lub obiekt. Jest to przydatne, gdy tworzysz NPC do pracy i chcesz, aby gracze wchodzili w interakcję dokładnie z tym NPC, a nie z dowolnym modelem peda w świecie gry. Cel podąża za encją, więc jeśli NPC się porusza, punkt interakcji przemieszcza się wraz z nim. Podajesz handle encji, tablicę opcji oraz opcjonalny dystans określający, z jakiej odległości celownik może wykryć encję. Zawsze usuwaj cele encji po usunięciu encji lub zatrzymaniu zasobu, aby zapobiec pozostawaniu osieroconych celów w pamięci.

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

Cele oparte na modelach: targetowanie wszystkich instancji

Cele oparte na modelach stosują opcje interakcji do każdej instancji określonego modelu w świecie gry. Jest to niezwykle przydatne w przypadku takich obiektów jak bankomaty, automaty z przekąskami, kontenery, kosze na śmieci czy dowolny prop istniejący w wielu miejscach na mapie. Zamiast ręcznie rejestrować cele na setkach współrzędnych, rejestrujesz raz według nazwy modelu i każdy pasujący obiekt staje się interaktywny. Wpływ na wydajność jest minimalny, ponieważ system target sprawdza tylko modele w zasięgu raycasta, a nie każdy obiekt na całym świecie. Uważaj jednak na powszechne modele. Celowanie w każdy prop_bin_01a na mapie daje graczom setki punktów interakcji, więc upewnij się, że akcja ma sens dla wszystkich instancji tego modelu.

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

Cele strefowe: niewidoczne obszary interakcji

Cele strefowe tworzą niewidoczne obszary interakcji pod określonymi współrzędnymi. Są one idealne dla miejsc, w których nie ma żadnego fizycznego obiektu, ale nadal chcesz, aby gracze wchodzili w interakcję, jak lada we wnętrzu budynku, określone miejsce na ziemi na schowek lub obszar przed drzwiami do użycia wytrycha. Strefy definiujesz jako prostopadłościany lub sfery o określonych współrzędnych, rozmiarze i obrocie. Strefa jest aktywna tylko wtedy, gdy celownik wskazuje jej obszar, co zapewnia precyzję i zapobiega przypadkowym interakcjom. Zone targets to także podstawowe rozwiązanie do dodawania interakcji w częściach MLO mapy, gdzie propy są wbudowane w mapę i nie można na nie celować według modelu.

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

Warunkowa widoczność z canInteract

Ten canInteract funkcja to coś, co czyni systemy targetowania naprawdę potężnymi. Uruchamia się za każdym razem, gdy celownik najeżdża na cel, i określa, czy dana opcja powinna być widoczna, czy ukryta. Pozwala to na tworzenie interakcji kontekstowych, w których policjant widzi inne opcje niż cywil, opcja wytrychu pojawia się tylko wtedy, gdy gracz ma wytrych, a opcja naprawy pojazdu wyświetla się tylko wtedy, gdy pojazd jest uszkodzony. Funkcja otrzymuje jako parametry handle encji, dystans, koordynaty oraz nazwę opcji. Trzymaj logikę w canInteract lekki, ponieważ wykonuje się w każdej klatce, gdy gracz najeżdża na cel. Unikaj w nim zapytań do bazy danych i ciężkich obliczeń. Zamiast tego buforuj wymagane dane w zmiennej lokalnej, która jest aktualizowana przez zdarzenia.

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

Global Targets: interakcje z graczami i pojazdami

Global targets dotyczą każdego peda gracza lub każdego pojazdu w świecie gry bez konieczności rejestrowania każdego z nich osobno. addGlobalPlayer dodaje opcje widoczne przy namierzeniu innego gracza, co idealnie sprawdza się przy interakcjach takich jak przekazywanie przedmiotów, sprawdzanie dokumentów, skuwanie podejrzanych czy leczenie innych graczy. addGlobalVehicle dodaje opcje dla każdego pojazdu, przydatne dla mechaników, przeszukiwań policyjnych lub systemów tankowania. Użyj canInteract swobodnie z celami globalnymi, aby zapobiec przeciążeniu opcjami. Cywil nie powinien widzieć opcji skuwania, a mechanik powinien widzieć opcje naprawy tylko na służbie. Cele globalne to najwygodniejszy sposób na dodanie uniwersalnych interakcji, ale też najłatwiejszy do nadużycia, więc przemyśl dokładnie, kiedy każda opcja powinna być faktycznie widoczna.

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

Wskazówki dotyczące wydajności i częste błędy

Systemy targetowania są dobrze zoptymalizowane, ale przy niewłaściwym użyciu mogą wpłynąć na wydajność. Najczęstszym błędem jest rejestrowanie zbyt wielu stref targetu z włączonym trybem debugowania na serwerze produkcyjnym. Renderowanie debugowania rysuje siatki dla każdej strefy, co jest kosztowne, gdy masz ich dziesiątki. Zawsze ustawiaj debug = false przed wdrożeniem. Innym błędem jest umieszczanie kosztownej logiki wewnątrz canInteract funkcje. Ponieważ działa to w każdej klatce podczas najechania kursorem, nawet pojedyncze exports.ox_inventory:Search wywołanie może się kumulować, jeśli masz dziesięć opcji jednocześnie sprawdzających ekwipunek. Zamiast tego zapisuj te wartości w pamięci podręcznej i aktualizuj je przy zmianie ekwipunku. Na koniec zawsze czyść targety po zatrzymaniu resource. Użyj onResourceStop event handler usuwający wszystkie targety stref, modeli i entity. Wyciekające targety pozostają w pamięci i mogą powodować błędy lub zduplikowane opcje po zrestartowaniu resource. ox_target obsługuje część czyszczenia automatycznie dla lokalnych entities, ale jawne czyszczenie jest zawsze najbezpieczniejszym podejściem.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.