如何设计一个易于扩展的游戏探索系统


How can I design a Game Quest System that is easily expanded?

解释

假设我们有多个任务,玩家完成任务后会获得奖励。以下示例显示了用户任务:

Name: Get a high score of 100
Requirement: Score of 100
Reward: 10 Coins
Name: Get a high score of 5000
Requirement: Score of 5000
Reward: 10 Diamonds
Name: Destroy 20 ships
Requirement: Destroy 20 ships
Reward: 5 Diamonds
Name: Reach level 5
Requirement: Level 5
Reward: 20 Coins, 10 Diamonds

然后我创建以下逻辑:

创建一个名为"Quest"的超类。为每个任务创建一个子类;继承了"Quest"超类。使用多态性,我们调用"CheckForCompletion"方法来检查是否满足了任务要求。如果是,用户将获得奖励。

问题

我的问题是,我是否要创建一个名为"任务"的数据库表,并将以上每个任务存储在表中?

Quests表数据示例:名称:摧毁20艘船要求:{"销毁"、"20"}奖励:{"钻石"、"5"}如果是,将每个任务加载到相应的类中的最佳方式是什么?

我是否为每个任务创建一个类而不创建任务表?如果是,我怎么知道用户何时完成了任务,这样用户就不会获得两次奖励?

我知道我可以创建一个表并存储必要的需求。然后有一个包含一堆if语句的类来执行正确的代码,但这对我来说似乎不正确;有更好的方法吗?

感谢

编辑1:

每个任务可以有多个需求。例如:摧毁5艘船,杀死10个单位,得分1000等。

为了进一步解释我想要的是消除if语句。我不想做这样的陈述;

if (score == 100)
   //process
else if (ships_destroyed == 5)
   //process
else if (level == 5)
   //process
else if (ships_destroyed == 5 && units_killed == 10 && score == 1000)
   //process

我希望取消这个过程,因为我可能有数百个任务,我认为它扩展得不够容易。那么有更好的方法吗?

您的问题有几个相关的方面,和往常一样,答案是"取决于"。

首先,我建议不要将"Quest"类划分为子类——当然,这不是非黑即白的,但总的来说,我更喜欢组合而不是继承

如果你确信游戏机制不会改变,我就不会麻烦数据库表了——我会在你的编程语言(Java或PHP)中使用常量。从开发和性能的角度来看,在数据库中查找静态数据是非常昂贵的。

如果你认为在调整游戏时,这些数据可能需要更改,那可能太有限了——每次你想调整任务奖励时都要进行完整的构建,这将很难获得正确的平衡。在这种情况下,我会使用一个配置文件——数据确实会改变,但只有当你启动游戏时,而不是在游戏运行时。

如果你的游戏机制要求在游戏中改变奖励,并且这是一个多人游戏,那么你可能需要使用数据库。

最后,大多数游戏引擎都包含了一种满足这种需求的脚本语言——也许对你的计划来说有些过头了,但值得考虑。

我也有类似的要求:在游戏中添加成就/徽章。

你应该考虑一下:

  1. 你将有什么类型的先决条件
  2. 你有什么类型的奖励

前提条件:如果前提条件很简单,比如"摧毁5艘船",那么很容易将其存储到数据库中。如果它很复杂,比如"摧毁5艘船和10个单位",它就不是前提条件的扁平结构。

所以,我可以建议你

  1. 任务描述表:id、名称、描述等等
  2. 先决条件表:quest_id,类型("destroy shop","win battle"…),金额(5,10)
  3. 奖励表:quest_id,money_type,amount。如果每个任务只有一种奖励类型,那么可以将哪些数据移动到第一个表中

如何检查每个条件也是个问题。特别是,如果你要吃很多的话。可能的解决方案之一是添加一些触发器定义。就像"用户刚刚摧毁了飞船","用户刚刚赢得战斗"。并为每个任务定义哪种类型的触发器可以触发它。这些信息也可以存储在单独的表中

UPD:为了检查每个前提条件,每个类型应该有一个java类。

interface Precondition {
    String type()
    boolean check(Map<String, Object> config, Long userId);
}
class DestroyShipPrecondition{
    public String type() {
        return "destroy_ship"
    }
    public boolean check(Map<String, Object> config, Long userId){
        Long expectdAmount = config.get("amount");
        Long realAmount = getDestroyedShipsByUser(userId);
        return expectedAmount<=realAmount;
    }      
}

您的一些服务应该具有按类型划分的所有先决条件的Map。所以,当你需要检查任务时,你加载它的先决条件,然后:

public boolean checkQuest(Quest quest, Long userId){
    for (PreconditionConfig preconditionConfig : quest.getPrecondidtionConfigs() ){
        Precondition precondition = precondititons.get(preconditionConfig.type);
        if (!precondition.check(preconditionConfig.getContext(), userId)){
            return false;
        }
    }
    return true;
}

TL;DR将您的逻辑存储为枚举中的lambdas,并使用您的游戏状态作为输入。

一个非常直率的解决方案是使用枚举。。。使用枚举并使用lambda加载它,如下面的代码示例所示。

为了密度起见,我把它放在一个类中,但你可能会看到我要用它去哪里…

稍后,您可以将触发器和操作隔离到一个单独的枚举中,向该枚举添加标识符,然后使用这些标识符在数据库中将它们链接在一起。添加参数字符串来定义所需的确切分数,您可能已经获得了所需的所有灵活性。。

我选择让每个游戏循环都检查条件。但将检查附加到游戏状态的某些变化上更有效,例如在setPlayerScore方法结束时。

public class Game {
  public static class GameState {
    public int playerscore = 1750;
  }
  public enum Quest {
    FIVE_HUNDRED_PT(
      (GameState gamestate) -> gamestate.playerscore > 500,
      () -> Game.trigger500pointHurray()
    ),
    FIVE_THOUSAND_PT(
      (GameState gamestate) -> gamestate.playerscore > 5000,
      () -> Game.trigger5000pointHurray()
    );
    private final Function<GameState, Boolean> trigger;
    private final Runnable action;
    Quest(Function<GameState, Boolean> trigger, Runnable action) {
      this.trigger = trigger;
      this.action = action;
    }
  }
  public static void gameloop(GameState state) {
    // Do all game logic.
    for (Quest q : Quest.values())
      if (q.trigger.apply(state))
        q.action.run();
  }
  public static void trigger500pointHurray() {
    System.out.println("Hurray x500");
  }
  public static void trigger5000pointHurray() {
    System.out.println("Hurray x5000");
  }
  public static void main(String[] args) {
    gameloop(new GameState());
  }
}