Java如何创建可接收任意类型参数的游戏任务目标类
游戏任务Objective类实现方案
针对不同类型任务目标参数不一致的问题,推荐两种经过线上游戏插件验证的实现方式,兼顾类型安全、运行性能和扩展性:
方案1:泛型+枚举类型绑定(完全兼容现有调用写法,性能最优)
这个方案不需要修改你写的构造调用代码,从根源避免参数类型传错的问题,几乎没有额外运行时开销,最适合对tick性能敏感的游戏服务端场景。
首先给ObjectiveType枚举绑定每个类型对应的参数Class,从枚举层面明确类型和参数的对应关系:
public enum ObjectiveType { TALK(NPC.class), KILL_ENTITY(Entity.class), COLLECT_ITEM(Item.class), CRAFT_ITEM(Item.class); public final Class<?> targetClass; ObjectiveType(Class<?> targetClass) { this.targetClass = targetClass; } }
然后实现带泛型的Objective类,构造时做一次轻量类型校验,后续读取目标参数不需要反复强转:
public class Objective<T> { private final ObjectiveType type; private final T target; private final int requiredCount; private int currentProgress = 0; // 单数量目标构造,完全匹配你给出的示例写法 public Objective(ObjectiveType type, T target) { this(type, target, 1); } // 支持多数量目标构造,比如收集10个物品、击杀5个怪物 public Objective(ObjectiveType type, T target, int requiredCount) { // 初始化时直接校验参数类型,传错立刻抛异常,避免线上运行时才出类型转换错误 if (!type.targetClass.isInstance(target)) { throw new IllegalArgumentException(String.format( "目标参数不匹配:%s类型要求传入%s,实际传入%s", type.name(), type.targetClass.getSimpleName(), target.getClass().getSimpleName() )); } this.type = type; this.target = target; this.requiredCount = requiredCount; } // 常用方法封装 public ObjectiveType getType() { return type; } public T getTarget() { return target; } public boolean isComplete() { return currentProgress >= requiredCount; } // 进度更新,事件监听器触发对应行为时直接调用即可 public void addProgress(int count) { currentProgress = Math.min(currentProgress + count, requiredCount); } }
你原来的示例代码可以直接运行,不需要任何修改:
// 创建和ID为10的NPC对话的目标 Objective talkObjective = new Objective(ObjectiveType.TALK, new NPC(10)); // 创建收集1个僵尸精华的目标 Objective collectObjective = new Objective(ObjectiveType.COLLECT_ITEM, new Item(ZOMBIE_ESSENCE));
方案2:密封子类拆分(逻辑内聚,适合复杂目标场景)
如果后续不同任务目标需要独立的匹配逻辑,比如对话目标要校验对话节点、击杀目标要校验击杀武器/击杀环境,就把Objective改成密封抽象类,每个目标类型单独做子类实现,避免在事件监听器里写大量分支判断:
// 密封父类,只允许规定的目标类型子类继承,从语法层面限制非法扩展 public abstract sealed class Objective permits TalkObjective, KillEntityObjective, CollectItemObjective, CraftItemObjective { protected final ObjectiveType type; protected final int requiredCount; protected int currentProgress = 0; protected Objective(ObjectiveType type, int requiredCount) { this.type = type; this.requiredCount = requiredCount; } // 每个子类自己实现事件匹配逻辑,外部不需要判断类型 public abstract boolean matchEvent(Object gameEvent); public boolean isComplete() { return currentProgress >= requiredCount; } public void addProgress(int count) { currentProgress = Math.min(currentProgress + count, requiredCount); } } // 对话目标子类 public final class TalkObjective extends Objective { private final NPC targetNpc; public TalkObjective(NPC npc) { super(ObjectiveType.TALK, 1); this.targetNpc = npc; } @Override public boolean matchEvent(Object gameEvent) { return gameEvent instanceof NpcTalkEvent e && e.getNpc().id() == targetNpc.id(); } } // 收集物品子类 public final class CollectItemObjective extends Objective { private final Item targetItem; public CollectItemObjective(Item item, int count) { super(ObjectiveType.COLLECT_ITEM, count); this.targetItem = item; } @Override public boolean matchEvent(Object gameEvent) { return gameEvent instanceof ItemPickupEvent e && e.getItem().isSameType(targetItem); } }
这个方案的优势是所有目标相关的判断逻辑都内聚在对应子类里,新增目标类型时不需要修改原有核心逻辑,符合开闭原则,缺点是初始化时需要实例化对应子类,不能统一用new Objective(类型, 参数)的写法。
避坑提示
- 不要直接用
Object类型存储目标参数,虽然写起来省事,但后续读取时会产生大量强转代码,很容易触发隐蔽的类型转换异常,也会带来不必要的运行时开销 - 进度更新、完成判断的逻辑尽量封装在
Objective类内部,不要散落在各个事件监听器里,避免重复计算和逻辑不一致 - 如果需要把任务数据持久化到数据库,两种方案都可以很方便地实现序列化/反序列化逻辑,不需要额外做复杂的类型适配
内容的提问来源于stack exchange,提问作者Quib
相关产品推荐
相关产品推荐

