返回博客
Tutorial3 分钟阅读

ox_inventory指南:2026年首选的FiveM库存系统

掌握 ox_inventory 在你的 FiveM 服务器上的使用。安装、配置物品、挂钩导出并集成 QBCore、ESX 或 standalone 框架。

Agency Scripts

Agency Scripts 创始人兼首席开发者

什么是 ox_inventory?

ox_inventory是一个现代高性能的FiveM库存系统,已成为严肃角色扮演服务器的标准选择。由Overextended团队打造,它取代了旧有库存解决方案,提供更快、更灵活且更易扩展的系统。它具有基于React的时尚UI、内置武器管理、制作支持、商店、储藏和强大的API,脚本开发者可用来创建自定义物品交互。无论您是构建新服务器还是从其他库存迁移,深入理解ox_inventory都会让您成为更优秀的FiveM开发者。

安装与配置

ox_inventory需要 ox_lib 作为依赖项,兼容ESX和QBCore框架以及独立设置。安装包括下载资源,将SQL架构导入数据库,并配置框架桥接。配置文件控制从库存重量限制到物品腐烂计时器的一切。

-- Configuration snippet from ox_inventory
return {
    playerslots = 50,
    playerweight = 30000,
    playerweaponslots = 5,

    decay = true,
    decayInterval = 60,

    trunkslots = 50,
    trunkweight = 60000,
    gloveboxslots = 5,
    gloveboxweight = 5000,
}

注册自定义物品

ox_inventory 中的物品定义在 data/items.lua 文件。每个物品需要唯一名称、标签、重量,且可选堆叠大小、衰减率、描述以及客户端或服务器端事件处理器。理解完整物品定义架构可让你创建完全符合脚本需求的物品。

-- data/items.lua
return {
    ['water_bottle'] = {
        label = 'Water Bottle',
        weight = 500,
        stack = true,
        close = true,
        description = 'A refreshing bottle of water',
        client = {
            image = 'water_bottle.png',
        }
    },

    ['lockpick'] = {
        label = 'Lockpick',
        weight = 200,
        stack = true,
        close = true,
        degrade = 300,
        description = 'Used to pick locks. Breaks after use.',
    },

    ['driver_license'] = {
        label = 'Driver License',
        weight = 0,
        stack = false,
        consume = 0,
        description = 'Issued by the DMV',
        client = {
            image = 'driver_license.png',
        }
    },
}

使用 ox_inventory 服务器 API

服务器端导出是ox_inventory中最常用的部分,允许您添加物品、移除物品、检查库存内容和操作元数据。始终使用服务器端API进行物品操作,以防止作弊者利用漏洞。

-- Server-side: Adding items to a player
RegisterNetEvent('myresource:giveReward', function(itemName, count)
    local src = source
    local success = exports.ox_inventory:AddItem(src, itemName, count)

    if success then
        TriggerClientEvent('ox_lib:notify', src, {
            title = 'Item Received',
            description = count .. 'x ' .. itemName,
            type = 'success'
        })
    else
        TriggerClientEvent('ox_lib:notify', src, {
            title = 'Inventory Full',
            description = 'Could not add item to inventory',
            type = 'error'
        })
    end
end)

-- Server-side: Check if player has required items
local function HasRequiredItems(src, requirements)
    for _, req in ipairs(requirements) do
        local count = exports.ox_inventory:GetItemCount(src, req.name)
        if count < req.amount then
            return false, req.name, req.amount - count
        end
    end
    return true
end

-- Server-side: Remove multiple items in a transaction
local function ConsumeRecipeItems(src, recipe)
    for _, ingredient in ipairs(recipe.ingredients) do
        local removed = exports.ox_inventory:RemoveItem(
            src, ingredient.name, ingredient.amount
        )
        if not removed then
            print(('[ERROR] Failed to remove %s from player %d'):format(
                ingredient.name, src
            ))
            return false
        end
    end
    return true
end

使用物品元数据

