一个专为 Godot 4 设计的运行时容器管理插件,适用于 RPG 背包、仓库、交易、掉落等一切需要物品存取的场景。核心特性包括 O(1) 的空位与物品位置查询、跨容器移动/交换/拆分/合并、层级化标签访问控制,以及不执行即可预览结果的模拟接口。
- O(1) 高性能查询 — 空位、物品位置均由索引映射缓存驱动,无线性扫描
- 跨容器操作 — 通过
Swapper统一编排移动、交换、拆分、合并,支持跨任意容器 - 模拟计算 —
simulate_move/simulate_swap返回预览数据,不修改实际状态,适合 UI 预览 - 层级化标签系统 — 标签支持树状层级(如
Food.Fruit.Apple),容器可按标签过滤允许存入的物品 - 智能堆叠分配 — 添加物品时自动优先填充已有未满堆叠,再占用新空位
- 容器动态缩放 — 运行时调整容器大小,自动重新分配溢出物品
- 信号驱动的 UI 解耦 — 所有数据变更通过信号广播,UI 只需订阅、响应即可
- 可扩展行为系统 — 继承
ItemBehaviourData实现自定义物品使用效果
addons/ContainerSystem/
├── core/
│ ├── ContainerSystem.gd # Autoload 单例 — 物品模板注册与查询
│ ├── ItemData.gd # 物品静态配置(Resource)
│ ├── Item.gd # 运行时物品实例(RefCounted)
│ ├── ItemContainer.gd # 容器核心 — 存取、校验、索引维护、信号
│ ├── Swapper.gd # 跨容器操作与模拟计算(静态工具类)
│ ├── Tag.gd # 层级化标签
│ ├── TagHierarchy.gd # 标签层级存储
│ ├── TagManager.gd # Autoload 单例 — 标签路径查询与层级遍历
│ ├── ItemDataMap.gd # 物品数据映射表(Resource)
│ ├── ItemBehaviourData.gd # 物品行为基类
│ └── PackageManager.gd # 包管理工具
├── editor/
│ └── TagManagerPanel.gd # 编辑器内的标签管理面板
├── classes/ # 预设简化类
├── templates/ # 模板资源与示例
└── plugin.cfg # 插件配置
| 类 | 继承 | 职责 |
|---|---|---|
ItemData |
Resource | 物品静态模板 — StringName ID、名称、图标、最大堆叠、标签、行为列表 |
Item |
RefCounted | 运行时物品实例 — 当前堆叠数、所在容器引用、位置索引 |
ItemContainer |
Node | 单容器管理 — 存取、标签校验、堆叠规则、O(1) 索引维护、信号广播 |
Swapper |
RefCounted | 跨容器编排 — 移动/交换/拆分/合并/模拟,所有操作的统一入口 |
Tag |
Resource | 层级标签 — 支持父子关系、路径匹配(如 Food.Fruit) |
TagManager |
Node | 全局标签注册表 — O(1) 路径查询、层级遍历 |
ContainerSystem |
Node | 全局物品注册表 — 按 StringName ID / 名称查询 ItemData 模板 |
UI 层(只触发操作 & 响应信号)
│
│ 用户操作(拖拽、点击)
↓
Swapper API(编排数据操作)
│
↓
ItemContainer(执行存取 & 维护索引)
│
│ 信号(item_changed / size_changed / illegal_items_changed)
↓
UI 层(根据信号刷新显示)
核心原则:UI 永远不直接修改数据。所有变更经由 Swapper → ItemContainer,再由信号通知 UI 更新。
ItemContainer 内部维护两个映射结构:
| 结构 | 类型 | 用途 |
|---|---|---|
item_id_pos_map |
Dictionary { StringName → Array[int] } |
根据物品 ID 即时定位所有槽位 |
item_empty_pos_map |
Array[int](有序) |
即时获取第一个空位 |
将 addons/ContainerSystem/ 复制到项目的 addons/ 目录下,然后在 Project → Project Settings → Plugins 中启用 Container System。
插件启用后会自动注册两个 Autoload 单例:
ItemContainerSystem— 物品模板查询TagManager— 标签层级查询
在 Project Settings 中设置:
container_system/item_data_map→ 指向你的ItemDataMap资源container_system/tag_hierarchy→ 指向你的TagHierarchy资源
Tag Manager 首次配置:启用插件后,编辑器左下角的 Tag Manager 面板会提示"尚未配置 Tag 存储文件"。点击"创建 Tag 配置文件"按钮,选择一个项目内的路径(如
res://data/TagHierarchy.tres),即可开始管理标签。该路径会自动写入项目设置,后续打开项目时将自动加载。
var container := ItemContainer.new()
container.initialize(20, "背包", "玩家背包", addable_tags)
add_child(container)# 通过全局单例获取物品模板(使用 StringName ID)
var item_data = ItemContainerSystem.get_item_data_by_id(&"apple")
# 方式一:通过模板添加(自动创建 Item 实例)
container.add_item_by_itemdata(item_data, -1, true, 1)
# 方式二:手动创建 Item 后添加
var item := Item.new(item_data, container, -1, 3)
container.add_item(item)关于物品 ID:
ItemData.id类型为StringName,建议使用有意义的标识符(如&"health_potion"、&"iron_sword")而非数字字符串。StringName在 Godot 中具有 O(1) 的比较性能,不影响运行效率。
container.remove_item_in_position(0, 1) # 按位置删除指定数量
container.remove_item_by_id(&"apple", 2) # 按物品 ID 删除指定数量# 移动物品到另一个容器
Swapper.move_item(src_container, dst_container, item, count, dst_index)
Swapper.move_by_id(bag, warehouse, &"apple", 5)
# 交换两个位置
Swapper.swap_positions(container_a, index_a, container_b, index_b)
# 堆叠合并 / 拆分
Swapper.merge_stack(container, from_index, to_index)
Swapper.split_stack(container, index, split_count, dest_index)var preview := Swapper.simulate_move(bag, warehouse, &"apple", 5)
if preview["code"] == Swapper.SUCCESS:
print("源容器变化: ", preview["src_changes"])
print("目标容器变化: ", preview["dst_changes"])container.item_changed.connect(_on_item_changed)
container.size_changed.connect(_on_size_changed)
container.illegal_items_changed.connect(_on_illegal_items)
func _on_item_changed(is_add: bool, index: int, item: Item):
# 刷新对应槽位的 UI
pass| 信号 | 参数 | 触发时机 |
|---|---|---|
item_changed |
is_add: bool, index: int, item: Item |
物品添加、移除、数量变更 |
illegal_items_changed |
illegal_items: Array[Item] |
容器缩容后溢出的物品 |
size_changed |
new_size: int |
容器大小变更 |
| 码值 | 常量 | 说明 |
|---|---|---|
| 200 | SUCCESS |
操作成功 |
| 400 | CAN_ADD_ITEM_TAG_CONTAIN_ERROR |
标签不匹配,物品被容器拒绝 |
| 401 | CAN_ADD_ITEM_INDEX_ERROR |
目标位置已被占用 |
| 402 | CAN_ADD_ITEM_STACK_ERROR |
堆叠数量超过上限 |
| 406 | CAN_ADD_ITEM_INDEX_OUT_OF_RANGE_ERROR |
索引越界 |
| 407 | CAN_REMOVE_ITEM_NUM_ERROR |
移除数量超过持有数量 |
| 408 | CAN_REMOVE_ITEM_INDEX_NULL_ERROR |
目标位置为空 |
| 409 | CAN_ADD_ITEM_SPACE_ERROR |
容器空间不足 |
| 410 | ID_NOT_FOUND_ERROR |
物品 ID 不存在 |
| 码值 | 常量 | 说明 |
|---|---|---|
| 500 | ERROR_SRC_CONTAINER_NULL |
源容器为空 |
| 501 | ERROR_DST_CONTAINER_NULL |
目标容器为空 |
| 502 | ERROR_SRC_INDEX_INVALID |
源索引无效 |
| 503 | ERROR_DST_INDEX_INVALID |
目标索引无效 |
| 504 | ERROR_ITEM_NULL |
物品为空 |
| 505 | ERROR_SPLIT_NUM_INVALID |
拆分数量无效 |
| 506 | ERROR_SAME_POSITION |
源与目标是同一位置 |
| 507 | ERROR_PARTIAL_SUCCESS |
批量操作部分成功 |
项目包含一个完整的背包-仓库交互示例,运行 Scenes/Container_Sample_01.tscn 即可体验。
ContainerSample(主场景)
├── Bagpack(背包面板 — 8 格,Tag 限制)
│ └── GridContainer
│ └── SampleContainerSlotUI × 8
│ └── SampleItemUI
├── Storehouse(仓库面板 — 16 格,无限制)
│ └── GridContainer
│ └── SampleContainerSlotUI × 16
│ └── SampleItemUI
└── ItemTooltip(物品详情悬浮框)
| 组件 | 脚本 | 职责 |
|---|---|---|
ContainerSample |
Scripts/container_sample.gd |
初始化容器、绑定信号、管理 UI |
SampleContainerSlotUI |
Scripts/container_slot.gd |
槽位 UI,处理拖放与标签校验 |
SampleItemUI |
Scripts/item_ui.gd |
物品图标与数量渲染、拖拽预览 |
ItemTooltip |
Scripts/item_tooltip.gd |
悬浮详情面板、"使用"按钮 |
| 操作 | 效果 |
|---|---|
| 点击"添加物品 A/B" | 向背包添加物品(需符合标签要求) |
| 拖拽物品到空位 | 移动物品 |
| 拖拽到同种物品 | 自动堆叠 |
| 拖拽到不同物品 | 交换位置 |
| 悬浮物品 1 秒 | 显示物品详情 |
| 点击"使用"按钮 | 消耗 1 个物品,触发物品行为 |
继承 ItemBehaviourData,重写 use_item 方法:
class_name MyBehaviour extends ItemBehaviourData
func use_item(item: Item, character_from, character_to, num: int):
# 实现你的物品使用逻辑
pass通过编辑器 Tag Manager 面板创建和管理层级标签,每个标签独立存储为 .tres 文件。然后将标签分配给容器的 addable_tags:
container.addable_tags = [fruit_tag, weapon_tag]
container.use_hierarchical_tags = true # 启用层级匹配标签的父子关系仅通过
child_tags单向存储,parent_tag在加载时由TagHierarchy.initialize_paths()自动重建,避免序列化时的循环引用。
- 重量 / 体积系统
- 自动整理与排序
- 交易 / 合成 / 掉落逻辑
- 物品冷却与耐久度
- 快捷栏绑定
- 引擎:Godot 4.x(建议 4.4+)
- 语言:GDScript
- 定位:运行时逻辑层,UI 需自行实现(可参考示例项目)
MIT License