时间: 2026年4月中旬 - 4月下旬

在项目框架搭建完成并成功验证大模型API接入后,我们FateWeaver团队进入了功能并行开发阶段。经过小组讨论,我们决定每人独立负责一个完整的功能模块(前后端+数据库),以验证团队的技术架构是否能够支撑真正的全栈开发。我被分配到的任务是实现游戏内的背包系统,包括物品的获取、使用、丢弃、与角色属性的联动,以及数据的持久化存储。

目录

一、分工背景与初始状态分析

二、从零开始:技术选型与架构设计

2.1 数据库设计

2.2 后端分层架构

2.3 物品模板的独立管理

三、后端实现:从内存存储到数据库持久化

3.1 数据访问层的实现

3.2 业务逻辑层的实现

3.3 API接口层

四、前端实现:侧边栏与弹窗的联动

4.1 数据结构统一

4.2 侧边栏改造

4.3 弹窗交互实现

五、遇到的关键问题与解决方案

问题1:背包弹窗只显示图标,不显示数量

问题2:创建"流浪剑客"角色后,背包显示了重复物品

问题3:后端模块导入路径反复报错

问题4:加 type="module" 后页面无法运行

六、当前成果与功能演示

后端功能

前端功能

七、技术理解与收获


一、分工背景与初始状态分析

在开始背包功能的开发之前,我先对项目当前状态进行了评估。初步框架虽然能够运行,但存在几个关键问题:

1. 后端数据存储问题

初始框架将游戏会话数据存储在Python内存字典中:

active_sessions = {}  # 存在内存中

这意味着一旦后端重启,所有数据都会丢失。对于背包系统来说,物品的获取和使用必须持久化,否则每次刷新页面背包就空了。

2. 前端数据来源不统一

侧边栏显示的背包内容来自 gameState.inventory(一个字符串数组),而弹窗需要的是包含名称、图标、数量、效果等详细信息的结构化对象。两者没有对应关系。

3. 角色出身物品未同步

角色创建时选择了不同出身(如"流浪剑客"会获得精钢长剑、旅行者斗篷、干粮x5),但这些物品只存在于角色创建阶段,没有传递到背包系统中。

4. 没有独立的物品数据库

物品模板(如武器、药水、材料)硬编码在后端服务代码中,无法灵活扩展,也不便于后续其他模块(如商店系统、任务奖励)复用。


二、从零开始:技术选型与架构设计

面对以上问题,我决定重新设计背包系统的完整架构,确保它能够独立运行并与其他模块(用户系统、存档系统)对接。

2.1 数据库设计

我们团队确定使用SQLite作为开发阶段的数据库。对于背包系统,需要两张表:

item_templates 表:存储所有物品模板

字段类型说明
item_idTEXT物品唯一ID(主键)
nameTEXT物品名称
typeTEXT类型(weapon/consumable/quest/material)
descriptionTEXT物品描述
iconTEXT图标emoji
usableINTEGER是否可使用
effectsTEXT使用效果的JSON数据
story_idTEXT所属故事ID

inventory_items 表:存储每个会话的背包物品

字段类型说明
idINTEGER自增主键
session_idTEXT会话ID
item_idTEXT物品ID
nameTEXT物品名称
quantityINTEGER数量

session_id作为外键关联到游戏会话,这意味着:

  • 每个游戏会话有独立的背包

  • 同一用户的不同存档互不影响

  • 为多用户多存档场景做好了准备

2.2 后端分层架构

参考了实际生产项目的设计模式,我将后端代码分为三层:

api/v1/inventory.py          # API路由层:处理HTTP请求
services/inventory_service.py # 业务逻辑层:物品使用、丢弃规则
models/database/inventory.py  # 数据访问层:数据库CRUD操作

这种分层的优势在于:如果将来需要修改数据库(比如从SQLite换成MySQL),只需要修改数据访问层;如果要修改业务规则(比如增加物品合成功能),只需要修改服务层。各层职责清晰,便于维护。

2.3 物品模板的独立管理

我创建了 db/init_items.py 文件,将所有物品的数据集中管理。以迷雾森林故事为例,定义了武器、消耗品、任务物品、材料、特殊物品等五大类共计20+件物品:

items = [
    ("item_sword_001", "精钢长剑", "weapon", "一把锋利的精钢长剑...", "⚔️", 1, '{"attack":5}', "fantasy_001", ...),
    ("item_potion_001", "治疗药水", "consumable", "恢复30点生命值...", "🧪", 1, '{"hp":30}', "fantasy_001", ...),
    ("item_quest_003", "月光碎片", "quest", "月光凝聚而成的碎片...", "✨", 0, '{"luck":2}', ...),
    # ...20+件物品
]

并且配置了不同出身对应的初始物品:

CHARACTER_STARTING_ITEMS = {
    "bg_001": ["item_sword_001", "item_tool_002", "item_food_003"],  # 流浪剑客
    "bg_002": ["item_quest_002", "item_sword_002", "item_tool_002"],  # 贵族后裔
    "default": ["item_sword_001", "item_food_001", "item_tool_001"],  # 默认
}

