返回博客
Tutorial5 分钟阅读

ox_lib指南:FiveM开发者必备库

从零开始学习ox_lib。通知、上下文菜单、回调、区域和导出,现代FiveM脚本依赖的功能,附真实代码示例。

Agency Scripts

Agency Scripts 创始人兼首席开发者

什么是 ox_lib,为什么要使用?

ox_lib 是一个开源的 FiveM 实用库,已成为现代脚本开发的事实标准。它提供大量预构建的 UI 组件、实用函数和性能工具,免去每次编写脚本时重复造轮子的麻烦。在 ox_lib 出现之前,开发者必须从零构建通知系统、输入对话框、进度条和上下文菜单,导致同一服务器上不同脚本的 UI 不一致。ox_lib 通过提供统一且精致的组件集解决了这一问题,这些组件外观专业且开箱即用,支持 Lua 和 JavaScript,兼容任何框架(QBCore、ESX 或 standalone),由 Overextended 团队积极维护。如果你在 2026 年编写 FiveM 脚本却不使用 ox_lib,就会浪费时间重复构建已有功能。

在您的资源中设置 ox_lib

将 ox_lib 集成到您的资源只需两步:将依赖项添加到您的 fxmanifest.lua 并在您的脚本中调用库。 @ox_lib/init.lua import让您访问所有共享实用程序,而模块系统允许您选择性加载仅需的功能。这保持了资源的轻量,因为未使用的模块永远不会被加载。确保在server.cfg中将ox_lib放在您的资源之前启动,方法是放置 ensure ox_lib 在您的自定义资源之上。以下是使用ox_lib的新资源的最小设置:

-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

name 'my-awesome-script'
version '1.0.0'

-- Required: import ox_lib
shared_scripts {
    '@ox_lib/init.lua',
    'config.lua',
}

client_scripts {
    'client/*.lua',
}

server_scripts {
    'server/*.lua',
}

-- Declare ox_lib as a dependency
dependencies {
    'ox_lib',
}

-- Enable ox_lib locale system (optional)
lua54 'yes'

通知:干净、一致的警报

ox_lib 通知替代了大多数脚本使用的丑陋默认聊天消息和自定义 NUI 弹窗。它们以带图标、颜色和自动消失的精致吐司消息形式出现。你可以设置位置、持续时间、类型(成功、错误、警告、信息),甚至在标题下添加描述。通知系统仅客户端运行,极其轻量,几乎不增加脚本负担。通知是 ox_lib 最常用的功能,应作为你构建任何脚本时与玩家沟通反馈的默认方式。

-- client.lua: Notification examples

-- Simple notification
lib.notify({
    title = 'Vehicle Stored',
    description = 'Your vehicle has been stored in the garage.',
    type = 'success',         -- 'success' | 'error' | 'warning' | 'info'
    duration = 5000,          -- milliseconds
    position = 'top-right',   -- 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left'
})

-- Error notification with icon
lib.notify({
    title = 'Access Denied',
    description = 'You do not have the required key.',
    type = 'error',
    icon = 'lock',
    iconColor = '#ff4444',
})

-- Notification from server side
-- server.lua
RegisterNetEvent('garage:store', function()
    local src = source
    TriggerClientEvent('ox_lib:notify', src, {
        title = 'Garage',
        description = 'Vehicle stored successfully.',
        type = 'success',
    })
end)

上下文菜单:交互式选项列表

上下文菜单是可滚动的选项列表,玩家可以点击触发操作。它们非常适合工作菜单、商店界面、车辆选项以及玩家需要从多个操作中选择的任何场景。每个菜单项可以有图标、描述、右侧显示的元数据和用于组织复杂选项树的嵌套子菜单。菜单保持打开状态,直到玩家明确关闭或选择非子菜单选项,非常适合浏览物品类别。上下文菜单还可以基于服务器数据动态生成,因此您可以构建反映数据库实时库存的商店菜单。

-- client.lua: Context menu examples

-- Simple shop menu
lib.registerContext({
    id = 'weapons_shop',
    title = 'Ammu-Nation',
    options = {
        {
            title = 'Pistol',
            description = 'Standard 9mm handgun',
            icon = 'gun',
            metadata = {
                {label = 'Price', value = '$2,500'},
                {label = 'Ammo', value = '12 rounds'},
            },
            onSelect = function()
                TriggerServerEvent('shop:buy', 'weapon_pistol')
            end,
        },
        {
            title = 'Body Armor',
            description = 'Standard kevlar vest',
            icon = 'shield',
            metadata = {
                {label = 'Price', value = '$5,000'},
                {label = 'Protection', value = '50%'},
            },
            onSelect = function()
                TriggerServerEvent('shop:buy', 'armor')
            end,
        },
        {
            title = 'Attachments',
            description = 'Browse weapon modifications',
            icon = 'wrench',
            arrow = true,  -- Shows arrow indicating submenu
            menu = 'attachments_submenu',
        },
    },
})

lib.showContext('weapons_shop')

进度条和进度圆环

进度条在锁定、制作、修理车辆或烹饪食物等计时动作中提供视觉反馈。ox_lib 提供线性条和圆形指示器。在进度动画期间,可以禁用玩家控制如移动、战斗和车辆进入以防止利用。还可以附加动画和道具到玩家,使其在进度条填充时可见地执行动作。该函数返回 true 如果玩家取消了操作(例如通过移动)且 false 如果操作成功完成。始终检查此返回值以避免在玩家中断时授予物品或完成动作。

-- client.lua: Progress bar examples

