ox_target addSpherezone 示例 - QBCore 和 ESX Lua 代码
ox_target addSpherezone 示例:可复制粘贴的 Lua 代码,准备使用。也涵盖 addBoxZone、addLocalEntity 和 qb-target addGlobalPlayer。支持 QBCore 和 ESX。
Agency Scripts
Agency Scripts 创始人兼首席开发者
什么是目标系统,为什么要使用?
目标系统,有时称为眼睛目标或交互系统,取代了传统的靠近物体按键方式。玩家不再走近NPC然后按键,而是 E 希望你足够接近,玩家按住一个键(通常是左Alt)进入瞄准模式,屏幕中央会显示一个小准星。当准星悬停在可目标实体、物体或区域上时,会出现可用操作菜单。这种方式更直观,因为它让玩家精确控制他们互动的对象。在有多个NPC、车辆和物体的拥挤场景中,目标系统让你准确选择想要的对象,而无需与重叠的交互提示争斗。最流行的两种实现是 ox_target (来自 Overextended 团队)和 qb-target (为 QBCore 构建)。本指南涵盖两者,重点是 ox_target,因为它是框架无关且更活跃开发的。
ox_target 与 qb-target:选择哪一个
两个系统实现相同目标,但在 API 设计和性能上有所不同。 ox_target 是独立的,适用于任何框架,使用射线投射进行实体检测,API更简洁,支持更好的TypeScript。 qb-target 与QBCore紧密集成,使用类似的射线投射方法,存在时间更长,因此更多现有脚本开箱即用支持它。如果您启动新服务器或使用ESX/独立,使用ox_target。如果您使用QBCore且大多数购买的脚本使用qb-target导出,继续使用qb-target可避免兼容性问题。好消息是ox_target包含qb-target兼容层,因此调用 exports['qb-target'] 将与安装了ox_target的环境兼容。但反之不成立,因此为ox_target编写的脚本未经修改无法在qb-target上运行。
为特定实体添加目标
实体目标为特定生成实体(如行人、车辆或物体)附加交互选项。当您创建一个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)
基于模型的目标:定位所有实例
模型目标对游戏世界中特定模型的每个实例应用交互选项。这对ATM、自动售货机、垃圾箱、垃圾桶或地图上多个位置存在的任何道具非常强大。无需在数百个坐标手动注册目标,只需按模型名称注册一次,所有匹配对象即可交互。性能影响极小,因为目标系统只检查射线范围内的模型,而非整个世界的所有对象。但要注意常见模型。目标所有 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 是使目标系统真正强大的关键。每当准星悬停在目标上时,它都会运行,并决定每个选项是否应显示或隐藏。这让你可以创建上下文敏感的交互,比如警察看到的选项与平民不同,只有玩家拥有开锁工具时才显示开锁选项,或者只有车辆受损时才显示修理选项。该函数接收实体句柄、距离、坐标和选项名称作为参数。请将逻辑保留在 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对本地实体进行部分自动清理,但显式清理始终是最安全的方法。