godot-inventory-system

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Inventory System

库存系统

Slot management, stacking logic, and resource-based items define robust inventory systems.
槽位管理、堆叠逻辑以及基于资源的物品是构建健壮库存系统的核心。

Available Scripts

可用脚本

grid_inventory_logic.gd

grid_inventory_logic.gd

Expert grid inventory with tetris-style placement logic.
带有俄罗斯方块式放置逻辑的专业网格型库存。

inventory_grid.gd

inventory_grid.gd

Grid-based inventory controller with drag-and-drop foundations and auto-sorting.
基于网格的库存控制器,具备拖放基础功能与自动排序机制。

NEVER Do in Inventory Systems

库存系统中的绝对禁忌

  • NEVER use Nodes for items
    Item extends Node
    = memory leak nightmare. Inventory with 100 items = 100 nodes in tree. Use
    Item extends Resource
    for save compatibility.
  • NEVER forget to check max_stack before adding
    add_item()
    without stack logic = items disappear silently. ALWAYS attempt stacking BEFORE creating new slots.
  • NEVER modify inventory directly from UI
    InventorySlotUI.item = null
    on click = desynced state. UI should emit signals, Inventory model updates, THEN UI refreshes via signals.
  • NEVER use float for item quantities — Floating-point error: 10.0 - 0.1 * 100 ≠ 0. Use
    int
    for countable items. Only use float for weight/volume limits.
  • NEVER forget to validate weight/capacity before adding — Player adds 1000kg item to 100kg inventory? Must check
    get_total_weight() + item.weight * amount <= max_weight
    BEFORE adding.
  • NEVER emit
    inventory_changed
    inside loop
    — Adding 100 items = 100 UI refreshes = lag spike. Batch operations, emit ONCE after loop completes.

  • 绝对不要用Node实现物品
    Item extends Node
    会造成内存泄漏噩梦。100个物品的库存就会在节点树中生成100个节点。请使用
    Item extends Resource
    以确保存档兼容性。
  • 绝对不要在添加物品前不检查最大堆叠数 — 不添加堆叠逻辑的
    add_item()
    会导致物品无声消失。务必在创建新槽位前尝试堆叠物品
  • 绝对不要直接从UI修改库存 — 点击时执行
    InventorySlotUI.item = null
    会导致状态不同步。UI应发送信号,由库存模型进行更新,随后UI再通过信号刷新。
  • 绝对不要用浮点数表示物品数量 — 浮点数误差:10.0 - 0.1 * 100 ≠ 0。可数物品请使用
    int
    类型。仅在表示重量/体积限制时使用浮点数。
  • 绝对不要在添加物品前不验证重量/容量 — 玩家要把1000kg的物品放进100kg容量的库存?添加前务必检查
    get_total_weight() + item.weight * amount <= max_weight
  • 绝对不要在循环内发送
    inventory_changed
    信号
    — 添加100个物品会触发100次UI刷新,导致卡顿。批量操作应在循环结束后仅发送一次信号。

Core Architecture

核心架构

gdscript
undefined
gdscript
undefined

item.gd (Resource)

item.gd (Resource)

class_name Item extends Resource
@export var id: String @export var display_name: String @export var icon: Texture2D @export var max_stack: int = 1 @export var weight: float = 0.0 @export_multiline var description: String
undefined
class_name Item extends Resource
@export var id: String @export var display_name: String @export var icon: Texture2D @export var max_stack: int = 1 @export var weight: float = 0.0 @export_multiline var description: String
undefined

Inventory Manager

库存管理器

gdscript
undefined
gdscript
undefined

inventory.gd

inventory.gd

class_name Inventory extends Resource
signal item_added(item: Item, amount: int) signal item_removed(item: Item, amount: int) signal inventory_changed
@export var slots: Array[InventorySlot] = [] @export var max_slots: int = 20 @export var max_weight: float = 100.0
func _init() -> void: slots.resize(max_slots) for i in max_slots: slots[i] = InventorySlot.new()
func add_item(item: Item, amount: int = 1) -> bool: var remaining := amount
# Try stacking first
if item.max_stack > 1:
    for slot in slots:
        if slot.item == item and slot.amount < item.max_stack:
            var space := item.max_stack - slot.amount
            var to_add := mini(space, remaining)
            slot.amount += to_add
            remaining -= to_add
            
            if remaining <= 0:
                item_added.emit(item, amount)
                inventory_changed.emit()
                return true

