6. 物品与背包
2026/4/14大约 10 分钟
6. 伏魔记——物品与背包
6.1 什么是背包系统?
想象你出门旅行,背了一个双肩包。里面装着衣服、零食、水壶、地图、雨伞——你需要根据不同的情况,从包里拿出不同的东西来用。
背包系统就是游戏里的这个"双肩包"。它让玩家能够:
- 查看物品:看看自己有哪些东西
- 使用物品:在战斗中或探索时使用药品恢复HP
- 装备武器/防具:穿上更好的装备来增强属性
- 丢弃物品:扔掉不需要的东西腾出空间
6.2 物品分类
RPG中的物品通常分为四大类:
| 类型 | 说明 | 举例 | 能否堆叠 |
|---|---|---|---|
| 消耗品 | 用一次就没了的东西 | 金创药、蓝瓶、解毒草 | 能(最多99个) |
| 武器 | 装在手上增加攻击力 | 铁剑、法杖、弓 | 不能 |
| 防具 | 穿在身上增加防御力 | 布衣、铁甲、皮靴 | 不能 |
| 关键道具 | 推进剧情的特殊物品 | 钥匙、信件、宝石 | 不能 |
物品数据定义
C
using Godot;
/// <summary>
/// 物品数据库——所有游戏物品的定义
/// </summary>
public static class ItemDatabase
{
public static readonly ItemData[] AllItems = new[]
{
// ===== 消耗品 =====
new ItemData
{
Id = "hp_potion_s",
Name = "小金创药",
Description = "恢复30点HP",
Type = ItemType.Consumable,
Price = 20,
IsStackable = true,
MaxStack = 99,
HealHp = 30,
HealMp = 0
},
new ItemData
{
Id = "hp_potion_m",
Name = "中金创药",
Description = "恢复80点HP",
Type = ItemType.Consumable,
Price = 50,
IsStackable = true,
MaxStack = 99,
HealHp = 80,
HealMp = 0
},
new ItemData
{
Id = "mp_potion_s",
Name = "小蓝瓶",
Description = "恢复15点MP",
Type = ItemType.Consumable,
Price = 30,
IsStackable = true,
MaxStack = 99,
HealHp = 0,
HealMp = 15
},
new ItemData
{
Id = "antidote",
Name = "解毒草",
Description = "解除中毒状态",
Type = ItemType.Consumable,
Price = 15,
IsStackable = true,
MaxStack = 99,
HealHp = 0,
HealMp = 0
},
// ===== 武器 =====
new ItemData
{
Id = "wooden_sword",
Name = "木剑",
Description = "新手用的木剑,聊胜于无",
Type = ItemType.Weapon,
Price = 50,
IsStackable = false,
AttackBonus = 5,
DefenseBonus = 0,
SpeedBonus = 0
},
new ItemData
{
Id = "iron_sword",
Name = "铁剑",
Description = "坚固的铁制长剑",
Type = ItemType.Weapon,
Price = 200,
IsStackable = false,
AttackBonus = 15,
DefenseBonus = 0,
SpeedBonus = 0
},
new ItemData
{
Id = "flame_blade",
Name = "烈焰之刃",
Description = "附魔了火焰的利剑",
Type = ItemType.Weapon,
Price = 800,
IsStackable = false,
AttackBonus = 35,
DefenseBonus = 0,
SpeedBonus = 2
},
// ===== 防具 =====
new ItemData
{
Id = "cloth_armor",
Name = "布衣",
Description = "普通的布制衣服",
Type = ItemType.Armor,
Price = 40,
IsStackable = false,
AttackBonus = 0,
DefenseBonus = 5,
SpeedBonus = 0
},
new ItemData
{
Id = "iron_armor",
Name = "铁甲",
Description = "厚重的铁制铠甲",
Type = ItemType.Armor,
Price = 300,
IsStackable = false,
AttackBonus = 0,
DefenseBonus = 18,
SpeedBonus = -2
},
// ===== 关键道具 =====
new ItemData
{
Id = "forest_key",
Name = "森林钥匙",
Description = "打开幽暗森林深处的大门",
Type = ItemType.KeyItem,
Price = 0,
IsStackable = false
}
};
/// <summary>
/// 根据ID查找物品
/// </summary>
public static ItemData FindById(string id)
{
foreach (var item in AllItems)
{
if (item.Id == id) return item;
}
return null;
}
}GDScript
## 物品数据库——所有游戏物品的定义
class_name ItemDatabase
const ALL_ITEMS: Array[Dictionary] = [
# ===== 消耗品 =====
{
"id": "hp_potion_s",
"name": "小金创药",
"description": "恢复30点HP",
"type": ItemType.CONSUMABLE,
"price": 20,
"is_stackable": true,
"max_stack": 99,
"heal_hp": 30,
"heal_mp": 0
},
{
"id": "hp_potion_m",
"name": "中金创药",
"description": "恢复80点HP",
"type": ItemType.CONSUMABLE,
"price": 50,
"is_stackable": true,
"max_stack": 99,
"heal_hp": 80,
"heal_mp": 0
},
{
"id": "mp_potion_s",
"name": "小蓝瓶",
"description": "恢复15点MP",
"type": ItemType.CONSUMABLE,
"price": 30,
"is_stackable": true,
"max_stack": 99,
"heal_hp": 0,
"heal_mp": 15
},
# ===== 武器 =====
{
"id": "wooden_sword",
"name": "木剑",
"description": "新手用的木剑",
"type": ItemType.WEAPON,
"price": 50,
"is_stackable": false,
"attack_bonus": 5,
"defense_bonus": 0,
"speed_bonus": 0
},
{
"id": "iron_sword",
"name": "铁剑",
"description": "坚固的铁制长剑",
"type": ItemType.WEAPON,
"price": 200,
"is_stackable": false,
"attack_bonus": 15,
"defense_bonus": 0,
"speed_bonus": 0
},
# ===== 防具 =====
{
"id": "cloth_armor",
"name": "布衣",
"description": "普通的布制衣服",
"type": ItemType.ARMOR,
"price": 40,
"is_stackable": false,
"attack_bonus": 0,
"defense_bonus": 5,
"speed_bonus": 0
},
# ===== 关键道具 =====
{
"id": "forest_key",
"name": "森林钥匙",
"description": "打开幽暗森林深处的大门",
"type": ItemType.KEY_ITEM,
"price": 0,
"is_stackable": false
}
]
## 根据ID查找物品
static func find_by_id(item_id: String) -> Dictionary:
for item in ALL_ITEMS:
if item.id == item_id:
return item
return {}6.3 背包管理器
InventoryManager 负责管理背包里的所有物品——添加、删除、使用、排序。
C
using Godot;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// 背包管理器——管理玩家持有的所有物品
/// </summary>
public partial class InventoryManager : Node
{
// 背包格子列表
private List<InventorySlot> _slots = new();
// 角色当前装备
private Dictionary<string, ItemData> _equipment = new();
// 信号
[Signal] public delegate void InventoryChangedEventHandler();
[Signal] public delegate void EquipmentChangedEventHandler(string slot);
public int ItemCount => _slots.Count;
public IReadOnlyList<InventorySlot> Slots => _slots.AsReadOnly();
public override void _Ready()
{
GD.Print("背包管理器已初始化");
}
/// <summary>
/// 添加物品到背包
/// 返回 true 表示添加成功
/// </summary>
public bool AddItem(string itemId, int quantity = 1)
{
var item = ItemDatabase.FindById(itemId);
if (item == null)
{
GD.PrintErr($"物品不存在: {itemId}");
return false;
}
// 可堆叠物品:先找已有的格子
if (item.IsStackable)
{
var existingSlot = _slots.Find(
s => s.Item.Id == itemId);
if (existingSlot != null)
{
int canAdd = item.MaxStack - existingSlot.Quantity;
int toAdd = Mathf.Min(quantity, canAdd);
existingSlot.Quantity += toAdd;
quantity -= toAdd;
if (quantity <= 0)
{
EmitSignal(SignalName.InventoryChanged);
return true;
}
}
}
// 剩余的物品需要新格子
while (quantity > 0)
{
if (_slots.Count >= RpgConstants.InventoryMaxSlots)
{
GD.Print("背包已满!");
return false;
}
int toAdd = item.IsStackable
? Mathf.Min(quantity, item.MaxStack)
: 1;
_slots.Add(new InventorySlot(item, toAdd));
quantity -= toAdd;
}
EmitSignal(SignalName.InventoryChanged);
return true;
}
/// <summary>
/// 从背包中移除指定数量的物品
/// </summary>
public bool RemoveItem(string itemId, int quantity = 1)
{
int remaining = quantity;
// 从后往前找(优先消耗后面的格子)
for (int i = _slots.Count - 1; i >= 0 && remaining > 0; i--)
{
if (_slots[i].Item.Id == itemId)
{
if (_slots[i].Quantity <= remaining)
{
remaining -= _slots[i].Quantity;
_slots.RemoveAt(i);
}
else
{
_slots[i].Quantity -= remaining;
remaining = 0;
}
}
}
if (remaining > 0)
{
GD.Print($"物品不足!需要 {quantity} 个,只有 {quantity - remaining} 个");
return false;
}
EmitSignal(SignalName.InventoryChanged);
return true;
}
/// <summary>
/// 检查背包中是否有指定数量的物品
/// </summary>
public bool HasItem(string itemId, int quantity = 1)
{
int total = _slots
.Where(s => s.Item.Id == itemId)
.Sum(s => s.Quantity);
return total >= quantity;
}
/// <summary>
/// 获取指定物品的总数量
/// </summary>
public int GetItemCount(string itemId)
{
return _slots
.Where(s => s.Item.Id == itemId)
.Sum(s => s.Quantity);
}
/// <summary>
/// 使用消耗品
/// </summary>
public bool UseItem(string itemId, CharacterStats target)
{
if (!HasItem(itemId)) return false;
var item = ItemDatabase.FindById(itemId);
if (item == null || item.Type != ItemType.Consumable) return false;
// 应用物品效果
if (item.HealHp > 0)
{
target = target.Heal(item.HealHp);
}
// 消耗一个物品
RemoveItem(itemId, 1);
GD.Print($"使用了 {item.Name}");
return true;
}
/// <summary>
/// 装备武器或防具
/// </summary>
public bool EquipItem(string itemId, CharacterStats character)
{
var item = ItemDatabase.FindById(itemId);
if (item == null) return false;
if (item.Type != ItemType.Weapon && item.Type != ItemType.Armor) return false;
// 从背包移除
RemoveItem(itemId, 1);
// 如果之前有装备,放回背包
string slotKey = item.Type == ItemType.Weapon ? "weapon" : "armor";
if (_equipment.ContainsKey(slotKey))
{
AddItem(_equipment[slotKey].Id, 1);
}
// 装备新物品
_equipment[slotKey] = item;
// 更新角色属性(加成)
// 实际项目中需要记录基础属性,这里简化处理
GD.Print($"装备了 {item.Name}");
EmitSignal(SignalName.EquipmentChanged, slotKey);
return true;
}
/// <summary>
/// 背包排序——按物品类型和名称排序
/// </summary>
public void SortInventory()
{
_slots = _slots
.OrderBy(s => (int)s.Item.Type)
.ThenBy(s => s.Item.Name)
.ToList();
EmitSignal(SignalName.InventoryChanged);
}
}GDScript
extends Node
## 背包管理器——管理玩家持有的所有物品
# 背包格子列表
var _slots: Array[Dictionary] = []
# 角色当前装备
var _equipment: Dictionary = {}
# 信号
signal inventory_changed
signal equipment_changed(slot: String)
func _ready() -> void:
print("背包管理器已初始化")
## 添加物品到背包
func add_item(item_id: String, quantity: int = 1) -> bool:
var item_data: Dictionary = ItemDatabase.find_by_id(item_id)
if item_data.is_empty():
printerr("物品不存在: ", item_id)
return false
# 可堆叠物品:先找已有的格子
if item_data.get("is_stackable", false):
for slot in _slots:
if slot.item.id == item_id:
var can_add: int = item_data.max_stack - slot.quantity
var to_add: int = mini(quantity, can_add)
slot.quantity += to_add
quantity -= to_add
if quantity <= 0:
inventory_changed.emit()
return true
# 剩余的物品需要新格子
while quantity > 0:
if _slots.size() >= RpgConstants.INVENTORY_MAX_SLOTS:
print("背包已满!")
return false
var to_add: int = mini(quantity, item_data.get("max_stack", 1))
_slots.append({"item": item_data, "quantity": to_add})
quantity -= to_add
inventory_changed.emit()
return true
## 从背包中移除指定数量的物品
func remove_item(item_id: String, quantity: int = 1) -> bool:
var remaining: int = quantity
var i: int = _slots.size() - 1
while i >= 0 and remaining > 0:
if _slots[i].item.id == item_id:
if _slots[i].quantity <= remaining:
remaining -= _slots[i].quantity
_slots.remove_at(i)
else:
_slots[i].quantity -= remaining
remaining = 0
i -= 1
if remaining > 0:
print("物品不足!")
return false
inventory_changed.emit()
return true
## 检查背包中是否有指定数量的物品
func has_item(item_id: String, quantity: int = 1) -> bool:
var total: int = 0
for slot in _slots:
if slot.item.id == item_id:
total += slot.quantity
return total >= quantity
## 获取指定物品的总数量
func get_item_count(item_id: String) -> int:
var total: int = 0
for slot in _slots:
if slot.item.id == item_id:
total += slot.quantity
return total
## 使用消耗品
func use_item(item_id: String, target: CharacterStats) -> bool:
if not has_item(item_id):
return false
var item_data: Dictionary = ItemDatabase.find_by_id(item_id)
if item_data.is_empty():
return false
# 应用物品效果
var heal_hp: int = item_data.get("heal_hp", 0)
var heal_mp: int = item_data.get("heal_mp", 0)
if heal_hp > 0:
target = target.heal(heal_hp)
remove_item(item_id, 1)
print("使用了 ", item_data.name)
return true
## 装备武器或防具
func equip_item(item_id: String) -> bool:
var item_data: Dictionary = ItemDatabase.find_by_id(item_id)
if item_data.is_empty():
return false
var item_type = item_data.get("type", 0)
if item_type != ItemType.WEAPON and item_type != ItemType.ARMOR:
return false
remove_item(item_id, 1)
var slot_key: String = "weapon" if item_type == ItemType.WEAPON else "armor"
if _equipment.has(slot_key):
add_item(_equipment[slot_key].id, 1)
_equipment[slot_key] = item_data
print("装备了 ", item_data.name)
equipment_changed.emit(slot_key)
return true
## 背包排序
func sort_inventory() -> void:
_slots.sort_custom(func(a, b):
var type_a: int = a.item.type
var type_b: int = b.item.type
if type_a != type_b:
return type_a < type_b
return a.item.name < b.item.name
)
inventory_changed.emit()6.4 装备系统
装备(武器和防具)穿戴后会直接影响角色的属性值。我们用一个简单的字典来记录角色当前穿了什么。
装备效果
| 装备 | 攻击加成 | 防御加成 | 速度加成 | 价格 |
|---|---|---|---|---|
| 木剑 | +5 | 0 | 0 | 50金 |
| 铁剑 | +15 | 0 | 0 | 200金 |
| 烈焰之刃 | +35 | 0 | +2 | 800金 |
| 布衣 | 0 | +5 | 0 | 40金 |
| 铁甲 | 0 | +18 | -2 | 300金 |
注意铁甲虽然防御很高,但会降低速度(因为太重了)——这就是装备权衡,让玩家需要思考"我该追求攻击还是防御"。
6.5 商店系统
商店NPC让玩家用金币买卖物品。
C
using Godot;
using System.Collections.Generic;
/// <summary>
/// 商店管理器——处理买卖交易
/// </summary>
public partial class ShopManager : Node
{
// 商店出售的商品列表
private List<string> _shopItems = new();
// 信号
[Signal] public delegate void PurchaseSuccessfulEventHandler(
string itemName, int price);
[Signal] public delegate void PurchaseFailedEventHandler(string reason);
/// <summary>
/// 打开商店,加载商品列表
/// </summary>
public void OpenShop(string[] itemIds)
{
_shopItems = new List<string>(itemIds);
GD.Print("商店已打开");
}
/// <summary>
/// 玩家购买物品
/// </summary>
public bool BuyItem(string itemId, int quantity = 1)
{
// 检查商店是否出售此物品
if (!_shopItems.Contains(itemId))
{
EmitSignal(SignalName.PurchaseFailed, "商店不卖这个");
return false;
}
var item = ItemDatabase.FindById(itemId);
int totalPrice = item.Price * quantity;
// 检查金币是否够
if (!GameManager.Instance.CanAfford(totalPrice))
{
EmitSignal(SignalName.PurchaseFailed, "金币不足");
return false;
}
// 扣除金币并添加物品
GameManager.Instance.SpendGold(totalPrice);
var inventory = GetNode<InventoryManager>("/root/Main/InventoryManager");
bool success = inventory.AddItem(itemId, quantity);
if (success)
{
EmitSignal(SignalName.PurchaseSuccessful, item.Name, totalPrice);
return true;
}
else
{
// 背包满了,退还金币
GameManager.Instance.AddGold(totalPrice);
EmitSignal(SignalName.PurchaseFailed, "背包已满");
return false;
}
}
/// <summary>
/// 玩家卖出物品
/// </summary>
public bool SellItem(string itemId, int quantity = 1)
{
var item = ItemDatabase.FindById(itemId);
if (item == null) return false;
// 关键道具不能卖
if (item.Type == ItemType.KeyItem)
{
EmitSignal(SignalName.PurchaseFailed, "关键道具不能出售");
return false;
}
var inventory = GetNode<InventoryManager>("/root/Main/InventoryManager");
if (!inventory.HasItem(itemId, quantity))
{
EmitSignal(SignalName.PurchaseFailed, "物品不足");
return false;
}
// 卖出价格是买入价格的一半
int sellPrice = (int)(item.Price * 0.5f) * quantity;
inventory.RemoveItem(itemId, quantity);
GameManager.Instance.AddGold(sellPrice);
GD.Print($"卖出 {item.Name} x{quantity},获得 {sellPrice} 金");
return true;
}
/// <summary>
/// 关闭商店
/// </summary>
public void CloseShop()
{
_shopItems.Clear();
GameManager.Instance.GameState = RpgGameState.TownExplore;
}
}GDScript
extends Node
## 商店管理器——处理买卖交易
# 商店出售的商品列表
var _shop_items: Array[String] = []
# 信号
signal purchase_successful(item_name: String, price: int)
signal purchase_failed(reason: String)
## 打开商店
func open_shop(item_ids: Array[String]) -> void:
_shop_items = item_ids.duplicate()
print("商店已打开")
## 玩家购买物品
func buy_item(item_id: String, quantity: int = 1) -> bool:
# 检查商店是否出售此物品
if not _shop_items.has(item_id):
purchase_failed.emit("商店不卖这个")
return false
var item_data: Dictionary = ItemDatabase.find_by_id(item_id)
var total_price: int = item_data.price * quantity
# 检查金币是否够
if not GameManager.can_afford(total_price):
purchase_failed.emit("金币不足")
return false
# 扣除金币并添加物品
GameManager.spend_gold(total_price)
var inventory = get_node("/root/Main/InventoryManager")
var success: bool = inventory.add_item(item_id, quantity)
if success:
purchase_successful.emit(item_data.name, total_price)
return true
else:
# 背包满了,退还金币
GameManager.add_gold(total_price)
purchase_failed.emit("背包已满")
return false
## 玩家卖出物品
func sell_item(item_id: String, quantity: int = 1) -> bool:
var item_data: Dictionary = ItemDatabase.find_by_id(item_id)
if item_data.is_empty():
return false
# 关键道具不能卖
if item_data.type == ItemType.KEY_ITEM:
purchase_failed.emit("关键道具不能出售")
return false
var inventory = get_node("/root/Main/InventoryManager")
if not inventory.has_item(item_id, quantity):
purchase_failed.emit("物品不足")
return false
# 卖出价格是买入价格的一半
var sell_price: int = int(item_data.price * 0.5) * quantity
inventory.remove_item(item_id, quantity)
GameManager.add_gold(sell_price)
print("卖出 ", item_data.name, " x", quantity, ",获得 ", sell_price, " 金")
return true
## 关闭商店
func close_shop() -> void:
_shop_items.clear()
GameManager.game_state = RpgGameState.TOWN_EXPLORE6.6 本章小结
| 知识点 | 说明 |
|---|---|
| 物品分类 | 消耗品、武器、防具、关键道具四种类型 |
| 物品数据库 | 集中定义所有物品的属性和效果 |
| 背包管理 | 添加、删除、查找、使用、排序 |
| 堆叠机制 | 可堆叠物品自动合并到已有格子 |
| 装备系统 | 穿戴装备获得属性加成,旧装备自动放回背包 |
| 商店系统 | 买入扣金币、卖出获半价、关键道具不可卖 |
下一章我们将实现剧情与任务系统,给游戏世界注入灵魂。
