一种通用、可扩展的卡牌游戏效果系统设计方案

一种通用、可扩展的卡牌游戏效果系统设计方案

本文将基于四川大学三山工作室的独立卡牌游戏《巷神》,深入剖析其内在的设计思想,将其提炼为一套通用的、可应用于绝大多数卡牌游戏或 RPG 技能系统的设计模式。

在卡牌游戏中,无论是《炉石传说》、《杀戮尖塔》还是无数后起之秀,核心魅力都源于卡牌效果之间那令人眼花缭乱的连锁反应。一张卡牌打出,可能引发一连串的事件,例如召唤一个单位,单位的战吼触发了奥秘,奥秘的效果改变了费用,从而让玩家能打出下一张本不可能使用的卡牌。

对玩家而言,这是一种“神抽”、“combo”和“运营”的乐趣,而对开发者而言,这背后则是一套需要极致精密与健壮的系统工程

一张卡牌上的描述,如“对所有敌人造成 3 点伤害,每有一个友方单位,伤害便 +1”,包含着目标筛选、数值计算、状态查询和效果执行等一系列复杂逻辑。如果将每个效果都单独硬编码,系统将很快变得庞杂,难以维护。

因此,本文将基于四川大学三山工作室的独立卡牌游戏《巷神》,深入剖析其内在的设计思想,将其提炼为一套通用的、可应用于绝大多数卡牌游戏或 RPG 技能系统的设计模式。

原子指令

任何复杂的卡牌效果,究其本质,都是一个或多个基础操作的有序组合。我们将这些不可再分的基础操作定义为原子指令。本文将其归纳为以下几个通用类别:

数值资源修改

这是最基础、最核心的指令。几乎所有卡牌游戏玩法都围绕着核心资源的修改而进行。

应用场景指令示例说明
生命 / 伤害(怪物)Modify_Resource(Target_Monster, HP, -Y)HP 等同于怪物生命值,-Y 为对怪物造成的伤害
生命 / 恢复(玩家)Modify_Resource(Player, HP, +10)+10 为恢复玩家 10 点生命值
费用 / 能量Modify_Resource(Player, Mana, ±X)对应玩家出牌消耗的法力水晶、能量点等资源,±X 为增减数值
特殊计数器Modify_Resource(Character, Counter, ±N)对应游戏内临时资源或计数,如额外行动点、角色怒气值、连击点数等

通过将所有数值变动统一到这个原子指令下,我们可以轻松地管理、追踪和响应任何资源的增减事件,为后续的触发机制(例如“每当一个角色受到伤害时...”)打下基础。

永久属性修改

与临时性的资源不同,属性是角色的内在能力基线,通常在游戏开始或通过特定升级事件发生改变。

应用场景指令示例说明
核心能力属性Modify_Attribute(Player, Max_HP, +10)提升玩家生命上限,增强生存能力
核心能力属性Modify_Attribute(Player, Max_Hand_Size, +1)提升玩家手牌上限,增加策略深度
衍生属性(光环效果)Modify_Attribute(Ally_Unit, Attack, +1)所有友方单位攻击力 + 1,为永久光环类效果
其他衍生属性Modify_Attribute(Target, Defense/CritRate/RefreshRate, ±X)可用于调整防御力、暴击率、卡牌刷新概率等属性

将属性与资源区分开,有助于构建更清晰的角色成长和永久增益体系

实体与场面操控

卡牌游戏的核心是场面。召唤、摧毁、变形、移动场上的实体(卡牌、单位、道具等)是必不可少的原子指令。

指令原型指令格式应用场景
Card_SpawnCard_Spawn(目标位置, 卡牌 ID, 数量)生成 / 召唤 / 获得卡牌
Card_DestroyCard_Destroy(目标, 类型)移除 / 摧毁卡牌
Card_ReplaceCard_Replace(原卡牌 ID, 新卡牌 ID)替换 / 变形 / 升级卡牌
Card_RefreshCard_Refresh(目标区域/状态)刷新抽卡、重置行动与状态

状态管理

状态(Buff / Debuff)是为游戏增加策略深度和动态变化的核心机制。

指令原型指令格式应用场景
Add_BuffAdd_Buff(目标, 状态 ID, 持续回合数)附加增益效果
Add_DebuffAdd_Debuff(目标, 状态 ID, 持续回合数)附加减益与规则限制
持续效果(DoT / HoT)Add_Debuff(目标, Bleed, N)回合结束时触发持续伤害

流程控制与交互

这类指令直接干预游戏的基础流程核心交互规则