物品元数据是 ox_inventory 最强大的功能之一。它允许您向单个物品实例附加任意数据,实现武器的唯一序列号、食物的过期日期、身份证上的玩家姓名以及任何物品的自定义属性。

-- Server: Create a driver license with metadata
RegisterNetEvent('myresource:issueLicense', function(targetId, licenseType)
    local src = source

    local metadata = {
        type = licenseType,
        issued = os.date('%Y-%m-%d'),
        issuedBy = GetPlayerName(src),
        holder = GetPlayerName(targetId),
        expires = os.date('%Y-%m-%d', os.time() + 30 * 86400),
    }

    exports.ox_inventory:AddItem(targetId, 'driver_license', 1, metadata)
end)

-- Client: Reading metadata when using an item
exports('driver_license', function(data, slot)
    local item = exports.ox_inventory:GetSlot(slot)
    if not item or not item.metadata then return end

    local meta = item.metadata
    lib.notify({
        title = 'Driver License',
        description = ('Holder: %s\nType: %s\nExpires: %s'):format(
            meta.holder or 'Unknown',
            meta.type or 'Standard',
            meta.expires or 'N/A'
        ),
        type = 'inform'
    })
end)

创建自定义商店

ox_inventory包含内置商店系统,您可以配置自定义物品目录、价格、职业限制和位置。商店定义于 data/shops.lua 并且可以附加到ped商贩、地图标记位置,或通过程序触发。

-- data/shops.lua
return {
    ['hardware_store'] = {
        name = 'Hardware Store',
        inventory = {
            { name = 'lockpick',    price = 150,  count = 10 },
            { name = 'screwdriver', price = 80,   count = 20 },
            { name = 'radio',       price = 250,  count = 5  },
            { name = 'repairkit',   price = 1000, count = 5  },
        },
        locations = {
            vec3(45.6, -1749.3, 29.6),
            vec3(2747.8, 3472.9, 55.7),
        },
        targets = {
            ped = 's_m_y_hardware_01',
            scenario = 'WORLD_HUMAN_STAND_IMPATIENT',
            distance = 2.5,
        },
    },
}

储物系统与自定义容器

储物箱是玩家可在特定位置访问的持久存储容器。ox_inventory 支持绑定玩家标识的个人储物箱、任何人可访问的共享储物箱,以及限制职业使用的储物箱,如警察证据室和医院存储。

-- Server: Register a custom stash for a property
local function CreatePropertyStash(propertyId, owner)
    local stashId = ('property_%s'):format(propertyId)

    exports.ox_inventory:RegisterStash(
        stashId,
        ('Property #%s Storage'):format(propertyId),
        100,     -- slots
        200000,  -- max weight
        owner    -- owner identifier (or false for shared)
    )

    return stashId
end

-- Client: Open a stash when interacting with a storage point
RegisterNetEvent('myresource:openPropertyStorage', function(propertyId)
    local stashId = ('property_%s'):format(propertyId)
    exports.ox_inventory:openInventory('stash', stashId)
end)

ox_inventory集成的最佳实践

  • 始终在服务器端验证。 切勿信任客户端的库存检查。使用服务器导出如 GetItemCountRemoveItem 适用于所有关键游戏逻辑。
  • 谨慎使用元数据。 虽然元数据功能强大,但在每个物品上存储过多数据会增加数据库大小和网络流量。只存储你实际需要的数据。
  • 优雅处理满库存。 始终检查返回值 AddItem 并在玩家背包满时提供清晰反馈。
  • 使用物品降解增加真实感。 降解属性创造自然物品消耗,保持经济平衡。为易腐商品和工具设置适当的衰减时间。
  • 利用内置制作系统。 无需自己构建制作逻辑,使用 ox_inventory 的制作工作台,它能自动处理检查、移除和创建。
  • 保持物品图片一致。 将所有物品图片放置于 web/images/ 带有匹配的文件名。使用100x100的PNG文件且背景透明,以获得最干净的外观。

准备好开始了吗?

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