# Add to empty slots
while remaining > 0:
    var empty_slot := find_empty_slot()
    if empty_slot == null:
        return false  # Inventory full
    
    var to_add := mini(item.max_stack, remaining)
    empty_slot.item = item
    empty_slot.amount = to_add
    remaining -= to_add

item_added.emit(item, amount)
inventory_changed.emit()
return true
func remove_item(item: Item, amount: int = 1) -> bool: var remaining := amount
for slot in slots:
    if slot.item == item:
        var to_remove := mini(slot.amount, remaining)
        slot.amount -= to_remove
        remaining -= to_remove
        
        if slot.amount <= 0:
            slot.clear()
        
        if remaining <= 0:
            item_removed.emit(item, amount)
            inventory_changed.emit()
            return true

return false  # Not enough items
func has_item(item: Item, amount: int = 1) -> bool: var count := 0 for slot in slots: if slot.item == item: count += slot.amount return count >= amount
func find_empty_slot() -> InventorySlot: for slot in slots: if slot.is_empty(): return slot return null
func get_total_weight() -> float: var total := 0.0 for slot in slots: if slot.item: total += slot.item.weight * slot.amount return total
undefined
class_name Inventory extends Resource
signal item_added(item: Item, amount: int) signal item_removed(item: Item, amount: int) signal inventory_changed
@export var slots: Array[InventorySlot] = [] @export var max_slots: int = 20 @export var max_weight: float = 100.0
func _init() -> void: slots.resize(max_slots) for i in max_slots: slots[i] = InventorySlot.new()
func add_item(item: Item, amount: int = 1) -> bool: var remaining := amount
# 优先尝试堆叠
if item.max_stack > 1:
    for slot in slots:
        if slot.item == item and slot.amount < item.max_stack:
            var space := item.max_stack - slot.amount
            var to_add := mini(space, remaining)
            slot.amount += to_add
            remaining -= to_add
            
            if remaining <= 0:
                item_added.emit(item, amount)
                inventory_changed.emit()
                return true

# 添加到空槽位
while remaining > 0:
    var empty_slot := find_empty_slot()
    if empty_slot == null:
        return false  # 库存已满
    
    var to_add := mini(item.max_stack, remaining)
    empty_slot.item = item
    empty_slot.amount = to_add
    remaining -= to_add

item_added.emit(item, amount)
inventory_changed.emit()
return true
func remove_item(item: Item, amount: int = 1) -> bool: var remaining := amount
for slot in slots:
    if slot.item == item:
        var to_remove := mini(slot.amount, remaining)
        slot.amount -= to_remove
        remaining -= to_remove
        
        if slot.amount <= 0:
            slot.clear()
        
        if remaining <= 0:
            item_removed.emit(item, amount)
            inventory_changed.emit()
            return true

return false  # 物品数量不足
func has_item(item: Item, amount: int = 1) -> bool: var count := 0 for slot in slots: if slot.item == item: count += slot.amount return count >= amount
func find_empty_slot() -> InventorySlot: for slot in slots: if slot.is_empty(): return slot return null
func get_total_weight() -> float: var total := 0.0 for slot in slots: if slot.item: total += slot.item.weight * slot.amount return total
undefined

Inventory Slot

库存槽位

gdscript
undefined
gdscript
undefined

inventory_slot.gd

inventory_slot.gd

class_name InventorySlot extends Resource
signal slot_changed
var item: Item = null var amount: int = 0
func is_empty() -> bool: return item == null
func clear() -> void: item = null amount = 0 slot_changed.emit()
undefined
class_name InventorySlot extends Resource
signal slot_changed
var item: Item = null var amount: int = 0
func is_empty() -> bool: return item == null
func clear() -> void: item = null amount = 0 slot_changed.emit()
undefined

Equipment System

装备系统

gdscript
undefined
gdscript
undefined

equipment.gd

equipment.gd

class_name Equipment extends Resource
signal equipment_changed(slot: String, item: Item)
@export var weapon: Item = null @export var armor: Item = null @export var accessory: Item = null
func equip(slot: String, item: Item) -> Item: var old_item: Item = null
match slot:
    "weapon":
        old_item = weapon
        weapon = item
    "armor":
        old_item = armor
        armor = item
    "accessory":
        old_item = accessory
        accessory = item

equipment_changed.emit(slot, item)
return old_item
func unequip(slot: String) -> Item: return equip(slot, null)
func get_total_stats() -> Dictionary: var stats := { "attack": 0, "defense": 0, "speed": 0 }
for item in [weapon, armor, accessory]:
    if item and item.has("stats"):
        for key in item.stats:
            stats[key] += item.stats[key]

