解释
假设我们有多个任务,玩家完成任务后会获得奖励。以下示例显示了用户任务:
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)中使用常量。从开发和性能的角度来看,在数据库中查找静态数据是非常昂贵的。
如果你认为在调整游戏时,这些数据可能需要更改,那可能太有限了——每次你想调整任务奖励时都要进行完整的构建,这将很难获得正确的平衡。在这种情况下,我会使用一个配置文件——数据确实会改变,但只有当你启动游戏时,而不是在游戏运行时。
如果你的游戏机制要求在游戏中改变奖励,并且这是一个多人游戏,那么你可能需要使用数据库。
最后,大多数游戏引擎都包含了一种满足这种需求的脚本语言——也许对你的计划来说有些过头了,但值得考虑。
我也有类似的要求:在游戏中添加成就/徽章。
你应该考虑一下:
- 你将有什么类型的先决条件
- 你有什么类型的奖励
前提条件:如果前提条件很简单,比如"摧毁5艘船",那么很容易将其存储到数据库中。如果它很复杂,比如"摧毁5艘船和10个单位",它就不是前提条件的扁平结构。
所以,我可以建议你
- 任务描述表:id、名称、描述等等
- 先决条件表:quest_id,类型("destroy shop","win battle"…),金额(5,10)
- 奖励表: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());
}
}