我该如何避免在整个代码中出现一次性标记和检查?


18

考虑一个纸牌游戏,例如《炉石传说》

有成百上千种卡片可以做各种各样的事情,其中​​有些甚至对于一张卡片来说都是独一无二的!例如,有一张牌(称为Nozdormu)可将玩家回合减少到仅15秒!

当您具有如此广泛的潜在影响时,如何在整个代码中避免幻数和一次性检查?如何避免PlayerTurnTime类中的“ Check_Nozdormu_In_Play”方法?以及如何组织代码,使得当您添加更多效果时,您无需重构核心系统即可支持以前从未支持过的东西?


这真的是性能问题吗?我的意思是,你可以做几乎没有时间与现代CPU的东西疯狂的数额..
杰瑞Komppa

11
谁说过性能问题?我会看到的主要问题是,每次制作新卡时,始终需要调整所有代码。
2015年

2
因此,添加脚本语言并为每张卡编写脚本。
Jari Komppa 2015年

1
没有时间做出正确的答案,但是可以在处理玩家转弯的“ PlayerTurnTime”类代码中进行例如Nozdormu检查和15秒的调整,而可以编写“ PlayerTurnTime”类来调用[class-,如果需要]功能从外部在特定点提供。然后Nozdormu卡代码(以及需要影响同一平面的所有其他卡)可以实现用于该调整的函数,并在需要时将该函数注入PlayerTurnTime类。从经典的《设计模式》一书中了解策略模式和依赖项注入可能会很有用
Peteris 2015年

2
在某个时候,我不得不怀疑是否将临时检查添加到代码的相关位是否是最简单的解决方案。
user253751

Answers:


12

您是否研究了实体组件系统和事件消息传递策略?

状态效果应该是某种类型的组件,可以在OnCreate()方法中应用其持久性效果,在OnRemoved()中使它们的效果失效,并订阅游戏事件消息以应用对某些事件的反应而产生的效果。

如果效果是永久性的(持续X圈,但仅在某些情况下适用),则您可能需要在各个阶段检查这些条件。

然后,只需确保您的游戏也没有默认的幻数。确保可以更改的所有内容都是数据驱动变量,而不是带有用于任何异常的变量的硬编码默认值。

这样,您永远不会假设转弯长度将是多少。它始终是一个不断检查的变量,可以通过任何效果更改,并且在过期时可以通过效果撤消。在默认使用幻数之前,您永远不会检查异常。


2
“确保可以更改的所有内容都是数据驱动变量,而不是带有用于任何异常的变量的硬编码默认值。” -哦,我很喜欢那样。我认为这很有帮助!
Sable Dreamer,2015年

您能否详细说明“应用其持久影响”?是否要订阅turnStarted然后更改Length值会使代码不可调试,甚至更糟地产生不一致的结果(当在相似效果之间进行交互时)?
温德拉

仅适用于将采用任何给定时间跨度的订户。您必须仔细建模。使当前的轮换时间与玩家的轮换时间不同可能会更好。将检查PTT以创建新的转弯。可以通过卡检查CTT。如果效果应增加当前时间,则计时器UI如果是无状态的,则自然应遵循。
RobStone

为了更好地回答这个问题。没有其他东西可以存储周转时间或基于此的任何东西。经常检查。
RobStone

11

RobStone走在正确的轨道上,但是我想详细说明一下,因为这正是我写《地下城与地下城》时所做的事情,《地下城与地下城》拥有非常复杂的武器和法术效果系统。

每张卡都应附有一组效果,其定义方式可以指示效果是什么,目标是什么,作用时间和持续时间。例如,“损害对手”的效果可能看起来像这样。

Effect type: deal damage (enumeration, string, what-have-you)
Effect amount: 20
Source: my weapon
Target: opponent
Effect Cost: 20
Cost Type: Mana

然后,当效果触发时,让通用例程处理效果的处理。像个白痴一样,我使用了一个巨大的case / switch语句:

switch (effect_type)
{
     case DAMAGE:

     break;
}

但是,通过多态性是一种更好,更模块化的方法。创建一个包装所有这些数据的效果类,为每种类型的效果创建一个子类,然后使该类重写特定于该类的onExecute()方法。

class Effect
{
    Object source;
    int amount;

