桌游卡牌行为的(反)序列化及ActionCard的useCard方法设计问询
针对桌游卡牌系统的序列化与方法设计解决方案
我之前做过类似的卡牌驱动游戏系统,你的两个核心问题其实可以通过策略模式+多态序列化的组合方案完美解决,彻底摆脱硬编码和方法重载的困境。
一、卡牌效果的无硬编码序列化/反序列化方案
你之前用switch-case处理原子效果的思路本质还是紧耦合,要实现“无需编译新增卡牌”,核心是把每个效果做成独立的可复用组件,用多态来动态加载。
具体实现步骤:
抽象效果接口:定义所有卡牌效果的统一执行入口
public interface CardEffect { // 接收游戏上下文,执行效果 void execute(CardExecutionContext context); }实现具体效果类:每个效果对应一个独立类,比如:
// 抽卡效果 public class DrawCardsEffect implements CardEffect { private int drawCount; // JSON序列化的参数 @Override public void execute(CardExecutionContext context) { context.getCurrentPlayer().drawCards(drawCount); } } // 指定骰子重掷效果 public class RerollTargetDiceEffect implements CardEffect { private List<Integer> targetDiceIndexes; // 要重掷的骰子索引 @Override public void execute(CardExecutionContext context) { context.getGameDice().reroll(targetDiceIndexes); } }用Gson处理多态序列化:
利用Gson的RuntimeTypeAdapterFactory(需要引入Gson扩展库)来实现接口的多态序列化,JSON中通过type字段标识具体效果类:// 配置Gson的多态适配器 RuntimeTypeAdapterFactory<CardEffect> effectAdapter = RuntimeTypeAdapterFactory.of(CardEffect.class, "type") .registerSubtype(DrawCardsEffect.class, "draw_cards") .registerSubtype(RerollTargetDiceEffect.class, "reroll_target_dice") .registerSubtype(IgnorePlacementRuleEffect.class, "ignore_placement_rule"); Gson gson = new GsonBuilder() .registerTypeAdapterFactory(effectAdapter) .create();JSON卡牌示例:
{ "name": "幸运重掷", "cost": 2, "effect": { "type": "reroll_target_dice", "targetDiceIndexes": [0, 2] } }
优势:
- 新增效果只需编写对应的
CardEffect实现类,修改JSON即可,完全不用改动原有代码 - 所有效果逻辑隔离,便于维护和测试
- 效果参数直接序列化到JSON,无需额外硬编码处理
二、ActionCard的useCard()方法设计
针对卡牌有使用条件、需要参数的场景,不要用重载或可变参数,而是用上下文模式统一处理。
具体设计:
封装执行上下文:把所有卡牌执行时需要的信息、参数都放进一个上下文类
public class CardExecutionContext { private Player currentPlayer; private GameState gameState; private Map<String, Object> userInputParams; // 存储玩家输入的动态参数(比如选择的骰子) // getter、setter方法 }扩展ActionCard类:加入使用条件的抽象,统一useCard逻辑
public class ActionCard { private String name; private int cost; private CardEffect effect; private CardUseCondition useCondition; // 可选的使用条件,可为null // 判断卡牌是否可使用 public boolean isUsable(CardExecutionContext context) { if (useCondition == null) { return true; } return useCondition.check(context); } // 统一的卡牌使用方法 public void useCard(CardExecutionContext context) { if (!isUsable(context)) { throw new IllegalStateException("卡牌使用条件不满足"); } // 扣除代币 context.getCurrentPlayer().spendTokens(cost); // 执行效果 effect.execute(context); } }抽象使用条件:和效果一样,把条件做成可序列化的组件
public interface CardUseCondition { boolean check(CardExecutionContext context); } // 示例:需要至少3个代币才能使用的条件 public class HasEnoughTokensCondition implements CardUseCondition { private int requiredTokens; @Override public boolean check(CardExecutionContext context) { return context.getCurrentPlayer().getTokenCount() >= requiredTokens; } }
处理动态参数:
对于需要玩家选择参数的卡牌(比如指定重掷哪个骰子),可以在调用useCard()之前,通过UI引导玩家输入参数,存入CardExecutionContext的userInputParams中,CardEffect实现类从上下文取出参数执行即可。
总结
这套方案完全满足“无需重新编译新增卡牌”的需求:
- 新增卡牌只需编写对应的
CardEffect和CardUseCondition实现类(可作为独立class文件添加) - 卡牌的名称、消耗、效果、条件全部通过JSON配置
- 原有核心代码完全不需要修改,扩展性极强
内容的提问来源于stack exchange,提问作者plumbe0
相关产品推荐
相关产品推荐