return stats
undefined
class_name Equipment extends Resource
signal equipment_changed(slot: String, item: Item)
@export var weapon: Item = null @export var armor: Item = null @export var accessory: Item = null
func equip(slot: String, item: Item) -> Item: var old_item: Item = null
match slot:
    "weapon":
        old_item = weapon
        weapon = item
    "armor":
        old_item = armor
        armor = item
    "accessory":
        old_item = accessory
        accessory = item

equipment_changed.emit(slot, item)
return old_item
func unequip(slot: String) -> Item: return equip(slot, null)
func get_total_stats() -> Dictionary: var stats := { "attack": 0, "defense": 0, "speed": 0 }
for item in [weapon, armor, accessory]:
    if item and item.has("stats"):
        for key in item.stats:
            stats[key] += item.stats[key]

return stats
undefined

UI Integration

UI集成

gdscript
undefined
gdscript
undefined

inventory_ui.gd

inventory_ui.gd

extends Control
@onready var grid := $GridContainer var inventory: Inventory
func _ready() -> void: inventory.inventory_changed.connect(refresh_ui) refresh_ui()
func refresh_ui() -> void: # Clear existing for child in grid.get_children(): child.queue_free()
# Create slot UI
for slot in inventory.slots:
    var slot_ui := InventorySlotUI.new()
    slot_ui.setup(slot)
    grid.add_child(slot_ui)
undefined
extends Control
@onready var grid := $GridContainer var inventory: Inventory
func _ready() -> void: inventory.inventory_changed.connect(refresh_ui) refresh_ui()
func refresh_ui() -> void: # 清空现有内容 for child in grid.get_children(): child.queue_free()
# 创建槽位UI
for slot in inventory.slots:
    var slot_ui := InventorySlotUI.new()
    slot_ui.setup(slot)
    grid.add_child(slot_ui)
undefined

Crafting Integration

制作系统集成

gdscript
undefined
gdscript
undefined

crafting_recipe.gd

crafting_recipe.gd

class_name CraftingRecipe extends Resource
@export var result: Item @export var result_amount: int = 1 @export var requirements: Array[CraftingRequirement]
func can_craft(inventory: Inventory) -> bool: for req in requirements: if not inventory.has_item(req.item, req.amount): return false return true
func craft(inventory: Inventory) -> bool: if not can_craft(inventory): return false
# Remove ingredients
for req in requirements:
    inventory.remove_item(req.item, req.amount)

# Add result
inventory.add_item(result, result_amount)
return true
undefined
class_name CraftingRecipe extends Resource
@export var result: Item @export var result_amount: int = 1 @export var requirements: Array[CraftingRequirement]
func can_craft(inventory: Inventory) -> bool: for req in requirements: if not inventory.has_item(req.item, req.amount): return false return true
func craft(inventory: Inventory) -> bool: if not can_craft(inventory): return false
# 移除材料
for req in requirements:
    inventory.remove_item(req.item, req.amount)

# 添加产物
inventory.add_item(result, result_amount)
return true
undefined

Save/Load

存档/读档

gdscript
func save_inventory() -> Dictionary:
    return {
        "slots": slots.map(func(s): return s.to_dict())
    }

func load_inventory(data: Dictionary) -> void:
    for i in data.slots.size():
        slots[i].from_dict(data.slots[i])
    inventory_changed.emit()
gdscript
func save_inventory() -> Dictionary:
    return {
        "slots": slots.map(func(s): return s.to_dict())
    }

func load_inventory(data: Dictionary) -> void:
    for i in data.slots.size():
        slots[i].from_dict(data.slots[i])
    inventory_changed.emit()

Best Practices

最佳实践

  1. Use Resources - Items as Resources, not class instances
  2. Signal-Driven UI - Emit signals, let UI listen
  3. Stack Logic - Always check
    max_stack
    first
  4. Weight Limits - Validate before adding
  1. 使用Resource - 物品应作为Resource实现,而非类实例
  2. 信号驱动UI - 发送信号,由UI监听
  3. 堆叠逻辑优先 - 始终先检查
    max_stack
  4. 重量限制验证 - 添加前务必验证

Reference

参考

  • Related:
    godot-save-load-systems
    ,
    godot-resource-data-patterns
  • 相关内容:
    godot-save-load-systems
    ,
    godot-resource-data-patterns

Related

相关

  • Master Skill: godot-master
  • 核心技能:godot-master