指令原型指令格式作用应用解析
End_TurnEnd_Turn()回合控制直接结束当前回合,干预游戏流程,用于抢占节奏跳过危险阶段
Prevent_DeathPrevent_Death(恢复到的生命值)死亡豁免生命值即将≤0 时中断死亡结算,强制执行 Modify_Resource 保证单位存活
Block_AttackBlock_Attack(目标, 减免值/次数)伤害格挡 / 防御防御卡核心指令,如抵抗一次攻击,抵消一次 Modify_Resource(Player, HP, ...)
Record_ActionRecord_Action(玩家操作/卡牌行为)行为记录与复现记录玩家动作,后续可触发复现,支撑回响、模仿、回溯类卡牌效果

通过建立一套正交、完备的原子指令集,我们成功地将卡牌效果的“语义”与“执行”分离开。策划不需要关心代码实现,这极大地提升了开发效率,并从根本上保证了系统的一致性和可维护性。

事件总线与触发时机

触发时机定义了什么时候做。一个强大的效果系统,必须有一个清晰、有序、可预测的游戏事件流。我们将这个系统称为事件总线

游戏进程可以被看作一条单向流动的时间线,其上镶嵌着无数个离散的事件节点。卡牌、技能、 Buff 的被动效果,就是注册在这些节点上的“监听器”。当游戏进程到达某个节点时,总线会广播一个事件,所有监听该事件的效果便会依次触发。

本文构建了一个非常典型的、适用于大多数回合制卡牌游戏的时序模型:

游戏 / 关卡初始化阶段

指令原型作用应用实例
On_Game_Start游戏全局启动事件,建立初始全局状态1. 触发天赋树永久属性加成,执行 Modify_Attribute

2. 游戏开始时通过 Card_Replace 修改初始牌库
On_Stage_Start关卡 / 阶段启动事件,初始化当前场景初始化关卡怪物,实体生成时附加初始状态
On_Spawn实体进场 / 生成时触发实体生成时自动执行增益

回合开始 / 刷新阶段

事件原型作用应用实例
On_Turn_Start新回合开始时统一结算,处理资源恢复、持续效果与触发类机制1. 基础抽卡与场面刷新:执行 Card_Refresh

2. 回合开始资源恢复:执行 Modify_Resource(Player, Mana, +X)

3. 持续伤害 / 治疗(DoT / HoT)结算

4. 条件触发效果:On_Turn_Start → If (Def_Cards >= 3) Then …,搭配状态查询(Query)判断防御牌数量

玩家主动阶段

事件原型作用应用实例
On_Card_Play玩家打出卡牌时触发,为核心决策与交互阶段,驱动卡牌主效果执行先执行费用消耗 Cost(Mana, X),选定目标后结算伤害 Modify_Resource(Target, HP, -Y)
Before_Card_Play打出前预处理阶段,用于前置判定、限制与拦截可用于检测是否满足出牌条件、限制目标类型等
On_Cost_Calculation费用计算阶段,专门用于费用修改类效果天赋树的费用减免节点,在此阶段修改费用结果
On_Target_Selection目标选择阶段,用于干预或锁定目标强制指定目标、禁止选择某类目标、自动选择最优目标
After_Card_Play卡牌主效果结算完毕后触发,用于追加额外效果打出进攻卡后,额外执行 Modify_Resource 造成追加伤害

敌人行动 / 回合结束阶段

事件原型作用应用实例
On_Turn_End回合结束统一结算:怪物行动、持续效果、状态计时、手牌管理1. 怪物攻击结算:If (Not_Blocked) Then Modify_Resource(Player, HP, ...)

2. 持续伤害(流血):On_Turn_End → Modify_Resource(Buffed_Monster, HP, -1)

3. Buff / Debuff 持续回合数减 1

反应式阶段

事件原型作用应用实例
On_Take_Damage目标受到伤害时触发,用于反伤、受击回血、护盾判定等玩家受击后回血 Modify_Resource(Player, HP, +X)
On_Entity_Destroyed单位 / 卡牌被摧毁时触发,用于掉落、击杀奖励、连锁效果怪物被击杀后掉落资源
On_Heal目标获得治疗时触发,可用于治疗增幅、触发被动等治疗过量时转化为护盾、治疗后附加增益状态
On_Card_Draw抽牌时触发,用于抽牌加成、追加效果、牌序操控抽牌时额外获得资源、抽特定牌时触发额外效果
链式事件动作触发事件 → 事件触发新动作,形成连锁逻辑On_Card_Play → 伤害 → On_Take_Damage → 击杀 → On_Entity_Destroyed → 获得资源

