Godot 4背包系统完整实现,包括数据层、逻辑层和UI层设计。支持物品管理、拖放交互、格子满了处理等常见功能。
Scanned 9/12/2026
Install to Claude Code
npx -y skills add 925236118/AlphaAgent --skill godot-inventory-system --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Godot Inventory System?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/925236118-godot-inventory-system)More formats (shields.io, HTML) on the badges page.
---
name: godot-inventory-system
description: Godot 4背包系统完整实现,包括数据层、逻辑层和UI层设计。支持物品管理、拖放交互、格子满了处理等常见功能。
---
# Godot 4 背包系统
完整的背包系统实现,支持物品添加、移除、拖放排序、格子容量管理等功能。
## 何时使用此技能
- 实现游戏物品管理系统
- 需要拖放交互的背包UI
- 物品类型和数量管理
- 装备栏或商店系统
## 架构设计
### 分层架构
```
背包系统
├── 数据层 (Data Layer)
│ ├── InventoryItem - 单个物品数据
│ └── InventorySlot - 格子数据(包含物品引用和数量)
├── 逻辑层 (Business Layer)
│ └── InventoryComponent - CRUD操作、堆叠逻辑
└── UI层 (Presentation Layer)
├── InventoryUI - 背包主面板
└── InventorySlotUI - 单个格子UI
```
### MVC分离
- **Model**: `InventoryItem`、`InventorySlot` - 纯数据
- **View**: `InventorySlotUI` - 仅负责显示
- **Controller**: `InventoryComponent` - 处理所有业务逻辑
## 数据层设计
### InventoryItem 物品资源
```gdscript
# inventory_item.gd
class_name InventoryItem
extends Resource
## 物品唯一标识符
@export var id: StringName
## 物品显示名称
@export var item_name: String
## 物品描述
@export_multiline var description: String
## 最大堆叠数量
@export var max_stack: int = 99
## 物品图标
@export var icon: Texture2D
## 物品 Prefab(可选,用于拖放到场景)
@export var item_scene: PackedScene
## 物品稀有度
@export_enum("Common", "Uncommon", "Rare", "Epic", "Legendary") var rarity: int = 0
## 是否可以丢弃
@export var can_drop: bool = true
## 是否可以堆叠
@export var can_stack: bool = true
## 物品价值(用于商店)
@export var value: int = 1
```
### InventorySlot 格子数据
```gdscript
# inventory_slot.gd
class_name InventorySlot
extends RefCounted
## 格子索引
var index: int
## 格子中的物品
var item: InventoryItem
## 物品数量
var quantity: int = 0
## 格子是否为空
var is_empty: bool:
get: return item == null or quantity <= 0
func can_add(new_item: InventoryItem, amount: int = 1) -> bool:
if is_empty:
return true
if item.id == new_item.id and item.can_stack:
return quantity + amount <= item.max_stack
return false
func add(new_item: InventoryItem, amount: int = 1) -> bool:
if not can_add(new_item, amount):
return false
if is_empty:
item = new_item
quantity = amount
else:
quantity += amount
return true
func remove(amount: int = 1) -> bool:
if is_empty or quantity < amount:
return false
quantity -= amount
if quantity <= 0:
clear()
return true
func clear() -> void:
item = null
quantity = 0
```
## 逻辑层设计
### InventoryComponent 背包组件
```gdscript
# inventory_component.gd
class_name InventoryComponent
extends Node
## 背包容量
@export var capacity: int = 20
## 信号:物品添加成功
signal item_added(slot_index: int, item: InventoryItem, quantity: int)
## 信号:物品移除成功
signal item_removed(slot_index: int, item: InventoryItem, quantity: int)
## 信号:背包满了
signal inventory_full(item: InventoryItem)
## 信号:格子数据变化
signal slot_changed(slot_index: int)
## 背包格子数组
var slots: Array[InventorySlot] = []
func _ready() -> void:
_initialize_slots()
func _initialize_slots() -> void:
for i in capacity:
var slot := InventorySlot.new()
slot.index = i
slots.append(slot)
## 添加物品到背包
func add_item(item: InventoryItem, quantity: int = 1) -> bool:
# 优先堆叠到已有物品
var target_slot := _find_stackable_slot(item)
if target_slot != null:
var old_qty := target_slot.quantity
target_slot.add(item, quantity)
slot_changed.emit(target_slot.index)
item_added.emit(target_slot.index, item, target_slot.quantity - old_qty)
return true
# 找空格子
var empty_slot := _find_empty_slot()
if empty_slot != null:
empty_slot.add(item, quantity)
slot_changed.emit(empty_slot.index)
item_added.emit(empty_slot.index, item, quantity)
return true
# 背包满了
inventory_full.emit(item)
return false
## 移除物品
func remove_item(slot_index: int, quantity: int = 1) -> bool:
if slot_index < 0 or slot_index >= slots.size():
return false
var slot: InventorySlot = slots[slot_index]
if slot.is_empty:
return false
var item := slot.item
if slot.remove(quantity):
slot_changed.emit(slot_index)
item_removed.emit(slot_index, item, quantity)
return true
return false
## 交换两个格子的物品
func swap_slots(index1: int, index2: int) -> void:
if index1 < 0 or index1 >= slots.size() or index2 < 0 or index2 >= slots.size():
return
var temp_item := slots[index1].item
var temp_qty := slots[index1].quantity
slots[index1].item = slots[index2].item
slots[index1].quantity = slots[index2].quantity
slots[index2].item = temp_item
slots[index2].quantity = temp_qty
slot_changed.emit(index1)
slot_changed.emit(index2)
## 移动物品到另一个背包
func move_to(target: InventoryComponent, slot_index: int, quantity: int = -1) -> bool:
if slot_index < 0 or slot_index >= slots.size():
return false
var slot: InventorySlot = slots[slot_index]
if slot.is_empty:
return false
var transfer_qty := quantity if quantity > 0 else slot.quantity
if target.add_item(slot.item, transfer_qty):
remove_item(slot_index, transfer_qty)
return true
return false
## 查找可堆叠的格子
func _find_stackable_slot(item: InventoryItem) -> InventorySlot:
if not item.can_stack:
return null
for slot in slots:
if not slot.is_empty and slot.item.id == item.id:
if slot.quantity + 1 <= slot.item.max_stack:
return slot
return null
## 查找空格子
func _find_empty_slot() -> InventorySlot:
for slot in slots:
if slot.is_empty:
return slot
return null
## 获取指定物品的数量
func get_item_count(item_id: StringName) -> int:
var total := 0
for slot in slots:
if not slot.is_empty and slot.item.id == item_id:
total += slot.quantity
return total
## 清空背包
func clear() -> void:
for slot in slots:
slot.clear()
slot_changed.emit(slot.index)
## 保存背包数据
func get_save_data() -> Dictionary:
var data := []
for slot in slots:
if not slot.is_empty:
data.append({
"index": slot.index,
"item_id": slot.item.id,
"quantity": slot.quantity
})
return {"slots": data, "capacity": capacity}
## 加载背包数据
func load_save_data(data: Dictionary) -> void:
clear()
if data.has("slots"):
for slot_data in data.slots:
var index: int = slot_data.index
var item_id: StringName = slot_data.item_id
var quantity: int = slot_data.quantity
# 查找物品数据(实际项目中从物品数据库获取)
var item := _get_item_by_id(item_id)
if item != null and index < slots.size():
slots[index].item = item
slots[index].quantity = quantity
slot_changed.emit(index)
```
## UI系统设计
### InventorySlotUI 格子UI
```gdscript
# inventory_slot_ui.gd
class_name InventorySlotUI
extends PanelContainer
## 信号:物品被拖走
signal item_drag_started(slot_index: int)
## 信号:物品放置到格子
signal item_dropped(slot_index: int, from_slot: int)
## 信号:右键点击
signal right_clicked(slot_index: int)
@export var slot_index: int = 0
@onready var icon_texture: TextureRect = %IconTexture
@onready var quantity_label: Label = %QuantityLabel
@onready var item_name_label: Label = %ItemNameLabel
var current_item: InventoryItem
var current_quantity: int = 0
var _is_dragging: bool = false
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_STOP
# 初始化为空白
_clear_display()
func set_slot_data(slot: InventorySlot) -> void:
if slot.is_empty:
_clear_display()
return
current_item = slot.item
current_quantity = slot.quantity
_update_display()
func _update_display() -> void:
if current_item == null:
_clear_display()
return
icon_texture.texture = current_item.icon
icon_texture.visible = true
if current_quantity > 1:
quantity_label.text = str(current_quantity)
quantity_label.visible = true
else:
quantity_label.visible = false
item_name_label.text = current_item.item_name
item_name_label.visible = true
func _clear_display() -> void:
current_item = null
current_quantity = 0
icon_texture.texture = null
icon_texture.visible = false
quantity_label.visible = false
item_name_label.visible = false
func _on_mouse_entered() -> void:
if current_item != null:
# 显示物品提示
pass
func _on_gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
_on_left_click()
else:
_on_left_release()
elif event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
right_clicked.emit(slot_index)
func _on_left_click() -> void:
if current_item != null:
item_drag_started.emit(slot_index)
func _on_left_release() -> void:
pass
```
### InventoryUI 背包主面板
```gdscript
# inventory_ui.gd
class_name InventoryUI
extends Control
@export var inventory_component: InventoryComponent
@export var slot_scene: PackedScene
@onready var grid_container: GridContainer = %GridContainer
@onready var title_label: Label = %TitleLabel
var slotuis: Array[InventorySlotUI] = []
var _dragged_slot_index: int = -1
var _drag_preview: Control
func _ready() -> void:
assert(inventory_component != null, "InventoryComponent未设置")
inventory_component.slot_changed.connect(_on_slot_changed)
inventory_component.inventory_full.connect(_on_inventory_full)
_create_slot_ui()
_refresh_all_slots()
func _create_slot_ui() -> void:
for i in inventory_component.capacity:
var slot_ui: InventorySlotUI = slot_scene.instantiate()
slot_ui.slot_index = i
slot_ui.item_drag_started.connect(_on_item_drag_started)
slot_ui.item_dropped.connect(_on_item_dropped)
slot_ui.right_clicked.connect(_on_slot_right_clicked)
grid_container.add_child(slot_ui)
slotuis.append(slot_ui)
func _refresh_all_slots() -> void:
for i in inventory_component.slots.size():
slotuis[i].set_slot_data(inventory_component.slots[i])
func _on_slot_changed(slot_index: int) -> void:
if slot_index < slotuis.size():
slotuis[slot_index].set_slot_data(inventory_component.slots[slot_index])
func _on_inventory_full(item: InventoryItem) -> void:
# 显示提示(例如:"背包已满")
print("背包已满,无法添加 ", item.item_name)
func _on_item_drag_started(slot_index: int) -> void:
_dragged_slot_index = slot_index
func _on_item_dropped(slot_index: int, from_slot: int) -> void:
if from_slot != slot_index:
inventory_component.swap_slots(from_slot, slot_index)
func _on_slot_right_clicked(slot_index: int) -> void:
var slot: InventorySlot = inventory_component.slots[slot_index]
if slot.is_empty:
return
# 丢弃物品或显示物品菜单
if slot.item.can_drop:
inventory_component.remove_item(slot_index, 1)
func _input(event: InputEvent) -> void:
if event.is_action_pressed("toggle_inventory"):
visible = not visible
if visible:
_refresh_all_slots()
```
## 使用示例
### 物品数据库
```gdscript
# item_database.gd (Autoload)
extends Node
@export var items: Array[InventoryItem]
func _ready() -> void:
# 注册所有物品到全局数据库
pass
func get_item(id: StringName) -> InventoryItem:
for item in items:
if item.id == id:
return item
return null
func has_item(id: StringName) -> bool:
return get_item(id) != null
```
### 玩家装备背包
```gdscript
# player.gd
extends CharacterBody2D
@export var inventory: InventoryComponent
func _ready() -> void:
# 给玩家一些初始物品
var sword := item_database.get_item("sword")
var potion := item_database.get_item("health_potion")
if sword:
inventory.add_item(sword, 1)
if potion:
inventory.add_item(potion, 5)
```
## 最佳实践
1. **数据与显示分离** - InventorySlot只存储数据,不负责显示
2. **使用Resource** - InventoryItem作为Resource便于序列化
3. **信号解耦** - UI通过信号监听数据变化,不直接查询数据
4. **堆叠逻辑** - 添加物品时优先堆叠,节省格子空间
5. **保存/加载** - 使用get_save_data()和load_save_data()实现持久化
## 扩展功能
- **物品类型过滤** - 不同背包类型接受不同物品
- **商人系统** - 买卖物品基于value
- **装备栏** - 特殊格子只能放特定类型装备
- **合成系统** - 物品组合生成新物品
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!