三、后端实现:从内存存储到数据库持久化

3.1 数据访问层的实现

在 models/database/inventory.py 中,我实现了 InventoryDB 类,封装了所有的数据库操作:

class InventoryDB:
    @staticmethod
    def get_items(session_id):
        """获取背包所有物品"""
        conn = get_connection()
        cursor = conn.cursor()
        cursor.execute(
            'SELECT * FROM inventory_items WHERE session_id = ? AND quantity > 0',
            (session_id,)
        )
        rows = cursor.fetchall()
        # 转换为字典列表返回
        ...
    
    @staticmethod
    def add_item(session_id, item_data):
        """添加物品,支持同类堆叠"""
        # 先检查是否已有同类可堆叠物品
        cursor.execute(
            'SELECT id, quantity FROM inventory_items WHERE session_id = ? AND item_id = ? AND type IN (?, ?)',
            (session_id, item_id, "consumable", "material")
        )
        existing = cursor.fetchone()
        if existing:
            # 堆叠:增加数量
            new_qty = existing["quantity"] + quantity
            cursor.execute('UPDATE inventory_items SET quantity = ? WHERE id = ?', (new_qty, existing["id"]))
        else:
            # 新增:插入新记录
            ...

这里我学到一个重要的概念:物品堆叠。消耗品和材料类物品是可堆叠的——如果有两个同样的"面包",不是插入两条记录,而是将一条记录的 quantity 字段加1。这在数据库层面通过先查询再更新的方式实现。

3.2 业务逻辑层的实现

在 services/inventory_service.py 中,实现了核心的游戏规则:

  • 使用物品:检查是否可用 → 应用效果 → 减少数量 → 记录系统消息

  • 丢弃物品:检查是否可丢弃(任务物品不可丢弃)→ 确认 → 删除记录

  • 任务物品保护:type为"quest"的物品不允许丢弃

def drop_item(self, session_id, item_id, quantity=1):
    target_item = self._find_item(session_id, item_id)
    if not target_item:
        return {"success": False, "message": "背包中没有该物品"}
    if target_item["type"] == "quest":
        return {"success": False, "message": "任务物品不能丢弃"}
    # 执行删除
    ...

3.3 API接口层

API接口保持与之前相同的格式(以便前端无需大改),但底层从内存字典切换到了数据库:

@router.get("/{session_id}")
async def get_inventory(session_id: str):
    result = inventory_service.get_inventory(session_id)  # 从数据库读取
    return result

@router.post("/use")
async def use_item(request: UseItemRequest):
    result = inventory_service.use_item(
        session_id=request.session_id,
        item_id=request.item_id,
        quantity=request.quantity
    )
    if not result["success"]:
        raise HTTPException(status_code=400, detail=result["message"])
    return result

四、前端实现:侧边栏与弹窗的联动

4.1 数据结构统一