-- Linear progress bar with animation
local cancelled = lib.progressBar({
    duration = 8000,
    label = 'Lockpicking door...',
    useWhileDead = false,
    canCancel = true,
    disable = {
        car = true,
        move = true,
        combat = true,
    },
    anim = {
        dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
        clip = 'machinic_loop_mechandler',
    },
    prop = {
        model = 'prop_lockpick_01',
        bone = 57005,
        pos = vec3(0.14, 0.0, -0.01),
        rot = vec3(0.0, 0.0, 0.0),
    },
})

if cancelled then
    lib.notify({ title = 'Cancelled', type = 'error' })
else
    lib.notify({ title = 'Door Unlocked', type = 'success' })
    TriggerServerEvent('lockpick:success', doorId)
end

-- Circular progress (useful for quick actions)
if lib.progressCircle({
    duration = 2000,
    label = 'Searching...',
    position = 'bottom',
    useWhileDead = false,
    canCancel = true,
    disable = { move = true },
}) then
    lib.notify({ title = 'Search cancelled', type = 'error' })
else
    TriggerServerEvent('search:complete')
end

输入对话:收集玩家数据

输入对话允许您通过简洁的模态界面收集玩家输入的文本、数字、下拉选择、复选框、颜色选择器、日期和滑块值。这对于需要玩家输入的脚本至关重要,比如设置房价、输入车牌、命名帮派或配置职业设置。每个输入字段都有标签、可选描述、必填标志和类型特定选项,如数字的最小/最大值或下拉菜单的预定义选项。该函数返回 nil 如果玩家取消对话,提交时会返回字段顺序的值数组。始终在客户端和服务器端验证返回数据以防止漏洞利用。

-- client.lua: Input dialog examples

-- Vehicle listing form
local input = lib.inputDialog('List Vehicle for Sale', {
    { type = 'input', label = 'Title', description = 'Name for the listing', required = true, max = 50 },
    { type = 'number', label = 'Price ($)', description = 'Asking price', required = true, min = 1000, max = 10000000 },
    { type = 'select', label = 'Condition', options = {
        { value = 'new', label = 'Brand New' },
        { value = 'used', label = 'Used - Good' },
        { value = 'damaged', label = 'Damaged' },
    }},
    { type = 'textarea', label = 'Description', description = 'Describe your vehicle', max = 500 },
    { type = 'checkbox', label = 'I agree to the marketplace terms' },
})

if not input then return end -- Player cancelled

local title, price, condition, description, agreedTerms = table.unpack(input)

if not agreedTerms then
    lib.notify({ title = 'You must agree to the terms', type = 'error' })
    return
end

TriggerServerEvent('marketplace:list', {
    title = title,
    price = price,
    condition = condition,
    description = description,
})

区域:高效的区域检测

ox_lib 区域替代了旧的、低效的每帧检查玩家位置的方法,使用 GetEntityCoords 和距离计算。区域系统使用优化的空间检测算法,仅在可配置间隔检查坐标,并在玩家跨越区域边界时触发进入/退出回调。您可以将区域定义为球体、盒子或多边形,使其足够灵活,适用于从小型交互点到大型社区边界的所有场景。区域支持旋转、开发时的调试绘制,以及传递给回调的任意数据。对于任何需要检测玩家是否处于特定区域的脚本,ox_lib区域是性能最高的解决方案。

-- client.lua: Zone examples

-- Sphere zone for a shop entrance
local shopZone = lib.zones.sphere({
    coords = vec3(25.7, -1347.3, 29.5),
    radius = 3.0,
    debug = true,  -- Set false in production
    onEnter = function(self)
        lib.notify({ title = 'Press [E] to open shop', type = 'info' })
        lib.showTextUI('[E] Open Shop', { position = 'right-center' })
    end,
    onExit = function(self)
        lib.hideTextUI()
    end,
})

-- Box zone with rotation for a parking spot
local parkingZone = lib.zones.box({
    coords = vec3(215.3, -810.0, 30.7),
    size = vec3(6.0, 3.0, 2.0),
    rotation = 70.0,
    debug = true,
    onEnter = function(self)
        lib.showTextUI('[E] Store Vehicle')
    end,
    onExit = function(self)
        lib.hideTextUI()
    end,
})

-- Clean up zones when resource stops
AddEventHandler('onResourceStop', function(resource)
    if resource == GetCurrentResourceName() then
        shopZone:remove()
        parkingZone:remove()
    end
end)

缓存:智能数据访问

lib.cache 模块提供即时访问常用玩家数据,无需每帧调用本地函数。值如 cache.ped, cache.vehicle, cache.seat, cache.weapon,和 cache.playerId 通过ox_lib的事件监听器自动保持最新,而非轮询。这意味着您可以安全地读取 cache.vehicle 可以在代码中的任何位置使用,而无需担心性能。缓存在值变化时也会触发事件,因此您可以注册处理程序来处理 ox_lib:cache:vehicle 响应玩家进入或离开车辆。结合区域和 ox_lib 的其余部分,缓存系统让你编写干净的事件驱动代码,而非浪费 CPU 周期检查很少变化条件的帧轮询循环。

-- client.lua: Cache examples

-- Access cached values (no native calls needed)
local myPed = cache.ped
local myVehicle = cache.vehicle  -- nil if not in a vehicle
local mySeat = cache.seat        -- -1 = driver, 0 = front passenger, etc.
local myWeapon = cache.weapon

-- React to vehicle changes
lib.onCache('vehicle', function(vehicle)
    if vehicle then
        -- Player entered a vehicle
        local plate = GetVehicleNumberPlateText(vehicle)
        lib.notify({
            title = 'Vehicle',
            description = 'Plate: ' .. plate,
            type = 'info',
        })
    else
        -- Player exited a vehicle
        lib.notify({ title = 'On foot', type = 'info' })
    end
end)

-- React to weapon changes
lib.onCache('weapon', function(weapon)
    if weapon then
        print('Player equipped weapon:', weapon)
    end
end)

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。