如何设计易于扩展的游戏任务系统?

时间:2015-10-26 11:13:54

标签: java php oop database-design

解释

假设我们有多个任务可以在完成某些事情时奖励玩家。以下示例显示了用户任务:

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艘船     要求:{“Destroy”,“20”}     奖励:{“Diamonds”,“5”} 如果是这样,将每个任务加载到适当的类中的最佳方法是什么?

OR

我是否在不创建任务表的情况下为每个任务创建一个类?如果是这样,我怎么知道用户何时完成了任务,以便用户没有获得两次奖励?

我知道我可以创建一个表并存储必要的要求。然后有一个带有一堆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

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

3 个答案:

答案 0 :(得分:3)

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

首先,我建议不要继承你的" Quest"当然,这不是黑白分明,但总的来说,我赞成composition over inheritance

如果您确信游戏机制不会发生变化,我就不会理解数据库表 - 我会在您的编程语言(Java或PHP)中使用常量。从开发和性能的角度来看,在数据库中查找静态数据是很昂贵的。

如果您认为这些数据可能需要随着您调整游戏而改变,那可能会限制太多 - 每次想要调整任务的奖励时都要进行完整的构建将很难获得平衡权利。在这种情况下,我使用配置文件 - 数据确实会发生变化,但只有在您开始游戏时才会发生变化,而不是在它运行时。

如果您的游戏机制需要在游戏过程中更改奖励,并且它是多人游戏,那么您可能想要使用数据库。

最后,对于这种要求,大多数游戏引擎include a scripting language - 对于你正在计划的事情可能有点过分,但值得考虑。

答案 1 :(得分:1)

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

你应该考虑:

  1. 你将拥有什么类型的先决条件
  2. 你有什么类型的奖励。
  3. 前提条件: 如果前提条件很简单,就像'破坏5艘船'一样,很容易将它存储到db。如果它很复杂,比如“摧毁5艘船和10艘船”,那就不是前置条件的扁平结构了。

    所以,我可以建议你

    1. 任务描述表:id,name,description等。
    2. 前提条件表:quest_id,类型('销毁店铺','赢得战斗'......),金额(5,10)。
    3. 奖励表:quest_id,money_type,金额。如果每个任务只有一种类型的奖励,那么哪些数据可以移动到第一个表格
    4. 如何检查每个条件也是个问题。特别是,如果你有很多这样的话。 一种可能的解决方案,添加一些触发器定义。就像'用户刚刚摧毁了船','用户只是赢得战斗'。并为每个任务定义哪种类型的触发器可以触发它。该信息也可以存储在单独的表中

      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;
          }      
      }
      

      您的某些服务应该按类型包含所有前提条件的映射。所以,当你需要检查任务时,你加载它的先决条件,然后:

      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;
      }
      

答案 2 :(得分:0)

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

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

为了密度,我把它全部放在一个班级,但你可能会看到我要去哪里......

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

我选择让每个游戏循环检查条件。但是将检查附加到游戏状态中的某些更改会更有效,例如在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());
  }
}