前端需要处理两套数据源:

  • gameState.inventory:游戏引擎返回的字符串数组(如 ["精钢长剑", "面包x3"]

  • inventoryItems:后端返回的结构化对象数组(包含 item_idnameiconquantity 等字段)

我实现了转换函数:

const syncMockInventory = () => {
    const items = gameState.value.inventory || [];
    inventoryItems.value = items.map((itemStr, idx) => {
        const match = itemStr.match(/^(.+?)x(\d+)$/);
        const name = match ? match[1].trim() : itemStr;
        const qty = match ? parseInt(match[2]) : 1;
        return {
            item_id: 'mock_' + idx,
            name: name,
            quantity: qty,
            icon: name.includes('剑') ? '⚔️' : '📦',
            usable: name.includes('药水') || name.includes('面包'),
            // ...
        };
    });
};

4.2 侧边栏改造

侧边栏从读取 gameState.inventory 改为读取 inventoryItems

<div v-for="item in inventoryItems.slice(0, 6)" :key="item.item_id" class="item">
    {{ item.icon || '📦' }} {{ item.name }}
    <span v-if="item.quantity > 1">x{{ item.quantity }}</span>
</div>

这样侧边栏和弹窗读取同一数据源,保持数据一致性。

4.3 弹窗交互实现

弹窗中实现了四个核心交互:

  1. 选中物品:点击后高亮,显示物品详情

  2. 使用物品:调用后端API,消耗品使用后数量减少或消失

  3. 丢弃物品:调用后端API,任务物品不可丢弃

  4. 数量显示:右上角角标显示 x1x3 等


五、遇到的关键问题与解决方案

问题1:背包弹窗只显示图标,不显示数量

现象:弹窗中每个物品只显示图标和名称,右上角的数量角标始终不出现。

排查过程:我在浏览器开发者工具中打印 inventoryItems,发现数组中的每个物品对象都有 quantity 字段,值为1。但弹窗中 v-if="item.quantity > 1" 条件为假,所以角标不显示。

原因分析:初始物品都是从出身配置中来的,每个物品初始数量只有1。弹窗的条件 item.quantity > 1 导致数量为1时角标隐藏。

解决方案:去掉条件限制,让所有物品都显示数量:

<!-- 改前 -->
<div v-if="item.quantity > 1" style="...">x{{ item.quantity }}</div>

<!-- 改后 -->
<div style="...">x{{ item.quantity }}</div>

这样即使只有1个物品,用户也能清楚看到数量。

问题2:创建"流浪剑客"角色后,背包显示了重复物品

现象:选择"流浪剑客"出身(初始物品:精钢长剑、旅行者斗篷、干粮x5),背包里却出现了"长剑、面包x3、精钢长剑、旅行者斗篷、干粮x5",前面多了默认物品。

排查过程:我在 startMockGame 函数中找到问题。mockState.inventory 被设置为默认值 ['长剑', '面包x3'],然后 applyCharacterBonuses 函数又把出身物品 ['精钢长剑', '旅行者斗篷', '干粮x5'] 追加了进去。

原因分析:游戏初始化时,默认物品和出身物品没有互斥。原始代码是为"不使用出身系统"的场景设计的,加入出身系统后产生了冲突。

解决方案:修改逻辑,当角色选择了出身后,用出身物品替换默认物品,而不是追加:

if (characterData?.background?.starting_items) {
    inventory = [...characterData.background.starting_items];  // 用出身物品替换
} else {
    inventory = ['长剑', '面包x3'];  // 无出身时用默认
}

问题3:后端模块导入路径反复报错

现象:运行 python run.py 后,不断出现 ModuleNotFoundError: No module named 'db'No module named 'models' 等错误。

原因分析:我们的后端使用了包结构(backend/ 目录下有多个子目录),每个Python文件中的导入语句需要根据文件位置使用正确的相对路径。最初我在不同文件中混用了绝对导入和相对导入,导致模块找不到。

解决方案:统一使用相对导入,并总结了规律:

导入来源写法
同目录from .session import get_connection
上一级from ..models.database import InventoryDB
上两级from ...db.session import get_connection

同时确保每个子目录都有 __init__.py 文件(即使是空文件),这样Python才会将其识别为包。

问题4:加 type="module" 后页面无法运行

现象:为了让前端使用ES Module导入,我把 <script src="app.js"> 改为 <script type="module" src="app.js">,结果页面完全白屏,控制台报错。

原因分析type="module" 要求服务器环境运行,不能直接双击HTML文件打开。而且模块导入中使用了Vue的CDN全局变量,在模块作用域中无法正确访问。

解决方案:放弃 type="module",将所有背包逻辑直接写在 app.js 的 setup() 函数中,通过 return 暴露给模板使用。这样虽然代码耦合度略高,但在当前CDN引入Vue的架构下是最稳定的方案。


六、当前成果与功能演示

经过多轮修改和调试,背包系统最终实现了以下功能:

后端功能

  • ✅ 物品模板数据库(20+件迷雾森林专属物品)

  • ✅ 背包数据库增删改查

  • ✅ 消耗品使用(效果应用到角色属性)

  • ✅ 任务物品丢弃保护

  • ✅ 物品堆叠机制

  • ✅ 背包容量上限检查

  • ✅ 导出/导入接口(供存档系统使用)

前端功能

  • ✅ 侧边栏实时显示背包物品

  • ✅ 弹窗网格展示物品图标、名称、数量

  • ✅ 选中物品高亮并显示详情

  • ✅ 使用/丢弃按钮交互

  • ✅ 使用物品后属性变化实时反映

  • ✅ 不同出身对应不同初始物品


七、技术理解与收获

通过这个背包系统的完整实现,我获得了以下几点深刻体会:

1. 数据库是基础

最初我想用内存字典快速实现,但很快就遇到了数据丢失、多会话隔离、物品结构化存储等问题。切换到SQLite后发现,很多业务逻辑(如物品堆叠、容量检查)用SQL语句比Python代码更简洁可靠。

2. 导入路径的错误是"必经之路"

之前写单文件脚本时从没遇到过导入问题。但在包结构的项目中,from .xxx 和 from xxx 的区别至关重要。每一次报错都是对Python模块系统理解的加深。

3. 前后端数据格式统一很重要

最初前端用字符串数组(["长剑", "面包x3"]),后端用结构化对象({item_id, name, quantity, icon}),导致两端数据不同步。统一为结构化对象后,代码可维护性大大提高。

4. 任务物品不能丢弃——这条规则的价值

看似简单的一条规则,实际上反映了游戏设计的核心:某些物品是推动剧情的关键,玩家不能随意丢弃。这让我开始思考后续更多的物品类型和交互规则。

5. 增量开发 + 及时测试

每次只修改一个功能点(比如先实现"使用物品",再实现"丢弃物品"),修改完立即用Swagger或浏览器测试。这样即使出错也能快速定位问题所在。


背包系统目前已经能够独立运行并与数据库交互。接下来需要等待其他成员完成用户系统、存档系统后,进行跨模块联调。同时,我还需要补充"物品给予NPC"等更复杂的交互功能,以满足任务书中的要求。

从最初的内存字典到数据库持久化,从假数据到真实物品,这个功能模块的实现让我完整经历了一次前后端全栈开发的过程,也为后续的协作开发积累了宝贵的经验。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