Ejemplo de ox_target addSpherezone: código Lua para QBCore y ESX
Ejemplo de ox_target addSpherezone con código Lua listo para copiar y pegar. Incluye addBoxZone, addLocalEntity y qb-target addGlobalPlayer. Para QBCore y ESX.
Agency Scripts
Fundador y desarrollador principal en Agency Scripts
¿Qué es un sistema de interacción por objetivos y por qué usarlo?
Un sistema de interacción por objetivos, a veces llamado sistema de interacción ocular o de selección, sustituye el método tradicional de pulsar una tecla cuando estás cerca de algo. En vez de acercarse a un NPC y pulsar E con la esperanza de estar lo bastante cerca, los jugadores mantienen pulsada una tecla, normalmente alt izquierdo, para activar el modo de selección, que muestra una pequeña mira en el centro de la pantalla. Cuando la mira pasa sobre una entidad, objeto o zona seleccionable, aparece un menú con las acciones disponibles. Este método es mucho más intuitivo porque permite a los jugadores elegir con precisión con qué interactuar. En una escena concurrida con varios NPC, vehículos y objetos, un sistema de interacción por objetivos permite seleccionar exactamente lo que quieres sin tener que lidiar con indicaciones de interacción superpuestas. Las dos implementaciones más populares son ox_target (del equipo Overextended) y qb-target (desarrollado para QBCore). Esta guía cubre ambos, con énfasis en ox_target porque es independiente del framework y tiene un desarrollo más activo.
ox_target vs qb-target: cuál elegir
Ambos sistemas cumplen el mismo objetivo, pero difieren en el diseño de la API y el rendimiento. ox_target es standalone, funciona con cualquier framework, usa raycasting para detectar entidades y tiene una API más clara, con mejor compatibilidad con TypeScript. qb-target está estrechamente integrado con QBCore, usa un método similar basado en raycasting y lleva más tiempo disponible, por lo que más scripts existentes lo admiten directamente. Si estás creando un servidor nuevo o usas ESX/standalone, usa ox_target. Si usas QBCore y la mayoría de los scripts que compraste utilizan exports de qb-target, seguir con qb-target evitará problemas de compatibilidad. La buena noticia es que ox_target incluye una capa de compatibilidad con qb-target, así que los scripts que llaman a exports['qb-target'] funcionarán con ox_target instalado. Sin embargo, lo contrario no es cierto, por lo que los scripts escritos para ox_target no funcionarán con qb-target sin modificaciones.
Añadir objetivos a entidades específicas
Los objetivos de entidad asocian opciones de interacción a una entidad concreta que se haya generado, como un ped, vehículo u objeto. Esto resulta útil cuando creas un NPC para un trabajo y quieres que los jugadores interactúen específicamente con ese NPC, en lugar de con cualquier modelo de ped del mundo. El objetivo sigue a la entidad, así que, si el NPC se mueve, el punto de interacción se desplaza con él. Debes proporcionar el identificador de la entidad, el array de opciones y una distancia opcional que controle hasta dónde puede detectar la entidad la mira. Limpia siempre los objetivos de entidad cuando se elimine la entidad o se detenga el recurso, para evitar que queden objetivos huérfanos en la memoria.
-- 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)
Objetivos basados en modelos: seleccionar todas las instancias
Los objetivos por modelo aplican opciones de interacción a cada instancia de un modelo específico en el mundo del juego. Esto es muy útil para elementos como cajeros automáticos, máquinas expendedoras, contenedores, cubos de basura o cualquier objeto que aparezca en varias ubicaciones del mapa. En lugar de registrar objetivos manualmente en cientos de coordenadas, los registras una sola vez por nombre de modelo y cada objeto coincidente se vuelve interactivo. El impacto en el rendimiento es mínimo porque el sistema de objetivos solo comprueba los modelos dentro del alcance del raycast, no todos los objetos del mundo. Ten cuidado con los modelos comunes. Por ejemplo, apuntar a cada prop_bin_01a del mapa ofrece a los jugadores cientos de puntos de interacción, así que asegúrate de que la acción tenga sentido para todas las instancias de ese modelo.
-- 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,
})
Objetivos por zona: áreas de interacción invisibles
Los objetivos por zona crean áreas de interacción invisibles en coordenadas específicas. Son ideales para lugares donde no hay ningún objeto físico, pero quieres que los jugadores puedan interactuar, como un mostrador dentro de un edificio, un punto concreto del suelo para un alijo o un área delante de una puerta para forzar la cerradura. Defines las zonas como cajas o esferas con coordenadas, tamaño y rotación. La zona solo está activa cuando la mira apunta a su área, lo que permite una interacción precisa y evita acciones accidentales. Los objetivos por zona también son la solución habitual para añadir interacciones a partes de los MLO del mapa donde los objetos están integrados en el mapa y no se pueden seleccionar por modelo.
-- 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)
Visibilidad condicional con canInteract
El canInteract La función es lo que hace que los sistemas de objetivos sean realmente potentes. Se ejecuta cada vez que la mira pasa sobre un objetivo y determina si se muestra u oculta cada opción. Así puedes crear interacciones contextuales en las que un policía ve opciones distintas a las de un civil, una opción para forzar la cerradura solo aparece si el jugador tiene una ganzúa o una opción para reparar un vehículo solo se muestra cuando el vehículo está dañado. La función recibe como parámetros el identificador de la entidad, la distancia, las coordenadas y el nombre de la opción. Mantén la lógica de canInteract ligera, porque se ejecuta en cada fotograma mientras el jugador apunta al objetivo. Evita hacer consultas a la base de datos o cálculos complejos dentro de ella. En su lugar, almacena los datos necesarios en una variable local y actualízala mediante eventos.
-- 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,
},
})
Objetivos globales: interacciones con jugadores y vehículos
Los objetivos globales se aplican a todos los ped de jugador o a todos los vehículos del mundo, sin tener que registrar cada uno individualmente. addGlobalPlayer añade opciones que aparecen al apuntar a cualquier otro jugador, algo ideal para interacciones como dar objetos, comprobar identificaciones, esposar sospechosos o curar a otros jugadores. addGlobalVehicle añade opciones para todos los vehículos, útiles para mecánicos, registros policiales o sistemas de repostaje. Usa canInteract con libertad con los objetivos globales para evitar sobrecargar las opciones. Un civil no debería ver una opción para poner esposas y un mecánico solo debería ver opciones de reparación cuando esté de servicio. Los objetivos globales son la forma más cómoda de añadir interacciones universales, pero también son los más fáciles de usar mal, así que piensa bien cuándo debería mostrarse cada opción.
-- 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,
},
})
Consejos de rendimiento y errores frecuentes
Los sistemas de objetivos están bien optimizados, pero su rendimiento puede verse afectado si se usan mal. El error más común es registrar demasiados objetivos por zona con el modo de depuración activado en producción. La visualización de depuración dibuja formas de alambre para cada zona, lo cual consume muchos recursos cuando hay decenas. Configura siempre debug = false antes de publicar. Otro error es incluir lógica costosa en las funciones canInteract . Como se ejecuta en cada fotograma mientras el jugador apunta al objetivo, incluso una sola llamada a exports.ox_inventory:Search puede acumularse si tienes diez opciones que comprueban el inventario al mismo tiempo. Guarda estos valores en caché y actualízalos cuando cambie el inventario. Por último, limpia siempre los objetivos cuando se detenga tu recurso. Usa el onResourceStop controlador de eventos para eliminar todos los objetivos de zona, modelo y entidad. Los objetivos que no se eliminan permanecen en la memoria y pueden provocar errores u opciones duplicadas si se reinicia el recurso. ox_target gestiona automáticamente parte de la limpieza de las entidades locales, pero la limpieza explícita siempre es la opción más segura.