本文へ移動
cccskills
無料GitHub で公開

godot-inventory-system

Godot 4背包系统完整实现,包括数据层、逻辑层和UI层设计。支持物品管理、拖放交互、格子满了处理等常见功能。

インストール方法を見る

含まれるファイル(1)

  • SKILL.md13.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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 物品资源

# 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 格子数据

# 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 背包组件

# 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

# 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 背包主面板

# 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()

使用示例

物品数据库

# 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

玩家装备背包

# 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
  • 装备栏 - 特殊格子只能放特定类型装备
  • 合成系统 - 物品组合生成新物品

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Godot 4 Autoload单例模式最佳实践,包含全局管理、事件总线、信号通信。

日本語の概要は準備中です。原文の説明を表示しています。

925236118/AlphaAgent1032026年10月8日 更新

Godot 4 Buff系统完整实现,支持状态效果管理、持续时间、循环效果、层叠机制等。使用Resource+Component架构。

日本語の概要は準備中です。原文の説明を表示しています。

925236118/AlphaAgent1032026年10月8日 更新

Godot 4 建造系统实现,包含GridMap方块管理、第一人称视角、射线交互。

日本語の概要は準備中です。原文の説明を表示しています。

925236118/AlphaAgent1032026年10月8日 更新

Godot 4 ECS实体组件系统模式,包含组件化设计、数据与逻辑分离、组件通信。

日本語の概要は準備中です。原文の説明を表示しています。

925236118/AlphaAgent1032026年10月8日 更新

Godot 4 种植系统设计,包含植物数据结构、生长阶段、浇水机制、TileMap应用。

日本語の概要は準備中です。原文の説明を表示しています。

925236118/AlphaAgent1032026年10月8日 更新

Master Godot 4 GDScript patterns including signals, scenes, state machines, and optimization. Use when building Godot games, implementing game systems, or learning GDScript best practices.

日本語の概要は準備中です。原文の説明を表示しています。

925236118/AlphaAgent1032026年10月8日 更新

925236118 のスキルをすべて見る

このスキルの問題を報告する