You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

桌游卡牌行为的(反)序列化及ActionCard的useCard方法设计问询

针对桌游卡牌系统的序列化与方法设计解决方案

我之前做过类似的卡牌驱动游戏系统,你的两个核心问题其实可以通过策略模式+多态序列化的组合方案完美解决,彻底摆脱硬编码和方法重载的困境。


一、卡牌效果的无硬编码序列化/反序列化方案

你之前用switch-case处理原子效果的思路本质还是紧耦合,要实现“无需编译新增卡牌”,核心是把每个效果做成独立的可复用组件,用多态来动态加载。

具体实现步骤:

  1. 抽象效果接口:定义所有卡牌效果的统一执行入口

    public interface CardEffect {
        // 接收游戏上下文,执行效果
        void execute(CardExecutionContext context);
    }
    
  2. 实现具体效果类:每个效果对应一个独立类,比如:

    // 抽卡效果
    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);
        }
    }
    
  3. 用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();
    
  4. JSON卡牌示例:

    {
      "name": "幸运重掷",
      "cost": 2,
      "effect": {
        "type": "reroll_target_dice",
        "targetDiceIndexes": [0, 2]
      }
    }
    

优势:

  • 新增效果只需编写对应的CardEffect实现类,修改JSON即可,完全不用改动原有代码
  • 所有效果逻辑隔离,便于维护和测试
  • 效果参数直接序列化到JSON,无需额外硬编码处理

二、ActionCard的useCard()方法设计

针对卡牌有使用条件、需要参数的场景,不要用重载或可变参数,而是用上下文模式统一处理。

具体设计:

  1. 封装执行上下文:把所有卡牌执行时需要的信息、参数都放进一个上下文类

    public class CardExecutionContext {
        private Player currentPlayer;
        private GameState gameState;
        private Map<String, Object> userInputParams; // 存储玩家输入的动态参数(比如选择的骰子)
        // getter、setter方法
    }
    
  2. 扩展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);
        }
    }
    
  3. 抽象使用条件:和效果一样,把条件做成可序列化的组件

    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:33:21