《Legends of Furry》卡牌数据化重构任务书
文档状态:阶段 0~7 已完成,数据库内容已成为唯一权威来源
编制日期:2026-08-24
适用项目:D:\LegendsOfFurry\Project
目标版本:卡牌内容数据库化 + WPF 卡牌维护工具 MVP
1. 项目结论
本次重构应以“策划可以独立创建、修改、校验并发布卡牌”为唯一主线,不应只把 switch(cardId) 换成另一种硬编码形式。
目标形态如下:
策划
↓
WPF 卡牌维护工具
↓ 读写
SQLite 内容数据库(唯一编辑源)
↓ 校验、编译、发布
Unity 只读内容包(JSON + 图片资源 + manifest)
↓ 加载
CardRegistry
↓
卡牌执行管线
├─ Trigger(何时执行)
├─ Condition(满足什么条件)
├─ TargetSelector(作用于谁)
├─ ValueExpression(数值如何计算)
└─ Effect(执行什么通用操作)最终,新增一张由现有通用能力组合而成的卡牌时,不修改 C#、不创建 Unity ScriptableObject、不重新编译脚本。策划只需在 WPF 工具中录入、验证并发布,即可在游戏中使用。
需要明确的能力边界:
- “造成伤害、治疗、加护甲、抽牌、施加状态、移动、击退、复活、改变资源、操作牌区”等是可复用规则能力。
- 一张具体卡牌使用哪些能力、数值、目标、条件和触发时机属于数据库内容。
- 策划可在不写代码的情况下组合已有规则能力。
- 如果设计出引擎从未具备的新基础规则,例如“交换两名角色的生命百分比”,仍需程序先新增一个通用 Effect 或表达式能力;新增后的能力会自动出现在维护工具中,之后可被任意卡牌复用。
- 数据库中不得存放可执行 C#、程序集类型名或任意脚本。只存受白名单和参数结构约束的规则节点,防止内容破坏游戏状态或导致运行时注入问题。
2. 已检查的项目现状
2.1 技术基线
- Unity 版本:
6000.5.7f1。 - 自有运行时代码约 30 个 C# 文件,核心卡牌链路约 2,000 行,整个自有脚本约 4,900 行。
- 项目使用 Unity 生成的旧式
Assembly-CSharp.csproj,目标框架显示为 .NET Framework 4.7.1,Unity 同时启用 .NET Standard 2.1 API。 - 本机已安装 .NET 10 SDK,并具备 WPF 桌面运行时。工具项目建议独立使用
net8.0-windows,避免改写 Unity 自动生成的解决方案文件。 - 当前仓库存在未跟踪的演示构建目录和压缩包,本次规划不触碰这些文件。
2.2 当前卡牌来源
目前存在两套并行的数据来源:
Assets/Script/Cards/Data/CardCatalog.cs- 运行时用 C# 创建 61 张卡牌。
- 卡名、费用、池、稀有度、目标方式、描述等均硬编码。
- 同文件还硬编码了装备池名称、初始牌库生成规则和稀有度概率。
Assets/Cards/Data/*.asset- 另有 5 个旧版
CardData资产。 PlayerStartingDeck.asset使用其中 4 种,组成 10 张基础牌。CD_FireBall.asset存在,但它的fireball_01未进入当前CardEffectResolver的 ID 分支。
- 另有 5 个旧版
这两条路径已经产生字段重复、行为来源不一致和旧代码残留,重构后必须收敛为一个内容源。
2.3 当前执行链路
HandCardSystem.BuildStartingDeck
↓
CardCatalog / DeckData
↓
HandCardView.PlayCard
↓
CardEffectResolver.CanAfford
↓
HandCardSystem 选择单位/格子/方向
↓
CardEffectResolver.TryPlay
↓
ValidateTarget
↓
扣除行动力和魔力
↓
ResolveCard switch(cardId)
↓
Unit / CombatantState / HandCardSystem / BoardClickController卡牌结算并不只在 CardEffectResolver:
HandCardSystem.EndTurnDiscardAll按 ID 处理“碎裂、入魔、残缺”。HandCardSystem.DrawOne按 ID 处理“反噬”。HandCardSystem.ResolveIdentify硬编码鉴定、稀有度抽取和 20% 诅咒判定。CardEffectResolver.ValidateTarget对复活牌、剑、匕首、盾、弓和游侠存在特殊判断。CombatantState硬编码所有状态的叠层、转化、回合效果、负面状态集合和中文名称。Unit.TakeTypedDamage硬编码普通免疫、闪避、易损、穿甲伤害类型和复活逻辑。CardEffectResolver.Damage硬编码锋利、速攻、恍惚、心火和剑蓄力。BattleFlow、BattleBootstrap、ClassCatalog硬编码职业资料、职业被动、单位数值和每回合抽牌数。BoardClickController硬编码寒冷对移动费用、力竭对行动力上限的影响。
2.4 已发现的重构风险和行为疑点
这些问题必须先建立基线测试或由设计确认,不能在迁移时静默“顺手修正”:
| 项目 | 当前表现 | 处理要求 |
|---|---|---|
| 旧效果处理器 | ICardEffectHandler 及 5 个处理器没有被当前解析器调用 | 垂直切片完成后删除或正式纳入新执行器体系,禁止保留第三条执行路径 |
fireball_01 | 有旧资产和旧处理器,但当前 ID 解析器无分支 | 迁移前确认保留、删除或改为基础牌 |
| “散射” | 描述为至多 3 名角色,实际只伤害一个目标 | 由设计决定正确行为并建立验收用例 |
| “照耀” | 描述写清除目标地块负面状态,实际清除目标单位负面状态 | 由设计确认语义 |
| “通灵” | 当前仅日志占位,不实际结算 | 数据中明确标记为 Placeholder/Disabled,禁止伪装成已实现 |
| 费用 | 同时存在 costText、行动力、魔力和 X 标记 | 新模型只保存结构化费用,显示文本由工具和游戏自动生成 |
| 目标规则 | cross_brilliant 通过卡牌 ID 特判允许选择死亡单位 | 改为目标规则字段 lifeState = Dead |
| 卡牌触发 | 抽到、回合结束留手等行为散落在手牌系统 | 全部转为卡牌 Trigger 图 |
| 状态 | 使用 C# 枚举且行为分散 | 改为稳定字符串 ID + 数据定义 + 通用状态生命周期 |
| 校验 | 只有编辑器菜单中的固定数量和固定 ID 检查 | 改为数据库、发布器、运行时三层结构化校验 |
3. 重构目标、范围与非目标
3.1 必须实现
- SQLite 成为卡牌及其关联内容的唯一编辑源。
- WPF 工具可完成卡牌查询、新建、复制、编辑、启用/停用、删除前引用检查、校验和发布。
- 卡牌的费用、目标、触发时机、条件、效果顺序和参数均由数据库描述。
- Unity 启动时加载发布内容,构建只读
CardRegistry,不再调用CardCatalog.Ensure()。 - 卡牌执行不再使用具体卡牌 ID 的
switch、if或字符串前缀分支。 - 61 张运行时卡牌、当前基础牌和相关牌库全部完成迁移。
- 当前卡牌依赖的状态、牌区操作、资源变化、移动和伤害修正进入通用执行管线。
- WPF 发布前给出可定位到“卡牌—触发器—节点—字段”的中文错误。
- 内容发布采用临时文件 + 原子替换;发布失败时保留上一个可用版本。
- 开发阶段新增或修改的每一个方法都必须有方法级注释。
3.2 建议同时纳入
- 卡牌池、稀有度权重、基础牌库、职业起始牌库配方。
- 状态的名称、标签、最大层数、叠层方式、持续时间和触发行为。
- 职业资料、职业被动和基础战斗参数。
- 卡面图片导入、相对路径管理和缺图占位预览。
- 测试牌库管理,保证新卡发布后能够立即在测试战斗中抽到。
3.3 本期不做
- Steam Workshop、联网内容商店和 Mod 依赖解析。
- 任意脚本编辑器或让策划书写 C#。
- 完整的卡面排版/绘图软件。
- 多人同时编辑、远程数据库和账号权限系统。
- 全量战斗关卡编辑器。
- 为所有未来机制预先实现 Effect;只覆盖当前卡牌和约定的通用扩展能力。
4. “策划只用维护工具新建可用卡牌”的验收定义
策划应能在未打开 IDE、未修改 C#、未手工编辑 JSON/SQLite、未创建 Unity 资产的情况下完成:
- 点击“新建卡牌”。
- 填写稳定 ID、名称、描述、稀有度、卡牌池、标签和卡图。
- 用结构化表单设置行动力/魔力费用和目标规则。
- 在效果树中选择触发时机,添加条件、目标选择和效果节点。
- 将卡牌加入一个卡池或“策划测试牌库”。
- 点击“验证”,修复所有阻断错误。
- 点击“发布到游戏”。
- 启动游戏后无需脚本重新编译即可看到、抽到并正确使用该卡牌。
首个端到端验收样例:
卡名:测试·淬毒斩
费用:1 行动力
目标:距离 1 内存活单位
效果顺序:
1. 对目标造成 4 点普通伤害
2. 若目标仍存活,施加 1 层中毒
归属:策划测试牌库发布后应在测试战斗中抽到并完整结算,Git 差异中不得出现任何为该卡新增的 C# 分支。
5. 规则层与内容层边界
| 规则层(C#) | 内容层(SQLite) |
|---|---|
| 什么叫伤害、伤害事件按什么顺序结算 | 某张牌造成 4 点普通伤害 |
| 行动力、魔力如何检查和扣除 | 某张牌消耗 1 行动力和 2 魔力 |
| 牌如何从手牌进入弃牌堆/消耗堆 | 某张牌打出后消耗 |
| 如何按距离、视线、阵营和生死筛选目标 | 某张牌选择距离 2 内死亡友方 |
| Trigger、Condition、Effect 如何调度 | 某张牌在抽到时触发 5 点暗伤 |
| 状态生命周期、叠层和到期的通用算法 | 中毒的最大层数、回合效果和衰减量 |
| 棋盘寻路、移动合法性、击退碰撞规则 | 某效果免费移动 2 格或击退 1 格 |
| 内容加载、版本、校验、回退 | 卡名、卡图、池、标签、排序和启用状态 |
| Effect 执行器的实现 | 卡牌引用哪个 Effect 及其参数 |
| 值表达式的安全求值 | 伤害值为 3 × 本次消耗魔力 |
判断准则:新增同类型卡牌是否需要改 C#。若需要,先判断缺的是不是一个可复用的规则能力;只有缺少新规则能力时才允许开发 Effect。
6. 目标工程结构
Project
├─ Assets
│ ├─ Script
│ │ ├─ Core
│ │ │ ├─ Combat
│ │ │ ├─ Cards
│ │ │ ├─ Statuses
│ │ │ ├─ Targeting
│ │ │ └─ Turns
│ │ ├─ Content
│ │ │ ├─ Contracts
│ │ │ ├─ Loading
│ │ │ ├─ Validation
│ │ │ └─ Registry
│ │ └─ Presentation
│ │ ├─ Cards
│ │ ├─ Board
│ │ └─ UI
│ ├─ StreamingAssets
│ │ └─ Content # 发布器生成,游戏只读
│ └─ Tests
│ ├─ EditMode
│ └─ PlayMode
├─ ContentSource
│ ├─ lof-content.db # 策划唯一编辑源
│ ├─ Artwork # 原始卡图/导入资源
│ ├─ Backups
│ └─ Migrations
├─ Tools
│ ├─ LegendsOfFurry.Content.sln # 独立于 Unity 自动生成的 sln
│ ├─ Content.Contracts
│ ├─ Content.Persistence
│ ├─ Content.Compiler
│ ├─ CardEditor.Wpf
│ └─ Content.Tests
└─ DocsUnity 自动生成的 Assembly-CSharp.csproj 和现有解决方案不作为手工维护目标。WPF 工具及发布器使用独立解决方案。
7. 数据存储与发布方案
7.1 方案决定
- SQLite 是编辑态权威数据源,WPF 直接通过事务读写。
- Unity 不直接编辑 SQLite,也不依赖桌面数据库驱动。
Content.Compiler从 SQLite 读取已启用内容,执行完整校验后导出确定性的只读 JSON 内容包。- 导出内容放入
Assets/StreamingAssets/Content,构建后仍可按需要被外部内容包覆盖。 - 内容包包含
manifest.json、卡牌/状态/牌库数据、资源清单和图片。 - manifest 至少记录 schema 版本、内容版本、生成时间、记录数和 SHA-256 校验值。
- 游戏加载失败时必须停止进入战斗并显示可读错误;开发环境可选择回退到最近一次成功内容包,但不能悄悄加载半套数据。
这一方案同时满足“可扩展内容进入数据库”和“Unity 运行时稳定、跨平台、只读、可测试”的要求。
7.2 数据库核心表
建议使用以下逻辑表;字段名最终以迁移脚本为准。
| 表 | 主要职责 |
|---|---|
schema_info | 数据库 schema 版本和迁移历史 |
content_versions | 内容版本、发布说明、创建时间和发布状态 |
cards | 卡牌基础信息、结构化费用、目标入口、卡图、启用状态 |
card_pools | 卡牌池/装备池定义 |
card_pool_members | 卡牌与池的多对多关系、权重和排序 |
card_tags / card_tag_members | 攻击、剑、诅咒、临时牌等可扩展标签 |
behaviors | 某卡牌/状态/职业在某 Trigger 下的行为图入口 |
behavior_nodes | Sequence、Condition、Effect、Repeat、ForEach 等节点 |
statuses | 状态名称、正负面标签、最大层数、叠层和持续策略 |
decks / deck_entries | 基础牌库、测试牌库及固定卡牌数量 |
deck_recipes | 按池、稀有度、权重随机生成的职业起始牌库配方 |
rarities | 稀有度显示、颜色、默认权重和排序 |
class_profiles | 职业名称、说明、初始资源和牌库配方引用 |
game_settings | 手牌上限、每回合抽牌、基础行动点等平衡配置 |
assets | 卡图、图标、VFX key 与相对路径/资源 key |
7.3 cards 关键字段
card_id:稳定字符串主键;创建后不可随意改名。display_name、description:显示内容。artwork_key:资源表引用,不保存本机绝对路径。rarity_id、family_tag、enabled、sort_order。action_cost、mana_cost、spend_all_action、spend_all_mana。is_attack、exhaust_on_play、temporary、curse、unplayable。selection_mode:Self、Unit、Cell、Direction、None。range、team_filter、life_state_filter、requires_line_of_sight、allow_self。row_version、created_at、updated_at。
costText 不再作为权威字段,始终由结构化费用自动生成。
7.4 行为图结构
behaviors 使用稳定 Trigger key;behavior_nodes 以父子关系和顺序构成树:
OnPlay
└─ Sequence
├─ Effect: Damage
│ ├─ target = SelectedUnit
│ ├─ amount = Constant(5)
│ └─ damageType = True
└─ Condition: TargetKilledByThisCard
└─ Then
└─ Effect: ModifyActionPoints(+2)节点参数以结构化 JSON 保存,但只能由注册的节点 schema 生成和校验。数据库不存 CLR 类型名,运行时只按稳定的 operation_key 查找执行器。
8. 通用规则能力清单
8.1 Trigger
首期至少支持:
OnPlayOnDrawOnAddedToHandOnTurnStartInHandOnTurnEndInHandOnDiscardOnExhaustOnDamageResolvedOnKillOnBattleStart
状态和职业行为还需支持 BeforeDealDamage、BeforeTakeDamage、AfterDealDamage、AfterTakeDamage、OnUnitTurnStart、OnUnitTurnEnd 和 OnMoveCostCalculated。
8.2 Condition
首期至少支持:
- 目标是否存活/死亡、是否自己、阵营是否匹配。
- 目标是否有护甲、状态、标签或指定层数。
- 当前卡牌是否具有指定标签/池/稀有度。
- 本卡是否击杀目标、伤害是否命中、生命伤害是否大于零。
- 当前资源、生命、护甲是否满足比较式。
- 本次是否消耗 X 行动力/魔力。
- 概率判定。
- 逻辑组合:All、Any、Not。
8.3 TargetSelector
首期至少支持:
- Self、SelectedUnit、SelectedCell。
- 选中目标周围的单位、曼哈顿范围内单位。
- 指定方向的直线单位、面前 1×3 范围。
- 所有友方、所有敌方、所有单位。
- 当前卡牌实例、手牌、抽牌堆、弃牌堆、消耗堆。
- 对结果设置数量上限、随机、排序和是否包含中心目标。
卡牌只负责描述选谁;距离、视线、地形阻挡、占用、生死和阵营检查由规则层统一执行。
8.4 ValueExpression
不得把公式作为任意字符串直接求值。使用受控表达式树:
- Constant。
- SpentActionPoints、SpentMana。
- CurrentHealth、MaxHealth、Armor、StatusStacks。
- CardRuntimeCounter。
- Add、Subtract、Multiply、Divide、Min、Max、Floor、Clamp。
这样可表示 3 × 本次消耗魔力、当前护甲 ÷ 2、基础伤害 + 卡牌永久加成 等当前需求。
8.5 Effect
为覆盖现有卡牌,首期至少实现:
| 类别 | Effect |
|---|---|
| 战斗 | Damage、Heal、GainArmor、Revive、KnockBack |
| 状态 | AddStatus、SetStatus、ReduceStatus、RemoveStatus、ClearStatuses |
| 资源 | ModifyActionPoints、ModifyMaxActionPoints、ModifyMana、SpendResource |
| 卡牌 | DrawCards、GenerateCard、MoveCards、RemoveCardsByQuery、ModifyCardRuntimeValue |
| 牌堆交互 | RevealTopCardsAndChooseDiscard、PlayTopCardsForFree |
| 移动 | BeginFreeMove |
| 流程 | EndTurn、NoOp/Placeholder |
| 表现 | PlayVfx、PlaySfx;失败不得改变核心结算 |
每个执行器必须声明自己的参数 schema、是否需要目标、是否可能等待玩家输入、是否修改牌区,以及可供 WPF 显示的中文名称和字段说明。
9. 新卡牌执行管线
1. 读取 CardDefinition
2. Preflight
├─ 卡牌是否启用/可打出
├─ 资源是否足够
├─ 目标入口是否合法
└─ 所有节点和执行器是否可用
3. 建立 CardExecutionContext
├─ 卡牌实例
├─ 施放者、已选单位/格子/方向
├─ 费用快照与资源快照
└─ 本次伤害/击杀/选择结果
4. 原子提交费用与出牌状态
5. 依顺序执行 OnPlay 行为图
6. 每个节点解析 Condition、Target、Value 后调用 EffectExecutor
7. 支持协程式等待 VFX、卡牌选择或额外目标选择
8. 执行 AfterPlay、OnKill、状态和伤害事件
9. 根据定义进入弃牌堆/消耗堆
10. 记录结构化战斗日志并刷新 UI必须解决的工程约束:
- 费用预检和提交分开,预检失败不能扣资源。
- 行为图执行过程中不得再次按卡牌 ID 判断。
- 多段伤害共享同一个执行上下文,能够判断“本卡造成击杀”。
- 用户选择型效果必须可暂停、取消和继续;战斗结束或对象销毁时必须安全取消。
- 伤害生命周期要固定:创建请求 → 攻击方修正 → 防守方修正 → 护甲/生命结算 → 死亡 → 事后事件。
- 表现层异步失败不能让逻辑层重复结算。
- 所有目标集合使用确定性排序;需要随机时使用注入的战斗随机源,便于测试和回放。
10. WPF 卡牌维护工具
10.1 技术方案
- WPF + MVVM。
- 工具目标框架:
net8.0-windows。 - SQLite 访问封装在
Content.Persistence,UI 不直接拼 SQL。 - 数据库 schema 使用按版本顺序执行的迁移脚本。
- ViewModel 使用
INotifyDataErrorInfo或等价机制展示字段错误。 - 保存、复制、删除、发布均通过命令执行,并具有未保存更改提示。
- 业务模型、校验规则和导出 DTO 尽量与 Unity 共享纯 C# 合同,不引用 WPF 或 Unity 类型。
10.2 主界面
建议采用三栏布局:
┌─────────────────────────────────────────────────────────────────┐
│ 数据库 保存 验证 发布 备份 内容版本 │
├──────────────┬──────────────────────┬───────────────────────────┤
│ 卡牌列表 │ 基础属性 │ 行为/效果树 │
│ 搜索 │ ID、名称、描述 │ OnPlay │
│ 池/稀有度筛选 │ 费用、标签、卡图 │ ├ Damage │
│ 启用状态 │ 目标、距离、视线 │ └ AddStatus │
│ 新建/复制 │ 牌库/卡池归属 │ 节点参数面板 │
├──────────────┴──────────────────────┴───────────────────────────┤
│ 错误/警告列表:双击定位到具体字段或效果节点 │
└─────────────────────────────────────────────────────────────────┘10.3 MVP 功能
- 打开/新建内容数据库,自动迁移 schema。
- 卡牌列表搜索、筛选、排序和启用状态显示。
- 新建、复制、编辑和安全删除卡牌。
- 结构化费用编辑,自动生成费用显示。
- 目标规则编辑及目标范围摘要。
- 卡图导入:复制到受管目录、生成资源 key、预览、检查丢失。
- Trigger/Condition/Target/Effect 树形编辑器。
- 根据 operation schema 动态显示正确参数控件。
- 卡池和测试牌库分配。
- 实时字段校验、整库校验和引用检查。
- 一键备份数据库。
- 一键发布,并显示导出文件、版本、记录数和错误。
- 发布日志和最近一次成功版本回退。
10.4 校验分级
- Error:阻止保存或发布,例如重复 ID、未知 Effect、缺少必填参数、无效引用、循环行为图。
- Warning:允许发布但需确认,例如无卡图、无牌库归属、描述与结构化数值可能不一致、占位效果。
- Info:提示自动生成的费用文本、目标摘要和最终效果摘要。
删除被引用的卡、池、状态或资源时,工具必须列出引用方并阻止直接删除;允许先停用或完成引用迁移。
11. 现有代码迁移表
| 当前文件/区域 | 迁移目标 | 完成标准 |
|---|---|---|
CardCatalog.cs | SQLite + CardRegistry | 61 张卡不再由 C# 创建,删除固定数量判断 |
CardData.cs | 纯 CardDefinition DTO + 运行时 CardInstance | 去除 ScriptableObject 权威数据和重复费用字段 |
DeckData.cs / .asset | decks、deck_entries | 基础牌库只由发布内容构建 |
ClassCatalog.cs | class_profiles、deck_recipes | 职业资料和起始牌库不再硬编码 |
CardEffectResolver.ResolveCard | 行为图执行器 | 删除全部具体卡牌 ID 分支 |
ValidateTarget | TargetRule + 统一目标服务 | 复活、武器类别、职业距离均由数据和修正规则描述 |
HandCardSystem 的诅咒 ID 判断 | OnDraw / OnTurnEndInHand | 手牌系统不认识“碎裂”等具体卡牌 |
ResolveIdentify | CardQuery + GenerateCard | 池、稀有度、概率、诅咒均由行为图配置 |
CombatantState | 数据化状态定义 + 通用状态容器 | 状态使用字符串 ID,行为进入 Trigger/Modifier |
Unit.TakeTypedDamage | DamagePipeline | 状态修正通过事件/修正规则参与,不按状态枚举判断 |
BattleFlow 职业特判 | 职业行为图 | 回合流程不认识战士、法师、刺客 |
BattleBootstrap 数值 | class/unit/game settings | 生命、魔力、抽牌数等从内容配置读取 |
BoardClickController 状态特判 | 属性修正服务 | 移动费用和最大行动点读取聚合修正结果 |
旧 ICardEffectHandler 系列 | 新 IEffectExecutor 或删除 | 工程中只保留一套正式效果系统 |
LegendsOfFurryProjectSetup.ValidateProject | 内容编译器 + 自动测试 | 不再检查固定卡牌数量和固定 ID |
展示层 HandCardView 只读取 CardDefinition 和图片服务,不参与结算。
12. 实施阶段与任务拆分
状态说明:✅ 已完成、🟡 已实现待 Unity/设计验收、⬜ 未完成。完成标记只在对应自动化测试或阶段验收通过后更新。
截至 2026-08-24:桌面内容工具解决方案构建为 0 警告/0 错误,12 项 .NET 自动化测试全部通过;Unity 工程交互式编译成功,最近一次 EditMode 测试 14/14 通过。新增的注册表与启动预检测试待下一次 Unity 刷新后回归。交互式编辑器曾在 Unity 原生 TextCore 字体引擎中崩溃一次,确认与 C# 卡牌代码无关。
阶段 0:冻结行为基线(2~3 个有效开发日)
| 编号 | 状态 | 任务 | 交付物/完成条件 |
|---|---|---|---|
| P0-01 | ✅ | 整理当前卡牌清单 | 已生成并校验 61 张目录卡 + 5 个旧资产,共 66 条字段快照 |
| P0-02 | ✅ | 建立行为争议表 | 散射、照耀、通灵、旧火球等差异已登记;阶段 5 迁移默认保持当前基线,设计变更须单独留痕 |
| P0-03 | ✅ | 建立最小测试骨架 | 已具备 EditMode、进入/退出 PlayMode 冒烟和独立 .NET 管线测试入口 |
| P0-04 | ✅ | 为当前关键行为写特征测试 | 已覆盖目录、费用、牌库、职业、状态、图解释、条件/目标/表达式、效果、伤害、异步和日志 |
| P0-05 | ✅ | 制定分支和数据备份规则 | 已完成单一权威源、备份时点、二进制数据库分支合并、发布回退和恢复演练规范 |
阶段门:现有行为被测试固定,所有已知不一致均有明确处理结论。
阶段 1:共享合同、SQLite 与发布器(4~6 日)
| 编号 | 状态 | 任务 | 交付物/完成条件 |
|---|---|---|---|
| P1-01 | ✅ | 建立独立工具解决方案 | Contracts、Persistence、Compiler、WPF、Tests 均可构建 |
| P1-02 | ✅ | 定义稳定 DTO 和 key 规范 | schema v1 及 Trigger、Effect、Condition、Target、Expression 白名单已建立 |
| P1-03 | ✅ | 实现 SQLite schema v1 | 表、外键、索引、约束、种子数据和迁移记录已实现并测试 |
| P1-04 | ✅ | 实现 Repository/Unit of Work | 聚合 CRUD 使用参数化 SQL 和事务,删除引用受外键保护 |
| P1-05 | ✅ | 实现 ContentValidator | 已覆盖 ID/引用、操作 key、参数 JSON、表达式、图结构和资源 |
| P1-06 | ✅ | 实现 ContentCompiler | 已实现确定性 JSON、manifest、SHA-256、版本目录和原子 current 指针 |
| P1-07 | ✅ | 建立 schema/导出测试 | 已验证 schema、完整聚合往返和重复导出内容/校验值一致 |
阶段门:✅ 已通过 seed-sample 命令及自动化测试验证可从空数据库创建样例伤害卡;发布器测试验证可生成有效内容包。
阶段 2:Unity 内容加载垂直切片(4~6 日)
| 编号 | 状态 | 任务 | 交付物/完成条件 |
|---|---|---|---|
| P2-01 | ✅ | 实现 ContentPackageLoader | Unity 测试已验证加载、版本、路径和 SHA-256 检查 |
| P2-02 | ✅ | 实现 CardRegistry | Unity 测试已验证卡牌、牌库和启用内容只读索引 |
| P2-03 | ✅ | 改造 CardDefinition/CardInstance | 新定义、兼容视图、测试牌库展开均已接入并通过 Unity 测试 |
| P2-04 | ✅ | 实现基础执行器 | Damage、Heal、GainArmor、DrawCards、BeginFreeMove、EndTurn 已通过 Unity 组合回归 |
| P2-05 | ✅ | 打通费用和基础目标 | 固定/X/免费费用及 Self/Unit/Cell/Direction、阵营、存活、距离、视线已通过 Unity 回归 |
| P2-06 | ✅ | 打通最小 WPF 录入页 | 自动化测试已完成新建伤害牌→SQLite→测试牌库→发布包链路 |
阶段门:首张完全由 WPF → SQLite → 内容包 → Unity 的卡牌可在战斗中使用。
阶段 3:完整卡牌规则引擎(8~12 日)
| 编号 | 状态 | 任务 | 交付物/完成条件 |
|---|---|---|---|
| P3-01 | ✅ | 建立 CardExecutionContext | 资源快照、目标、逐次伤害/击杀记录和卡牌运行时值已通过 Unity 回归 |
| P3-02 | ✅ | 实现行为图解释器 | Sequence、Condition、Effect、Repeat、ForEach、稳定排序、深度/次数保护和取消流程已通过 Unity 回归 |
| P3-03 | ✅ | 实现 Condition/Target/Expression 注册表 | 三类白名单注册表、受控递归表达式、确定性目标集合、正式行为图接入和启动预检已纳入 Unity 21/21 回归 |
| P3-04 | ✅ | 完成 Effect 执行器 | 第 8.5 节全部首期操作已进入白名单执行器并通过编译/发布预检 |
| P3-05 | ✅ | 实现异步选择节点 | 看牌弃置、免费打牌和免费移动均通过完成回调暂停/恢复;额外目标由现有目标确认与 foreach 续接 |
| P3-06 | ✅ | 重构伤害生命周期 | DamageRequest/Resolution、前后事件、属性、护甲、免疫、复活和击杀顺序可测试 |
| P3-07 | ✅ | 实现结构化战斗日志 | 日志包含卡牌、Trigger、Behavior、节点、操作、目标、输入 JSON 和结果 |
阶段门:不增加具体卡牌分支即可表达当前全部卡牌行为。
阶段 4:状态、职业与平衡配置数据化(6~9 日)
| 编号 | 状态 | 任务 | 交付物/完成条件 |
|---|---|---|---|
| P4-01 | ✅ | 重构状态容器 | RuntimeStatusInstance 使用字符串 ID、层数、持续时间、来源和实例值,保留枚举兼容入口 |
| P4-02 | ✅ | 实现状态生命周期 | 叠层策略、数据库上限、到期、正负面分类和清除规则已接入 |
| P4-03 | ✅ | 状态参与战斗事件 | 状态行为运行时已接入单位回合事件;中毒、再生、腐化定义已写入数据库 |
| P4-04 | ✅ | 数据化职业资料和被动 | 五职业资料和被动从内容包加载,BattleFlow 已移除职业枚举行为分支 |
| P4-05 | ✅ | 数据化牌库配方和稀有度权重 | 新牌库构建入口只读取职业配方、卡池查询和稀有度权重;旧内容保留到阶段 5 迁移 |
| P4-06 | ✅ | 数据化基础战斗参数 | 手牌上限、起手/每回合抽牌、基础行动点、职业生命和魔力均有数据库配置来源 |
阶段门:卡牌相关的可扩展内容不再散落在 Unit、BattleFlow、HandCardSystem 和输入控制器中。
阶段 0~4 验收记录(2026-08-24):独立 .NET 测试 12/12;隔离 Unity EditMode/PlayMode 组合回归 21/21;主程序集与编辑器测试程序集均为 0 编译错误;phase4 内容包发布成功,包含 1 张管线样例卡、20 个状态、5 个职业资料、1 套测试牌库和基础战斗参数。
阶段 5:全量迁移与等价性验证(6~9 日)
| 编号 | 任务 | 交付物/完成条件 |
|---|---|---|
| P5-01 | ✅ 编写一次性迁移工具 | migrate-legacy 从 Unity 导出 CSV 自动生成数据库,不人工复制字段 |
| P5-02 | ✅ 迁移基础牌、61 张卡、池和牌库 | 66 张基线记录、全部卡池、基础牌库和全卡测试牌库已入库 |
| P5-03 | ✅ 迁移所有状态和职业行为 | 20 状态、5 职业、职业牌库配方和卡牌生命周期触发器已数据化 |
| P5-04 | ✅ 逐卡数据校验 | 66/66 通过 schema、引用、行为图和 Unity 运行时能力预检 |
| P5-05 | ✅ 代表性逐类 PlayMode 测试 | Trigger、Condition、Target、Expression、Effect 与伤害生命周期均有回归 |
| P5-06 | ✅ 全量冒烟 | 66 张进入测试牌库;可打出卡均有 on_play,诅咒生命周期入口已验证 |
阶段门:正式内容完全来自数据库发布包,旧目录只作为迁移历史存在。
阶段 6:WPF 工具完善(8~12 日)
| 编号 | 任务 | 交付物/完成条件 |
|---|---|---|
| P6-01 | ✅ 完成主界面和导航 | 列表搜索、三页编辑区、定位错误面板和未保存提示已完成 |
| P6-02 | ✅ 完成卡牌基础编辑 | 字段、费用、目标、卡图上传/预览/备份、池、标签和测试牌库入口已完成 |
| P6-03 | ✅ Electron 全量迁移 | Vue 3 + Element Plus + Electron + SQLite;功能、校验、发布、回退与卡面拖拽已迁移,WPF 暂留回退 |
| P6-03 | ✅ 完成效果树编辑器 | 简易效果与无损高级树;增删、复制、排序、缩进、分支、JSON 参数已完成 |
| P6-04 | ✅ 完成删除/引用保护 | 外键阻止悬空引用,界面提示先解除引用或停用 |
| P6-05 | ✅ 完成备份、版本和发布体验 | 保存/删除/发布自动备份,一键验证发布,历史版本原子回退 |
| P6-06 | ✅ 完成策划使用说明 | 新建到发布流程、常见错误、能力词典和阶段 5 迁移报告已交付 |
| P6-07 | ✅ 策划可用性验收包 | 独立验收样例已写入说明;WPF 发布版启动、保存链路和发布级校验通过 |
阶段门:达到第 4 节的最终验收定义。
阶段 5~6 验收记录(2026-08-24):正式 phase5 包包含 66 张卡、20 个状态、5 个职业资料、2 套固定牌库;内容校验 0 error;.NET 测试 12/12;隔离 Unity 回归 23/23;WPF Release 与 win-x64 发布版编译成功。缺少的 61 张卡图保持可见 warning,不阻止策划测试。
阶段 7:切换、清理与发布(4~6 日)
| 编号 | 任务 | 交付物/完成条件 |
|---|---|---|
| P7-01 ✅ | 删除旧执行路径 | 已移除 ResolveCard switch、旧 Handler、运行时 CardCatalog 和 ID 特判 |
| P7-02 ✅ | 删除旧权威资产依赖 | 场景和牌库已解除旧 CardData/DeckData 资产引用,旧资产已物理删除 |
| P7-03 ✅ | 构建前内容检查 | ContentBuildValidator 在构建前验证 current、SHA-256、合同和执行能力 |
| P7-04 ✅ | 完成回归与构建验证 | Unity 21/21、.NET 13/13、WPF Release 和 Windows Player 构建通过 |
| P7-05 ✅ | 更新开发文档 | 已补充阶段 7 架构、Effect 扩展、注释规范和故障处理报告 |
阶段门:工程中不存在按具体卡牌 ID 执行业务逻辑的生产代码。
阶段 7 验收记录(2026-08-24):生产代码静态扫描不存在具体卡牌 ID 执行分支、CardCatalog、ResolveCard、DeckData 或旧 Handler;5 张旧 CardData 资产和 PlayerStartingDeck.asset 已删除;隔离 Unity 编辑器测试 21/21,.NET 测试 13/13,Windows x64 Player 构建成功。
13. 测试策略
13.1 内容层测试
- SQLite 迁移可从空库升级到最新版本。
- 外键、唯一 ID、停用引用、资源路径和行为图结构校验。
- 每个 operation key 的参数 schema 正反例。
- 导出稳定性、manifest 校验值和损坏内容检测。
- 数据库自动备份和发布失败回退。
13.2 规则层单元测试
- 固定费用、X 行动力、X 魔力和免费打牌。
- 目标距离、视线、阵营、生死、方向和范围扩展。
- 伤害类型、护甲、免疫、闪避、多段、增减伤和击杀。
- 状态叠层、上限、持续时间、清除、转化和回合触发。
- 抽牌、弃牌、消耗、洗牌、生成卡、移除卡族和手牌上限。
- 确定随机种子下的稀有度和 CardQuery 结果。
13.3 集成测试
- WPF 新建卡 → 保存数据库 → 发布 → Unity 加载 → 加入测试牌库 → 打出。
- 代表性复杂卡:连斩、刺击、蓄力狙杀、招雷术、魔力风暴、封喉、反噬、碎裂、鉴定、预演、占卜、复活。
- 内容包版本不兼容、未知 Effect、缺失状态、缺失图片、重复 ID 时的错误体验。
- 场景重载、战斗结束和异步选择中断不造成重复扣费或重复结算。
13.4 回归红线
- 不允许以“测试不好写”为由保留具体卡牌 ID 分支。
- 不允许只测试数据能加载,而不测试效果真实结算。
- 不允许 WPF 校验通过但 Unity 启动后才发现未知节点。
- WPF 与 Unity 必须复用同一套内容合同和校验规则,或通过合同一致性测试保证等价。
14. 方法注释与开发规范
用户要求“开发阶段给每个方法写上注释”,本项目将其作为 Definition of Done,而不是建议。
14.1 强制要求
- 所有新增或被修改的方法均写 XML 文档注释,包括
public、internal、protected、private、构造方法、Unity 生命周期方法、命令处理方法和测试辅助方法。 - 注释必须说明方法目的;存在副作用、事务边界、事件顺序、线程要求、取消行为或失败策略时必须写明。
- 有参数或返回值且含义不直观时,使用
<param>、<returns>;可能主动抛出的业务异常使用<exception>。 Awake、Start、Update等不能只写“Unity 生命周期函数”,应说明在本组件中完成什么初始化或调度。- Effect、Condition、TargetSelector 和 ValueResolver 必须在注释中写清输入、输出、支持的目标和边界情况。
- 不接受与代码逐字重复的无信息注释,例如“设置 value 的值”。
示例:
/// <summary>
/// 验证卡牌、资源和主目标,并在全部检查通过后创建一次不可变的执行快照。
/// 此方法只做预检,不扣除行动力或魔力。
/// </summary>
/// <param name="request">包含卡牌实例、施放者和玩家选择结果的出牌请求。</param>
/// <returns>成功时返回执行快照;失败时返回可展示给玩家的错误。</returns>
public CardPreflightResult Preflight(CardPlayRequest request)14.2 检查方式
- 代码评审清单加入“本次新增/修改方法是否全部有有效 XML 注释”。
- 工具项目启用 XML 文档生成和文档警告。
- 增加一个仓库检查脚本或 Roslyn 分析器,扫描自有代码中的无注释方法;Unity 自动生成代码和第三方包排除。
- 每个阶段门执行注释检查;不满足时任务不得标记完成。
15. 交付物
ContentSource/lof-content.db及版本化迁移脚本。- WPF 卡牌维护软件及可直接启动的 Windows 发布包。
- Content.Contracts、Persistence、Validator、Compiler 及自动测试。
- Unity 内容加载、注册表、卡牌执行管线、状态和伤害生命周期。
- 迁移后的全部正式卡牌、状态、卡池、牌库、职业和基础配置。
- 生成的 Unity 只读内容包与 manifest。
- 策划使用说明、Effect/Condition/Target 能力词典。
- 程序扩展新通用 Effect 的开发说明。
- 自动化测试和内容构建前校验。
- 旧硬编码清理报告和迁移差异报告。
16. 工期评估与里程碑
以一名熟悉 Unity/C#/WPF 的开发者全职计算:
| 里程碑 | 内容 | 预计有效开发日 |
|---|---|---|
| M1 | 行为基线、数据库、合同、发布器 | 6~9 |
| M2 | Unity 垂直切片,WPF 可创建第一张卡 | 4~6 |
| M3 | 完整卡牌规则引擎 | 8~12 |
| M4 | 状态、职业、牌库和配置数据化 | 6~9 |
| M5 | 61 张卡及旧资产全量迁移 | 6~9 |
| M6 | WPF 完整 MVP 和策划验收 | 8~12 |
| M7 | 清理、回归、构建和文档 | 4~6 |
| 合计 | 达到本任务书全部目标 | 42~63 |
如果只做“卡牌基础字段 + 常用 OnPlay 效果 + 简易 WPF”,可在约 20~30 日形成受限 MVP;但它无法完整覆盖当前诅咒、状态、职业被动、看牌选择和复杂卡牌,不应宣称已达到最终目标。
建议按阶段门交付,优先在 M2 证明完整链路,避免先迁移 61 张卡后才发现数据模型或编辑器不适用。
17. 主要风险与控制措施
| 风险 | 后果 | 控制措施 |
|---|---|---|
| 把具体卡牌改写成 61 个“自定义 Effect” | 表面数据化,实际仍需程序维护 | Effect 命名必须是通用动词,评审禁止卡牌专属执行器 |
| JSON 参数过于自由 | WPF 与 Unity 对字段理解不一致 | operation schema 唯一来源,编辑、校验、导出和运行时共同使用 |
| 状态仍用 enum 特判 | 新卡引用新状态仍需改多处 C# | 稳定字符串 ID、状态定义和事件修正器 |
| 异步选择/动画与逻辑混杂 | 重复扣费、卡死或结算两次 | 执行上下文、显式暂停状态、取消令牌和逻辑/表现分离 |
| 数据库损坏或发布半成品 | 游戏无法启动或卡牌缺失 | 事务、自动备份、临时目录、原子替换、manifest 校验 |
| 迁移时顺手改变旧行为 | 难以定位回归 | 先写特征测试,争议行为由设计签字确认 |
| 新卡创建后游戏中找不到 | 未加入任何牌库/池 | 工具提供测试牌库,发布前对“无入口卡牌”给出警告 |
| 卡图使用绝对路径 | 换电脑或构建后丢失 | 工具导入并复制到受管资源目录,只存相对 key |
| Unity 与工具框架不兼容 | 共享程序集无法加载 | 共享合同保持纯 C#/.NET Standard 兼容,WPF/Unity 适配层分离 |
| 一次性大爆炸替换 | 长期无法得到可运行版本 | 垂直切片、双读仅限短期迁移、阶段门后立即删除旧路径 |
18. 最终验收清单
19. 开发启动顺序
确认本任务书后,开发不得直接从 WPF 界面或 61 张卡迁移开始。首批任务固定为:
- 确认第 2.4 节的行为疑点。
- 建立当前版本的特征测试和卡牌清单快照。
- 定义共享合同、稳定 key 和 schema v1。
- 实现 SQLite → 校验 → 只读内容包的发布链。
- 用一张基础伤害牌完成 WPF 到 Unity 的垂直切片。
- 垂直切片评审通过后再扩充规则引擎并迁移全部内容。
任何阶段如果需要新增专属于某张卡的 C# 分支,应暂停实现并回到规则能力评审,确认它是否能够抽象成可复用 Effect、Condition、TargetSelector 或 ValueExpression。