34 KiB
34 KiB
Tasks: Port ROK Hero & Bag System
交付策略:P1-P3 服务端业务先行,用 GM 命令 + 日志验证,不写 UI。所有 YIUI 面板集中在 P4 一次性补齐。每个新 RPC Handler MUST 同时新增对应 GM 命令。
复用原则(详见
Doc/ET-Packages-Audit.md):38 个 ET 包已覆盖 GM / 日志 / BSON / 协议 / 资源热更 / UI / 网络。本变更严格遵守"能复用不重写":日志扩展Log类、GM 直接用[GM]/[ConsoleHandler]属性、BSON 复用MongoHelper、登录扩展C2G_LoginGateHandler。仅以下能力新建业务包:db/logging/config/hero/bag/gacha。
1. P0 - 基础设施准备
1.1 客户端编译产物
- 1.1.1 在 Unity 中执行
ET/Loader/Compile(F6) 生成客户端热更 DLL - 1.1.2 检查
Packages/cn.etetet.loader/Bundles/Code/下存在ET.Model.dll.bytes/ET.Hotfix.dll.bytes/ET.ModelView.dll.bytes/ET.HotfixView.dll.bytes - 1.1.3 执行
ET/HybridCLR/Init初始化 AOT 元数据(如未执行过)
1.2 服务端编译产物
- 1.2.1 执行
dotnet build "My project/ET.sln" -c Debug编译服务端 - 1.2.2 检查
My project/Bin/ET.App.dll存在 - 1.2.3 编写
tools/build-server.ps1一键编译脚本
1.3 跑通 Login Demo
- 1.3.1 在 Unity Build Settings 中添加
Packages/cn.etetet.loader/Scenes/Init.unity - 1.3.2 启动 ET Server (单进程模式):
ET/Loader/ServerTools→ "Start Server" - 1.3.3 启动 Unity Play,验证
C2R_Login→R2C_Login→C2G_LoginGate→G2C_LoginGate链路打通 - 1.3.4 验证 Gate Scene 创建了
PlayerEntity 并关联Session
1.4 DBComponent 业务封装(基于现成 MongoHelper + MongoDB.Driver)
- 1.4.1 复用确认:
cn.etetet.core/MongoHelper已有 BSON 序列化、com.etetet.init/Plugins/MongoDB已有原生 DLL,仅缺业务层封装 - 1.4.2 新建
Packages/cn.etetet.db/包目录与 package.json(依赖 cn.etetet.core) - 1.4.3 定义
IDBComponent接口(Save/Query/QueryByEntityId/Update/Delete) - 1.4.4 实现
MongoDBComponent:用IMongoClient直连 +MongoHelper.ToBson/FromBson序列化;Collection 命名约定 =typeof(T).Name - 1.4.5 实现
MemoryDBComponent(Dictionary<long, byte[]> + MongoHelper.Clone)开发期默认 - 1.4.6 在 Gate Scene 启动时根据
StartConfig.DBConnection二选一挂载 - 1.4.7 实现
DBSaveComponent(脏标记 + 30 秒定时批量 Save + Player 下线强制 Flush) - 1.4.8 添加
Entity.SetDirty()扩展方法(写入 dirty set) - 1.4.9 单元测试:save → query → update → delete 闭环(内存模式 + Mongo 模式各一遍)
1.5 Player 数据加载(扩展现有 cn.etetet.login,不重写)
- 1.5.1 复用确认:
C2G_LoginGateHandler已有,目前只创建内存 Player;扩展点 = 在await CreatePlayer(...)后插入 DB 加载分支 - 1.5.2 在
cn.etetet.login/Hotfix/Server/Gate/C2G_LoginGateHandler.cs加入await DBHelper.LoadOrCreatePlayer(player, dbComponent)调用(约 5-10 行) - 1.5.3 新建
Packages/cn.etetet.config/Datas/InitialResource.xlsx(首次登录发放 RewardEntry 列表) - 1.5.4 实现
DBHelper.LoadOrCreatePlayer:DB 查 → 有则反序列化挂 Hero/Bag/Gacha Component;无则新建并按 InitialResource 配置调BagComponentSystem.AddItems - 1.5.5 注册 Session Dispose 事件(用 ET 现有 EventSystem),玩家离线时
DBSaveComponent.FlushNow(playerId) - 1.5.6 验证:登录 → DB 中存在 → 重启服务端 → 二次登录数据完整恢复
1.6 协议工具链
- 1.6.1 验证
ET/Proto/Proto2CS工具可正常运行 - 1.6.2 在
Packages/cn.etetet.proto/Proto/新建占位HeroOuter_C_3000.proto(仅含 1 个 ping 消息测试) - 1.6.3 运行 Proto2CS 验证代码生成到
CodeMode/Model/{Client|Server|ClientServer}/HeroOuter_C_3000.cs - 1.6.4 添加新消息后客户端能正常发送、服务端 Handler 能接收
- 1.6.5 给所有 C2G/C2R 消息基类加入
reqId字段(uuid 字符串,便于日志全链路追踪)
1.7 Luban 配置工具链(替代 ExcelExporter)
- 1.7.1 下载 luban 工具(dotnet luban),放到
tools/luban/ - 1.7.2 新建
Config/Defines/Root.bean.xml+Config/Datas/目录结构 - 1.7.3 编写
tools/luban/gen.ps1一键导出脚本(生成 C# 到Packages/cn.etetet.config/Scripts/Model/Share/cfg/,json 到Bundles/Config/) - 1.7.4 写一张示例表
ItemConfig.xlsx+Item.bean.xml(含 itemId / itemType / name / icon / useType / maxStack / quality / desc / 通用 RewardEntry 字段示例) - 1.7.5 跑通 gen.ps1 → 生成代码 + json 文件
- 1.7.6 在 YooAsset
AssetBundleCollectorSetting加Bundles/Config/收集规则 - 1.7.7 新建
Packages/cn.etetet.config/包,实现ConfigComponent挂在 Scene 上加载 Luban Tables - 1.7.8 实现
SceneExtensions.Cfg()扩展方法 - 1.7.9 服务端 + 客户端 都能调
scene.Cfg().TbItem.Get(1)查到金币配置 - 1.7.10 编写 Luban 使用文档
Doc/Luban-Guide.md
1.8 日志扩展(基于现成 Log + NLog,仅加扩展方法)
- 1.8.1 复用确认:
cn.etetet.core/Log.cs已有Log.Debug/Info/Warning/Error/Trace;cn.etetet.loader已集成 NLog 后端;不重写不替换 - 1.8.2 新建
Packages/cn.etetet.logging/包(依赖 cn.etetet.core + cn.etetet.loader) - 1.8.3 在新包内加扩展方法:
Log.Audit(module, action, payload)/Log.Slow(module, action, durationMs, ctx)/Log.Module(tag, level, msg)(全部走 ET 现有 Log 入口,仅做格式化前缀) - 1.8.4 提供
NLog.config.template:3 个 target 分别输出Logs/Business//Logs/Audit/{module}//Logs/Error/(jsonl 行格式)+ rolling by day - 1.8.5 提供
LoggingInstaller:服务端启动时合并 template 到现有Packages/cn.etetet.loader/Scripts/Loader/Server/NLog.config - 1.8.6 实现
Log.Audit同步落盘(NLog<target type="AsyncWrapper">关闭 +KeepFileOpen="true"强制 flush) - 1.8.7 约定模块标签前缀:
[Hero][Bag][Gacha][Equip][DB][Login][GM][Net](仅写文档约定,不做编译期校验) - 1.8.8 实现 RPC Handler 基类
RpcMessageHandler<TReq,TResp>:在 OnRun 前后自动Log.Info($"[{module}] {action} in: ..." )+ 耗时计算(业务 Handler 改继承即可,老 Handler 不强制) - 1.8.9 实现 reqId 透传:在 C2G/C2R proto 基类加
reqId字段(任务 1.6.5 已包含)+ Handler 基类把 reqId 放到AsyncLocal<string>,扩展Log.Info自动拼前缀 - 1.8.10 实现慢操作告警:包装
IDBComponent入口 +MessageDispatcher,超阈值(DB 查询 100ms / 写入 200ms / Handler 500ms)调Log.Slow - 1.8.11 实现错误码降频:同 playerId 同 errorCode 1 分钟内仅首条 + 计数(用
MemoryCache1 分钟 TTL) - 1.8.12 用 1.9 注册
gm SetLogLevel <module> <level>运行时调级(写NLogManager.Configuration.LoggingRules) - 1.8.13 实现敏感字段脱敏(配置化 MaskFields,proto attribute 标记或 Json 路径配置)
- 1.8.14 实现客户端日志桥接
C2G_ClientLogReport+ 服务端Logs/Client/{date}.log(默认开发期开启,Release 关闭) - 1.8.15 日志单测:扩展方法格式正确 / 模块过滤 / 同步落盘验证
1.9 GM 业务命令接入(完全复用 cn.etetet.yiuigm + cn.etetet.console,零框架开发)
- 1.9.1 复用确认:
cn.etetet.yiuigm:已有[GM]属性 +IGMCommand接口 + 完整 GMPanel UI(参数 6 类型)cn.etetet.console:已有[ConsoleHandler]属性 +IConsoleHandler接口 + stdin REPL- 不新建 GM 框架,仅按这两个属性写业务 GM 类
- 1.9.2 新建文档
Doc/GM-Commands.md:约定服务端 console 用cn.etetet.console、客户端 panel 用cn.etetet.yiuigm、共享业务实现走GMHelper静态类 - 1.9.3 新建
cn.etetet.gachainvoke/cn.etetet.heroinvoke/cn.etetet.baginvoke跨包调用契约(用cn.etetet.yiuiinvoke),让 GM 类不需要直接依赖业务包 - 1.9.4 在
cn.etetet.logging提供Log.AuditGM(operator, action, targetPlayer, args, success)专用方法,所有 GM 业务类调用前自动审计 - 1.9.5 用
#if ENABLE_GM包裹所有[GM]业务类(参考cn.etetet.yiuigm/Scripts/HotfixView/Client/GM/GM_Command_Test.cs已有的条件编译模式) - 1.9.6 编写 CI 校验脚本
tools/ci/check-release-no-gm.ps1:Release IL 不含EGMType字符串 - 1.9.7 编写 CI 校验脚本
tools/ci/check-rpc-has-gm.ps1:扫描每个*Handler.cs,确认对应GM_*.cs或[ConsoleHandler]类存在
1.10 错误码体系
- 1.10.1 新建
HeroBagErrorCode.cs集中定义所有 Hero/Bag/Equipment/Gacha 错误码(数值段位约定:3xxxx Hero、4xxxx Bag、5xxxx Equip、6xxxx Gacha) - 1.10.2 客户端添加
ErrorMessageHelper.GetMessage(int errorCode)本地化文案;展示走cn.etetet.yiuitips的TipsHelper.OpenSync<TipsTextViewComponent>(scene, ErrorMessageHelper.GetMessage(code)),禁止自写 Toast - 1.10.3 服务端 Handler 返回错误码时自动写 L3 错误日志
1.11 通用 Item 基础设施(整个系统的基础)
- 1.11.1 在 Luban
Common.bean.xml定义RewardEntry { itemId, count }/CostEntry { itemId, count } - 1.11.2 在 proto
Common_3000.proto定义同名 message(与 Luban 一致) - 1.11.3 在
BagComponentSystem上预留AddItems(rewards)/RemoveItems(costs)/GetItemCount(itemId)/HasItems(costs)接口签名 - 1.11.4 在 Luban
ItemConfig中提前定义货币 itemId:1=金币、2=钻石、3=PvP币(写入 Datas/ItemConfig.xlsx 首批条目) - 1.11.5 写设计文档约定:全游戏所有可获得资源必须用 itemId 表示,禁止在 Player Entity 上加货币字段
1.12 GM Web Console(策划/产品可见)— 基于 console + http 包,最小化新增
- 1.12.1 复用确认:服务端 stdin REPL 已现成(
cn.etetet.console),cn.etetet.http已现成轻量 HTTP Server - 1.12.2 新建
tools/gm-web/index.html(input + fetch + 命令历史)作为静态资源 - 1.12.3 实现
[HttpHandler("/gm")] HttpGMHandler:转发 POST body 到ConsoleComponentSystem命令分发器(直接复用现有 console 命令注册) - 1.12.4 简单密码校验(StartConfig 配
GMWebPassword) - 1.12.5 默认仅 Debug 包加载,Release 不监听端口
1.13 ROK 配置迁移工具链(新任务:取代手工录入)
- 1.13.1 背景沉淀:在
Doc/ROK-Config-Source.md记录数据源真相:ROK 服务端只有Configs.data(Lua 表,tabtoy 生成);ROK 客户端有加密 SQLite + 205 个*Define.csDTO;无 Excel 源文件 - 1.13.2 拉取姐妹工程
E:/Game/gmd/Tools/export_hero_json_from_configs_data.py作为基础(已验证可解析 Hero/HeroLevel/HeroStar) - 1.13.3 在
tools/rok-migrate/export.py扩展支持本次需要的表(Hero/HeroLevel/HeroStar/HeroSkill/HeroTalent/Item/Equip/GachaPool/GachaWeight 等) - 1.13.4 在
tools/rok-migrate/define_to_bean.py写 Roslyn-free 文本扫描器:读ROK/Client/.../*Define.cs→ 提取字段名 + 类型 → 输出 Luban*.bean.xml - 1.13.5 在
tools/rok-migrate/json_to_luban_xlsx.py写 JSON → Luban*.xlsx转换器(首行类型 / 二行字段名 / 三行注释 / 数据行) - 1.13.6 一键脚本
tools/rok-migrate/run.ps1:bean.xml + xlsx 一次出齐到Config/Defines/和Config/Datas/ - 1.13.7 跑通 Hero + Item 两张表端到端:
run.ps1→tools/luban/gen.ps1→ 服务端scene.Cfg().TbHero.Get(10001)拿到 ROK 真实数据 - 1.13.8 文档化:每张表的 ROK 源字段 ↔ Luban bean 字段映射表
2. P1 - 抽卡 + 通用 Item + 英雄基础(纯服务端 + GM 验证,零 UI)
执行顺序调整(2026-05-27 用户确认):先跳过 2.0 抽卡,优先做 2.1+ 英雄/背包;抽卡并入“酒馆(Tavern)发奖”语义后再回填 2.0。
2.0 抽卡系统(已延期:并入酒馆 Tavern 语义后再落地)
2.0.1 抽卡 Luban 配置
- 2.0.1.1 在
Config/Defines/Gacha.bean.xml定义GachaPoolbean(id / name / singleCost / multiCost / guaranteeCount / guaranteeRare / multiGuaranteeRare / openTime / closeTime) - 2.0.1.2 定义
GachaWeightItembean(poolId / entryIndex / reward: RewardEntry / weight / rare) - 2.0.1.3 创建
Config/Datas/GachaPool.xlsx+GachaWeight.xlsx,填充测试卡池:1 个普池(消耗金币)+ 1 个高级池(消耗钻石) - 2.0.1.4 跑 gen.ps1 验证代码生成 + json 输出
2.0.2 抽卡协议
- 2.0.2.1 创建
Packages/cn.etetet.gacha/Proto/GachaOuter_C_3200.proto - 2.0.2.2 定义 DTO:
GachaPoolStats { poolId, totalCount, guaranteeRemain } - 2.0.2.3 定义 RPC:
C2G_GachaDraw { poolId, drawType }/R2C_GachaDraw { error, rewards: repeated RewardEntry, isNewHero: repeated bool } - 2.0.2.4 定义推送:
Gacha_GachaInfo { stats: repeated GachaPoolStats } - 2.0.2.5 运行 Proto2CS 生成代码
2.0.3 服务端 GachaComponent
- 2.0.3.1 创建
cn.etetet.gacha/Scripts/Model/Server/GachaComponent(挂在 Player 下) - 2.0.3.2 含
Dictionary<int, GachaPoolStats> PoolStats,标记[MongoElement]持久化 - 2.0.3.3 实现
GachaComponentSystem.Draw(poolId, drawType)完整算法(消耗→随机→保底→发奖→更新统计→审计日志) - 2.0.3.4 实现
UpdateGuarantee(poolId, rare)保底计数更新 - 2.0.3.5 实现
ReplaceWithGuaranteed(rewards, weights, minRare)强制保底 - 2.0.3.6 实现
RandomNumberGenerator工具类(CSPRNGSystem.Security.Cryptography.RandomNumberGenerator),禁止使用 UnityEngine.Random - 2.0.3.7 抽卡过程中严格走
BagComponentSystem.RemoveItems扣消耗 +AddItems发奖励,禁止绕过
2.0.4 服务端 Handler + 日志
- 2.0.4.1
C2G_GachaDrawHandler:继承自带埋点的 RpcMessageHandler 基类(入口/出口自动 L1 日志) - 2.0.4.2 抽卡完成后写 L2 审计日志:playerId / poolId / drawType / cost / rewards / guaranteeRemain / reqId
- 2.0.4.3 推送 R2C_GachaDraw + Gacha_GachaInfo
- 2.0.4.4 接口限速:单玩家 100ms 最多 1 次请求,超过返回 ERR_RATE_LIMIT 并写 L3
- 2.0.4.5 服务端单元测试:模拟 10000 次抽卡,验证概率分布与配置误差 < 1%
- 2.0.4.6 服务端单元测试:连续 90 抽 1 次不出保底品质 → 第 90 次必出
- 2.0.4.7 服务端单元测试:自然抽到保底品质 → 计数重置
2.0.5 客户端 GachaComponent
- 2.0.5.1 创建
cn.etetet.gacha/Scripts/Model/Client/GachaComponent - 2.0.5.2 实现
Gacha_GachaInfoHandler同步保底进度 - 2.0.5.3 发布
GachaUpdatedEvent
2.0.6 抽卡 GM 命令(P1 唯一验证手段)
- 2.0.6.1
gm Gacha <poolId> <times>触发抽卡(默认 1 次,可指定 N 次) - 2.0.6.2
gm SimulateGacha <poolId> <times>服务端模拟 N 次抽卡(不修改真实玩家数据),输出概率统计 - 2.0.6.3
gm ShowGachaStats [playerId]输出玩家所有卡池的累计/保底进度 - 2.0.6.4
gm ResetGachaGuarantee <poolId> [playerId]重置保底计数 - 2.0.6.5
gm SetGachaCount <poolId> <count> [playerId]直接设置抽数(测试用)
2.0.7 抽卡验收(GM + 日志)
- 2.0.7.1
gm AddItem 2 100→gm Gacha 1 1→gm ShowBag→ 钻石 -1,奖励入包 - 2.0.7.2
gm Gacha 1 10→ 收到 10 个奖励,钻石按十连消耗扣除 - 2.0.7.3
gm SetGachaCount 1 89→gm Gacha 1 1必出保底品质(查审计日志确认) - 2.0.7.4 自然抽到保底品质 →
gm ShowGachaStats显示保底计数重置为 0 - 2.0.7.5 不同卡池保底独立:抽卡池 A 不影响卡池 B 进度
- 2.0.7.6
gm SimulateGacha 1 10000概率与配置误差 < 1% - 2.0.7.7
gm Snapshot before→gm Gacha 1 10→ 重启 →gm DumpPlayer对比 → 数据完整恢复 - 2.0.7.8 篡改 reqId / 客户端伪造字段 → 日志记录但服务端结果不受影响
- 2.0.7.9 端到端验证:
gm Gacha 1 100→ 抽到英雄碎片 →gm SummonHero→ 招募成功
2.1 英雄 Luban 配置
- 2.1.1 在
Config/Defines/Hero.bean.xml定义Herobean(含 List skillIds、List talentIds、fragmentItemId、recruitLimit 等) - 2.1.2 定义
HeroLevel/HeroSkill/HeroSkillEffect/HeroSkillLevelbean - 2.1.3 在 bean 中使用
List<RewardEntry>/List<CostEntry>表达消耗与奖励 - 2.1.4 填充测试数据:每张表 5 个条目
- 2.1.5 跑 gen.ps1 验证客户端/服务端读到
scene.Cfg().TbHero.Get(heroId)
2.2 英雄协议
- 2.2.1 创建
Packages/cn.etetet.hero/Proto/HeroOuter_C_3000.proto - 2.2.2 定义 DTO:
HeroInfo、SkillInfo - 2.2.3 定义 RPC:
C2G_SummonHero/C2G_HeroAddExp/C2G_HeroSkillUp及响应 - 2.2.4 定义推送:
Hero_HeroInfo、Hero_HeroInfoList - 2.2.5 运行 Proto2CS
2.3 服务端 Hero 实体与持久化
- 2.3.1 创建
HeroEntity(含 level/exp/star/starExp/skills/talents) - 2.3.2 创建
HeroComponent(挂在 Player 下) - 2.3.3 实现 Awake/Destroy + AddHero / RemoveHero / GetHero / GetAllHeroes
- 2.3.4 给所有持久化字段加
[MongoElement] - 2.3.5 验证:登录后服务端能加载玩家所有 Hero 实体
2.4 服务端业务 Handler + 日志
- 2.4.1
C2G_SummonHeroHandler:校验碎片(BagComponentSystem.HasItems)+ 不重复 + 上限 → AddHero +BagComponentSystem.RemoveItems扣碎片 - 2.4.2
C2G_HeroAddExpHandler:校验道具 → 累加经验 → 触发升级 → 受星级上限约束 - 2.4.3
C2G_HeroSkillUpHandler:校验道具 → 技能等级 +1 → 受 maxLevel 约束 - 2.4.4
HeroNoticeHelper.NotifyHeroUpdate(player, hero)封装推送 - 2.4.5 登录后自动推送
Hero_HeroInfoList - 2.4.6 所有 Handler 走 RpcMessageHandler 基类自动埋点
- 2.4.7 关键操作(招募、首次满级、首次满技能)写 L2 审计
2.5 客户端 Hero 缓存
- 2.5.1 创建客户端
HeroComponent缓存 - 2.5.2 实现
Hero_HeroInfoHandler增量更新 /Hero_HeroInfoListHandler全量同步 - 2.5.3 发布
HeroUpdatedEvent/HeroListUpdatedEvent
2.6 战力计算(客户端 + 服务端共享)
- 2.6.1 在
cn.etetet.hero/Scripts/Model/Share/HeroPowerCalculator.cs实现 GetBaseScore / GetLevelScore / GetSkillScore / GetPower - 2.6.2 单元测试:相同数据 → 客户端与服务端算出相同战力
2.7 英雄 GM 命令(P1 唯一验证手段)
- 2.7.1
gm AddHero <heroId>直接创建英雄 - 2.7.2
gm SetHeroLevel <heroId> <level>设置等级 - 2.7.3
gm SetHeroSkill <heroId> <skillIndex> <level>设置技能等级 - 2.7.4
gm ShowHeroes [playerId]输出 jsonl - 2.7.5
gm SummonHero <heroId>走完整招募流程(验证碎片消耗逻辑) - 2.7.6
gm HeroAddExp <heroId> <itemId> <count>走完整升级流程
2.8 P1 验收(GM + 日志,零 UI)
- 2.8.1
gm AddItem <碎片itemId> 30→gm SummonHero <heroId>→ 招募成功 + 碎片扣 30 - 2.8.2
gm HeroAddExp <heroId> <expBookId> 100→ 等级提升 + 经验书减少 - 2.8.3 升级技能 → 技能等级 +1 → 战力变化(用
gm ShowHeroes对比) - 2.8.4 重启客户端 + 服务端 →
gm DumpPlayer输出对比 → 数据完整恢复 - 2.8.5 审计日志:
tail Logs/Audit/Hero/2026-*.jsonl含完整招募/升级记录 - 2.8.6 错误日志:尝试用 0 碎片招募 → L3 日志含错误码 + reqId
3. P2 - 背包与装备穿戴(服务端 + GM 验证,零 UI)
3.1 背包/装备 Luban 配置
- 3.1.1 完善
Config/Defines/Item.bean.xml(含 BagItemType 枚举、useType 枚举) - 3.1.2 定义
Equipbean(含 List 属性、equipItemType 枚举) - 3.1.3 填充约 30 个测试道具 + 10 个测试装备到 xlsx
- 3.1.4 跑 gen.ps1 验证
3.2 背包/装备协议
- 3.2.1 创建
Packages/cn.etetet.bag/Proto/BagOuter_C_3100.proto - 3.2.2 定义 DTO:
ItemInfo、EquipSlot - 3.2.3 定义 RPC:
C2G_UseItem/C2G_HeroWearEquip/C2G_TakeOffEquip - 3.2.4 定义推送:
Item_ItemInfo、Item_ItemInfoList、Item_RemoveItem - 3.2.5 运行 Proto2CS
3.3 服务端 BagComponent(通用 Item 入口)
- 3.3.1 创建
cn.etetet.bag/Scripts/Model/Server/BagComponent - 3.3.2 创建
ItemUnit子 Entity(含 itemIndex / itemId / count / heroId) - 3.3.3 实现
AddItem(itemId, count)- 自动堆叠(非装备类)/ 新建条目(装备类) - 3.3.4 实现
RemoveItem(itemId, count)与RemoveItemByIndex(itemIndex) - 3.3.5 实现
SetHeroIdOnEquip(itemIndex, heroId) - 3.3.6 实现
AddItems(List<RewardEntry>)/RemoveItems(List<CostEntry>)/HasItems(List<CostEntry>)批量接口(事务性:任一失败全失败) - 3.3.7 实现
GetItemCount(itemId)通用查询 - 3.3.8 所有变更通过
BagNoticeHelper.NotifyItemChange推送 - 3.3.9 资源大额变动(>1000)写 L2 审计日志
- 3.3.10 玩家首次登录通过
InitialResourceConfig调AddItems发初始金币/钻石(走通用接口)
3.4 服务端业务 Handler
- 3.4.1
C2G_UseItemHandler:通用 dispatcher,按 ItemConfig.useType 分发 - 3.4.2
C2G_HeroWearEquipHandler:校验装备 + 卸旧穿新 + 同步 Hero + Item - 3.4.3
C2G_TakeOffEquipHandler:清空槽位 + 装备 heroId = 0 - 3.4.4 登录后推送
Item_ItemInfoList
3.5 客户端 BagComponent
- 3.5.1 创建客户端
BagComponent(按 6 大类分类缓存) - 3.5.2 实现
Item_ItemInfoHandler/Item_ItemInfoListHandler - 3.5.3 实现新道具标记本地存储(OctoberStudio SaveManager 集成)
- 3.5.4 发布
ItemChangedEvent/BagUpdatedEvent
3.6 背包/装备 GM 命令
- 3.6.1
gm AddItem <itemId> <count>通用发放(已在 P0/P1 准备) - 3.6.2
gm RemoveItem <itemId> <count>通用扣除 - 3.6.3
gm ShowBag [playerId]输出 jsonl - 3.6.4
gm BagClear [playerId]清空背包(保留绑定装备) - 3.6.5
gm HeroWearEquip <heroId> <itemIndex>穿戴 - 3.6.6
gm HeroTakeOffEquip <heroId> <slotIndex>卸下 - 3.6.7
gm UseItem <itemIndex> [count]使用道具
3.7 P2 验收(GM + 日志)
- 3.7.1
gm AddItem <装备itemId> 1→gm ShowBag出现 → 查属性 - 3.7.2
gm HeroWearEquip→gm ShowHeroes看战力变化 - 3.7.3
gm HeroTakeOffEquip→ 装备回背包未绑定状态 - 3.7.4 切换英雄穿同一装备 → 自动从原英雄卸下
- 3.7.5 重启验证装备穿戴关系持久化
- 3.7.6 通用 Item 测试:金币、钻石、碎片、装备、材料都用
gm AddItem发放,全部走 BagComponent.AddItems
4. P3 - 高级养成(服务端 + GM 验证,零 UI)
4.1 升星系统
- 4.1.1 Luban 配置
HeroStar/HeroStarExpbean - 4.1.2 proto
C2G_HeroStarUp/R2C_HeroStarUp - 4.1.3 服务端 Handler:扣升星材料 + 幸运计算 + 星级提升
- 4.1.4 GM 命令:
gm HeroStarUp <heroId>走完整流程,gm SetHeroStar <heroId> <star>直接设置 - 4.1.5 单元测试:连续升星,验证幸运系数概率分布
4.2 觉醒系统
- 4.2.1 给 Hero Entity 增加
IsAwakening字段 - 4.2.2 proto
C2G_HeroAwake - 4.2.3 服务端 Handler:校验前 4 技能满级 + 扣觉醒材料 + 解锁第 5 技能
- 4.2.4 GM 命令:
gm HeroAwake <heroId>/gm ForceAwake <heroId>(跳过校验,测试用)
4.3 天赋系统
- 4.3.1 Luban 配置
HeroTalent/HeroTalentTree/HeroTalentMasterybean - 4.3.2 给 Hero Entity 增加
TalentIndex+ 3 套TalentTreeData - 4.3.3 proto
C2G_TalentUp/C2G_ChangeTalentIndex/C2G_ResetTalent - 4.3.4 服务端 Handler:扣点数 + 节点等级 +1 + 前置依赖校验
- 4.3.5 GM 命令:
gm TalentUp <heroId> <nodeId>/gm TalentReset <heroId>/gm ChangeTalentIndex <heroId> <index>
4.4 装备锻造
- 4.4.1 Luban 配置扩展:
Equipbean 增加List<CostEntry> requireMaterials/List<RewardEntry> resolveRewards - 4.4.2 proto
C2G_MakeEquipment/C2G_ResolveEquipment/C2G_MixMaterial - 4.4.3 服务端 Handler:扣材料 + 创建装备 / 分解装备 + 返还
- 4.4.4 GM 命令:
gm MakeEquip <equipId>/gm ResolveEquip <itemIndex>/gm MixMaterial <materialId>
4.5 P3 验收
- 4.5.1
gm HeroStarUp重复使英雄升至 5 星 - 4.5.2 前 4 技能升满 →
gm HeroAwake成功 → 第 5 技能可用 - 4.5.3
gm TalentUp学习天赋 →gm ChangeTalentIndex切换天赋页 →gm TalentReset重置 - 4.5.4
gm MakeEquip用图纸 + 材料锻造装备 → 装备出现在背包 - 4.5.5
gm ResolveEquip分解装备 → 材料返还 - 4.5.6 全流程审计日志完整:每个操作都有对应 jsonl 记录
5. P4 - UI 集中实现(深度复用 YIUI 全套子包,零造轮)
前提:P0-P3 服务端业务已稳定,所有协议、错误码、推送已定型。
强制约束(详见
Doc/ET-Packages-Audit.md中 "YIUI 框架" 一节):
- 面板打开/关闭只用
scene.YIUIRoot().OpenPanelAsync<T>(),禁止自建 PanelHelper- 长列表只用
YIUILoopScrollChild.SetDataRefresh,禁止自写 ScrollRect 池化- 按钮事件只用
UIEventBind*+[YIUIInvoke],禁止Button.onClick.AddListener- 数据 → UI 刷新只用
u_Data*.SetValue+UIDataBind*,禁止手写 Refresh 样板- 弹窗只用
TipsHelper.OpenWait/OpenSync,禁止自写 ConfirmDialog/Toast- 红点只在叶子节点
SetCount,父节点 DAG 自动汇总
5.1 YIUI 通用组件准备
- 5.1.1 复用现有
CommonHeader+YIUICloseCommon(不再造顶栏/关闭) - 5.1.2 创建
BagItemUICommon.prefab:图标用UIDataBindImage+u_DataIcon、数量用UIDataBindText+u_DataCount、品质边框用UIDataBindSprite+u_DataQualityFrame、新道具标记用UIDataBindActive+u_DataNew、不可用用UIDataBindGray+u_DataEnabled(cn.etetet.yiuieffect) - 5.1.3
BagItemUICommonComponentSystem.Show(itemConfigId, count, options)入口:内部全走u_Data*.SetValue,禁止手写Image.sprite = xxx - 5.1.4 多尺寸预设:
BagItemUICommon_Small/Mid/Large.prefab三个变体(仅 RectTransform 不同),共享同一 Component 代码 - 5.1.5 点击事件统一发布
BagItemClickedEvent(ET EventSystem)让宿主 Panel 处理
5.2 抽卡 UI
- 5.2.1 用 YIUI 自动化工具生成
GachaPanelComponent + System - 5.2.2 Prefab 用
UIEventBindClick+[YIUIInvoke]接线(单抽/十连/切池按钮);按钮可用性用UIDataBindGray+u_DataCanDraw(钻石不足自动灰显) - 5.2.3 保底进度用
UIDataBindSlider+u_DataGuaranteeProgress+UIDataBindText+u_DataGuaranteeText - 5.2.4 卡池列表用
cn.etetet.yiuiloopscrollrectasync的YIUILoopScrollChild.SetDataRefresh(poolList) - 5.2.5 RPC 期间挡点击:
scene.YIUIMgr().BanLayerOptionForever()开 → 收响应 close 关 - 5.2.6 抽卡 CD 用
CountDownMgr(cn.etetet.yiuiframework),禁止自建 Timer - 5.2.7
GachaResultPanel:10 个奖励槽用BagItemUICommon渲染;新英雄高亮用UIEffect发光 +UIParticle粒子(cn.etetet.yiuieffect) - 5.2.8 "确认消耗"用
TipsHelper.OpenWait<TipsMessageViewComponent>(scene, "消耗 X 钻石?")模态等待 - 5.2.9 抽卡完成飘字用
TipsHelper.OpenSync<TipsTextViewComponent>(scene, "招募成功")
5.3 英雄 UI
- 5.3.1
HeroListPanel长列表用YIUILoopScrollChild.SetDataRefresh(heroList)+ReRenderer增量刷新 - 5.3.2 三段分组(已拥有 / 可召唤 / 未集齐):在数据层 IList 中插入 Header Item,不改 Loop 框架
- 5.3.3
HeroListItem字段(等级/星级/战力/碎片数)全用u_Data*+UIDataBind* - 5.3.4 SortType 切换:
UIEventBindToggleGroup+ 重排 IList →Loop.ReRenderer() - 5.3.5
HeroDetailPanel:3D 模型用YIUI3DDisplayChild.ShowAsync("HeroModel_10001")(cn.etetet.yiui3ddisplay),禁止自建 RT/Camera - 5.3.6 升级/升星/技能/觉醒 按钮 →
UIEventBindClick+[YIUIInvoke]→ RPC - 5.3.7 召唤按钮带"消耗 N 碎片"模态:
TipsHelper.OpenWait
5.4 背包 UI
- 5.4.1
BagPanel6 Tab +YIUILoopScrollChild(每 Tab 复用同一 Loop,切 Tab 仅换 IList) - 5.4.2 道具 Tooltip:自建
BagItemTooltipView(tips 包无现成 Tooltip View),容器走TipsHelper.Open<BagItemTooltipViewComponent>(scene, itemId)承载 - 5.4.3 Tab 切换:
UIEventBindToggleGroup自动响应 +Loop.SetDataRefresh(filteredList) - 5.4.4 点击 Item → 发布
BagItemClickedEvent→ Panel 弹 Tooltip → "使用 / 穿戴"按钮走 RPC - 5.4.5 操作确认用
TipsHelper.OpenWait;操作结果飘字用TipsHelper.OpenSync
5.5 装备穿戴 UI
- 5.5.1
HeroDetailPanel8 装备槽:每个槽 =BagItemUICommon+UIEventBindClick - 5.5.2
EquipSelectPanel:YIUILoopScrollChild列表 + 按 SubType 过滤 IList - 5.5.3 点击空槽位 →
OpenPanelParamAsync<EquipSelectPanelComponent>(slotIndex, subType) - 5.5.4 点击已穿 →
TipsHelper.OpenWait"卸下 / 替换 / 取消"三选一
5.6 天赋 UI
- 5.6.1
HeroTalentPanel天赋树可视化:YIUI 无图编辑器组件,需自绘(普通 RectTransform 拼节点 + LineRenderer 连线) - 5.6.2 节点状态用
UIDataBindActive(已点亮)+UIDataBindGray(不可点) - 5.6.3 切换 / 重置交互:
UIEventBindClick+[YIUIInvoke]+ 二次确认走TipsHelper.OpenWait
5.7 锻造 UI
- 5.7.1
EquipForgePanel3 Tab:用UIEventBindToggleGroup - 5.7.2 材料 / 产物用
BagItemUICommon渲染 - 5.7.3 锻造按钮:足够材料时正常,不足时
UIDataBindGray灰显 - 5.7.4 成功后
TipsHelper.OpenSync飘字 +UIParticle粒子动画
5.8 红点系统(完全复用 cn.etetet.yiuireddot)
- 5.8.1 扩展
ERedDotKeyType枚举:HeroCanUpgrade / HeroCanStarUp / HeroCanAwake / TalentHasPoint / BagHasNew / BagHasUsable / GachaHasFree / EquipCanForge 等 - 5.8.2 在
UIRedDotConfigDAG编辑器配置父子关系:HeroEntry = 4 个 Hero* 之和;BagEntry = Bag* 之和;MainMenu = HeroEntry + BagEntry + GachaEntry + EquipForge - 5.8.3 业务侧只写叶子:
RedDotMgr.Inst.SetCount(ERedDotKeyType.HeroCanUpgrade, count),禁止给中间节点 SetCount - 5.8.4 Panel / Button 上挂
RedDotBind选 Key,不写代码自动显隐 - 5.8.5 红点检查逻辑统一封装到
RedDotChecker.RefreshAll(),业务变化时调用
5.9 UI 与 OctoberStudio 集成
- 5.9.1 在 OctoberStudio Main Menu 添加"英雄"/"背包"/"抽卡"三个按钮(普通 Button,因 OctoberStudio 不是 YIUI 体系)
- 5.9.2 按钮 OnClick →
scene.YIUIRoot().OpenPanelAsync<HeroListPanelComponent>()(不写 PanelHelper) - 5.9.3 处理"未联网"状态:按钮 OnClick 前检查 Session → 未连接时
TipsHelper.OpenSync<TipsTextViewComponent>(scene, "请先登录")
5.10 P4 验收
- 5.10.1 全功能 UI 走查:抽卡 → 招募 → 升级 → 穿戴 → 升星 → 觉醒 → 天赋 → 锻造
- 5.10.2 UI 操作与 GM 命令产生相同的业务结果
- 5.10.3 性能:背包列表 1000+ 道具滑动 60 FPS(
YIUILoopScrollChild池化保证) - 5.10.4 红点正确显示与清除(叶子 SetCount → 父节点自动汇总)
- 5.10.5 代码扫描验证零造轮(CI 脚本
tools/ci/check-no-ui-wheel.ps1校验业务包内不含Button.onClick.AddListener/Image.sprite =/Resources.Load/ 自写 ScrollRect 池化)
6. 跨阶段任务
6.1 单元测试与集成测试
- 6.1.1 服务端 Handler 单元测试覆盖率 ≥ 60%
- 6.1.2 客户端 Component 缓存逻辑测试
- 6.1.3 协议序列化往返测试
- 6.1.4 DBComponent 持久化测试(内存模式 + MongoDB 模式各跑一遍)
- 6.1.5 端到端测试:登录 → 抽卡 → 招募 → 升级 → 穿戴 → 退出 → 重新登录验证
- 6.1.6 抽卡概率回归测试:10000 次抽卡,各品质实际概率与配置误差 < 1%
- 6.1.7 抽卡保底回归测试:200 抽,保底触发次数与理论值匹配
6.2 文档
- 6.2.1
Doc/Luban-Guide.mdLuban 使用文档 - 6.2.2
Doc/GM-Commands.md所有 GM 命令列表 + 用法 - 6.2.3
Doc/Logging-Guide.md日志体系说明 + grep/jq 示例 - 6.2.4
Doc/Hero-System-Guide.md业务文档 - 6.2.5
Doc/Bag-System-Guide.md - 6.2.6
Doc/Server-Setup.md(如何起服 + 起 mongo + 编译) - 6.2.7 在工程根
README.md中添加新业务模块入口指引
6.3 性能与监控
- 6.3.1 验证慢操作告警实际生效(构造 100ms+ 的 DB 操作触发 L3 日志)
- 6.3.2 验证单服务器 100 个模拟玩家并发登录 + 业务操作不崩溃
- 6.3.3 客户端背包列表 1000+ 道具滑动 60 FPS(LoopScrollRect 确保)
- 6.3.4 日志写盘性能测试:1000 QPS 业务日志 + 100 QPS 审计日志稳定
6.4 CI 校验
- 6.4.1
tools/ci/check-release-no-gm.ps1Release IL 不含 GM 代码 - 6.4.2
tools/ci/check-rpc-has-gm.ps1每个 RPC Handler 有对应 GM 命令 - 6.4.3
tools/ci/check-logging.ps1每个 Handler 必含日志埋点(继承 RpcMessageHandler 或显式声明) - 6.4.4
tools/ci/check-no-ui-wheel.ps1业务包cn.etetet.hero/bag/gacha内禁止出现Button.onClick.AddListener/Image.sprite =/Resources.Load/ 手写ScrollRect池化等 YIUI 已覆盖能力的造轮代码 - 6.4.5
openspec validate --strict在 CI 中跑
6.5 上线前检查
- 6.5.1 所有 GM 命令仅 Debug 包启用,Release 包剥离 + IL 校验通过
- 6.5.2 错误码本地化覆盖(中文 + 备 EN)
- 6.5.3 数据备份策略(MongoDB 每日自动备份脚本)
- 6.5.4 服务端日志切割与归档(L2 转 OSS)
- 6.5.5 HybridCLR 在 IL2CPP 包下验证:
ET/HybridCLR/Generate/All - 6.5.6 概率审计:所有卡池跑 SimulateGacha 10000 次,截图保留