    public void onExecute(Object target)
    {
          // Do nothing
    }
}

class DamageEffect extends Effect
{
    public void onExecute(Object target)
    {
          target.health -= amount;
    }
}

因此,我们将拥有一个基本的Effect类,然后是一个具有onExecute()方法的DamageEffect类,因此在我们的处理代码中,我们将继续;

Effect effect = card.getActiveEffect();

effect.onExecute();

知道正在发生什么的方法是创建一个Vector / Array /链表/等。附加到任何对象(包括运动场/“游戏”)的活动效果(效果类型,基类)的数据,因此不必检查是否正在播放特定效果,只需遍历附加到该对象的所有效果对象并让它们执行。如果效果未附加到对象上,则说明该效果不起作用。

Effect effect;

for (int o = 0; o < objects.length; o++)
{
    for (int e = 0; e < objects[o].effects.length; e++)
    {
         effect = objects[o].effects[e];

         effect.onExecute();
    }
}

这正是我做到的方式。这样做的好处是,您实际上拥有一个数据驱动的系统,并且可以在每个效果的基础上轻松调整逻辑。通常,您必须在效果的执行逻辑中进行一些条件检查,但由于这些检查仅针对所涉及的效果,因此它的一致性仍然更高。
manabreak

1

我将提供一些建议。其中一些相互矛盾。但是也许有些有用。

考虑列表与标志

您可以遍历整个世界并检查每个项目上的标志,以决定是否执行标志操作。或者,您可以仅保留那些应该做标记的项目的列表。

考虑列表和枚举

您可以继续向项目类isAThis和isAThat添加布尔字段。或者,您可以具有字符串或枚举元素的列表,例如{“ isAThis”,“ isAThat”}或{IS_A_THIS,IS_A_THAT}。这样,您可以在枚举(或字符串const)中添加新项,而无需添加字段。并不是说添加字段确实有什么问题...

考虑函数指针

除了标志或枚举的列表外,还可以具有要在不同上下文中对该项目执行的动作的列表。(像实体的…)

考虑对象

有些人更喜欢数据驱动的,脚本化的或组件实体的方法。但是老式的对象层次结构也值得考虑。基类需要接受动作,例如“在B相阶段使用这张牌”或其他动作。然后,每种卡都可以覆盖并适当地响应。可能还存在一个玩家对象和一个游戏对象,因此游戏可以执行以下操作:if(player-> isAllowedToPlay()){做游戏…}。

考虑调试能力

关于一堆标记字段的一件好事是,您可以以相同的方式检查和打印每个项目的状态。如果状态由不同的类型,成组的组件,功能指针或位于不同的列表中表示,则仅查看项目的字段可能不够。这都是权衡。

最终,重构:考虑单元测试

无论您对体系结构进行多少概括,您都可以想象它没有涵盖的内容。然后,您必须进行重构。也许一点,也许很多。

一种更安全的方法是进行单元测试。这样,您可以确信,即使重新布置了下面的内容(也许很多!),现有功能仍然有效。通常,每个单元测试如下:

void test1()
{
   Game game;
   game.addThis();
   game.setupThat(); // use primary or backdoor API to get game to known state

   game.playCard(something something).

   int x = game.getSomeInternalState;
   assertEquals(“did it do what we wanted?”, x, 23); // fail if x isn’t 23
}

如您所见,保持游戏(或玩家,纸牌和&c)上的顶级API调用稳定是单元测试策略的关键。


0

与其单独考虑每张卡,不如开始考虑效果的类别,而卡包含一个或多个这些类别。例如,要计算转牌的时间,您可以遍历所有正在玩的牌并检查包含该类别的每张牌的“操纵转轮持续时间”类别。然后,每张卡都会根据您确定的规则增加或覆盖转弯持续时间。

这本质上是一个微型组件系统,其中每个“卡片”对象都只是一堆效果组件的容器。


由于这些卡(以及将来的卡)几乎可以执行任何操作,因此我希望每张卡都带有脚本。不过我敢肯定这是不是一个真正的性能问题..
杰瑞Komppa

4
根据主要评论:除您之外,没有人说过有关性能问题的任何事情。至于完整脚本的替代方法,请在答案中进行详细说明。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.