事件总线将游戏流程转变为一个事件驱动的网状结构。这种设计不仅让逻辑关系一目了然,更重要的是提供了可拓展的挂载点,让新卡牌、新机制的开发变成了实现一个监听器,并将其挂载到正确的事件节点上的简单操作,极大地增强了系统的可扩展性。

核心技术框架

事件总线与分发器

事件总线是一个全局或场景级的单例对象,负责:

  • 事件注册: 任何游戏对象(卡牌、Buff、角色、管理器)都可以向事件总线“订阅”自己感兴趣的事件。
  • 事件广播: 当某个事件发生时(例如,玩家生命值减少),伤害计算模块会调用 EventBus.Publish("On_Take_Damage", damageInfo)
  • 事件分发: 事件总线收到广播后,会遍历所有订阅了 On_Take_Damage 事件的监听器,并调用它们注册的回调函数,将 damageInfo 作为参数传入。

这种设计模式实现了高内聚、低耦合的设计理念。伤害计算模块完全不需要知道卡牌的存在,它只负责广播“我受到了伤害”这一事实。反之,卡牌也不关心伤害是怎么来的,它只关心事件本身。这使得各个功能模块可以独立开发和测试

标签系统

标签系统是实现效果目标筛选和规则判定的关键,它为游戏世界中的每一个对象都贴上了可供查询和分类的元数据

  • 卡牌标签: 这是最基础的应用。例如给玩家附加一个 Silence_DefDebuff,这个 Debuff 会使所有带有【防御类】标签的卡牌无法被打出。
  • 对象标签: 如【怪物】、【道具】。例如“摧毁所有怪物”的内部实现就是 Card_Destroy(Target_With_Tag("Monster"))。这比遍历所有场上实体再判断其类型要高效和优雅得多。
  • 状态标签: 状态本身也可以有标签。例如【流血】标签则告诉回合结束管理器“这个单位需要在回合结束时结算持续伤害”。

标签系统支持了效果的动态查询,为策划提供了极大的灵活性,同时也降低了程序实现新功能的负担。

修饰器链与处理管道

对于游戏中频繁变化的动态数值(如伤害、费用、治疗量),使用修饰器链是一种极其强大的设计模式。它将一个数值的计算过程,从一个简单的赋值语句,变成了一个可被中途拦截和修改的数据处理管道

以伤害计算为例,一个完整的流程应该是这样的:

  1. 原始事件触发: 怪物攻击,意图对玩家造成 5 点伤害。一个 DamageEvent 对象被创建,包含 {Source: Monster, Target: Player, BaseValue: 5}
  2. 进入管道: 这个 DamageEvent 被推入“伤害计算管道”。
  3. 第一层修饰器(减免): 系统检查玩家身上是否有伤害减免效果。该修饰器修改事件对象,{..., BaseValue: 5, FinalValue: 4}
  4. 第二层修饰器(护盾): 系统检查玩家是否有护盾。如果存在,护盾修饰器可能将 FinalValue 直接置为 0,并消耗一层护盾。
  5. 第三层修饰器(易伤): 如果玩家此时有“受到的伤害 + 50%”的 Debuff,另一个修饰器会读取 FinalValue,计算后将其修改为 4 * 1.5 = 6
  6. 管道输出: 事件对象流出管道,最终的 FinalValue 为 6。
  7. 最终执行: 系统执行 Modify_Resource(Player, HP, -6)
  8. 后处理事件: 伤害执行后,广播 On_Take_Damage 事件,此时后处理事件才会响应,进行回血等操作。

这个管道模型同样适用于费用计算等。

修饰器链的优势在于,它将复杂的、顺序敏感的数值计算,分解成了一系列独立、可插拔、可排序的修饰器。增加一个新的减伤 Buff,只需要开发一个新的修饰器并插入到管道的正确位置即可,完全不会影响其他伤害效果的逻辑。

构建一套健壮的卡牌游戏效果系统,其核心在于解耦抽象。通过将庞杂的卡牌描述拆解为正交的原子指令,以标签系统作为目标筛选的锚点,再利用全局事件总线修饰器管道搭建起灵活的事件流与数据流,我们将原本牵一发而动全身的硬编码逻辑,转化为了一套高度模块化、可插拔的规则引擎

这套设计模式不仅大幅降低了后期的开发与维护成本,赋予了策划极大的配置自由度,更为游戏未来海量卡牌机制的平滑迭代与无限 combo 的组合碰撞奠定了坚实的技术基础。

LICENSED UNDER CC BY-NC-SA 4.0
评论