关于Java:使用继承和多态来解决常见的游戏问题

Using inheritance and polymorphism to solve a common game problem

我有两个班,我们叫他们"食人魔"和"巫师"。(所有字段都是公共的,以使示例更易于键入。)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Ogre
{
  int weight;
  int height;
  int axeLength;
}

public class Wizard
{
  int age;
  int IQ;
  int height;
}

在每一个类中,我都可以创建一个名为battle()的方法,该方法将确定如果一个食人魔遇到了一个食人魔,或者一个向导遇到了一个向导,谁会赢。这是一个例子。如果一个食人魔遇到一个食人魔,较重的食人魔获胜。但如果重量相同,斧头越长的人获胜。

1
2
3
4
5
6
7
8
public Ogre battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else if (this.axeLength > o.axeLength) return this;
  else if (this.axeLength < o.axeLength) return o;
  else return this;    // default case
}

我们可以为巫师制作类似的方法。

但是如果一个巫师遇到一个食人魔怎么办?当然,我们可以用一种方法来比较,比如说,身高。

1
2
3
4
5
6
public Wizard battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else return this;
}

我们会为遇见巫师的食人魔做一个类似的。但是如果我们必须向程序中添加更多的字符类型,事情就会失控。

这就是我被卡住的地方。一个明显的解决方案是创建一个具有共同特征的角色类。食人魔和巫师从角色继承并扩展到包含定义每个角色的其他特征。

1
2
3
4
5
6
7
8
9
10
11
public class Character
{
  int height;

  public Character battle(Character c)
  {
    if (this.height > c.height) return this;
    else if (this.height < c.height) return c;
    else return this;
  }
}

有没有更好的方法来组织课堂?我看过策略模式和中介模式,但我不确定它们中的任何一个(如果有的话)在这里能起到什么作用。我的目标是达到某种常见的战斗方法,这样如果一个食人魔遇到一个食人魔,它使用食人魔对食人魔的战斗,但是如果一个食人魔遇到一个巫师,它使用一个更通用的战斗方法。此外,如果遇到的角色没有共同特征呢?我们怎样才能决定谁赢了一场战争?

编辑:很多伟大的回应!我需要消化它们,找出哪一个最适合我的情况。


"访问者模式"是一种将算法与它所操作的对象结构分离的方法。

例如,您可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Character {
    boolean battle(BattleVisitor visitor) {
       return visitor.visit(this);
    }
}

class Ogre extends Character {..}
class Wizard extends Character {..}
class Dwarf extends Character {..}

interface BattleVisitor {
    boolean visit(Ogre character);
    boolean visit(Wizard character);
    boolean visit(Dwarf character);
}

class OgreBattleVisitor implements BattleVisitor {
    private Ogre ogre;
    OgreBattleVisitor(Ogre ogre) { this.ogre = ogre; }
    boolean visit(Ogre ogre) {
      // define the battle
    }

    boolean visit(Wizard wizard) {
      // define the battle
    }
    ...
}

无论何时发生战斗:

1
targetChar.battle(new OgreBattleVisitor(ogre));

为一个向导和一个侏儒定义一个访问者实现,以及出现的任何内容。还要注意,我将visit方法的结果定义为boolean(赢或输),而不是返回获胜者。

因此,在添加新类型时,必须添加:

  • 访问者处理与新类型的冲突的方法。
  • 处理新类型的战斗的实现

现在,事实证明,如果"ogre vs wizard"="wizard vs ogre",您将有一些代码重复。我不知道这是不是真的-例如,根据谁先攻,可能会有不同。另外,你可能想提供完全不同的算法,比如说"与食人魔的沼泽之战"和"与食人魔的村庄之战"。因此,您可以创建一个新的访问者(或访问者的层次结构),并在需要时应用适当的访问者。


如果两个食人魔具有相同的高度/重量和相同的斧头长度,该怎么办?根据你的例子,幸运地被称为第一个会赢。

我不知道这是否是一个合适的选择,但是如果你选择了一个完全不同的方案,并将"战斗分数"归因于每个角色,而不是依赖于比较个人的特征,那该怎么办呢?您可以在公式中使用一个字符的属性来给出可以与另一个字符比较的整数。然后,您可以使用通用的battle方法来比较这两个分数,并将字符返回到较高的分数。

例如,如果一个怪物的"战斗分数"是由他的身高加上他的体重乘以他的斧头长度计算出来的,而一个巫师的分数是由他的年龄乘以他的智商计算出来的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class Character {
    public abstract int battleScore();

    public Character battle(Character c1, Character c2) {
        (c1.battleScore() > c2.battleScore()) ? return c1 : c2;
    }
}

class Ogre extends Character {
    public int battleScore() {
        return (height + weight) * axeLength;
    }
 }

 class Wizard extends Character {
    public int battleScore() {
        return height + (age * IQ);
    }
 }


听起来你要双重派遣。

基本上,你的OgreWizard有一个共同的基础。当从Ogre的基调用battle方法时,它以Wizard的基调用另一个以Ogre为参数的基函数。两个函数调用上的多态行为有效地同时为您提供了两种类型上的多态性。


用类似的方法把战斗逻辑分成自己的类怎么样?

1
Battle(Ogre ogre, Wizard wizard)

它将返回包含获胜者(或获胜者本身)的对象。这将把战斗逻辑与战斗者分离开来,并允许你将其基因化,即:

1
Battle(Creature creat1, Creature creat2)

对于任何没有特定逻辑的生物对(假设巫师/食人魔/等等都将"生物"作为基类),这将是一种回退方法。这将允许您添加/编辑/删除战斗逻辑,而无需修改任何生物本身。


这正是战略所要解决的问题。

让我们回顾一下EDOCX1[0]的部分

  • 背景:战斗
  • 策略:如何你能确定谁会赢吗
  • 具体策略:哪里打做出决定。

所以,不要把责任留给他们自己的角色(因为他们总是说,"我赢了!"!,不我赢了,不我。")你可以创建一个RefereeStrategy

具体实施将决定谁获胜。

行动策略http://bit.ly/cvglb

使用http://yuml.me生成的图表

您可以定义所有字符都同意响应的常用方法(这似乎不像您想要的那样,当所有字符都具有相同的"属性"或方法(如defense():int, attack():int, heal():int)时,这很有用)或执行"盲"策略。

我在做第二个("盲"策略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// All the contenders will implement this.
interface Character {
    public String getName();    
}
// The context
class FightArena {
    Character home;
    Character visitor;

    // The strategy
    Referee referee;

    Character fight(){
        this.referee = RefereeFactory.getReferee( home.getName(), visitor.getName() );
        Character winner = referee.decideFightBetween( home, visitor );
        out.println(" And the winner iiiiss......" + winner.getName() );
    }
}

interface Referee {
    Character decideFightBetween( Character one, Character two );
}

class RefereeFactory {

        static Referee getReferee( Character one, Character two ) {
             .... return the appropiate Refereee...
        }    
}

// Concrete Referee implementation
// Wizard biased referee, dont' trust him
class OgreWizardReferee implements Referee {
    Character decideFightBetween( Character one, Character two ) {
        if( one instanceof Wizard ){
            return one;
        }else{
            return two;
        }
    }
}
class OgreReferee implements Referee {
    Character decideFightBetween( Character one, Character two ) {
        Ogre a = ( Ogre ) one;
        Ogre b = ( Ogre ) two;

        if( a.height > b.height || a.axeLength > a.axeLength ) {
            return a;
        }
        return b;
    }

}

这允许您根据需要插入新的算法(策略-推荐-模式适合的内容)。

它通过将胜利者的决定转发给裁判员,使上下文(您的竞技场)不受"if/else if/else/if/else"构造的影响,并将您的不同角色相互隔离。


我认为你应该重新考虑这件事。

让我们以魔兽世界为例来说明如何进行战斗,因为这是一个众所周知的游戏。

你有许多不同的班级,他们能够做不同的事情,并且有他们自己的优点和缺点。但是,它们都共享一些常见的统计类型。例如,一个法师比一个战士有更多的智力,但是战士将比法师有更多的力量。

那他们到底是怎么战斗的呢?好吧,不管是哪门课,每个角色都有很多可以支配的能力。每种能力都会造成一定的伤害,一旦其中一个角色的生命值降低到0,该角色就会死亡-他们会输掉战斗。

您应该使用类似的方法:定义一个通用的基本类,该类具有适用于所有人的通用属性——比如力量、法术、防御和耐力。然后,当每种类型的角色进行战斗时,他们可以使用一系列的攻击或法术中的任何一种-每种攻击或法术造成的伤害将取决于攻击者和防御者的统计,使用一些合适的公式(有一些随机性来保持有趣,如果一个巫师不可能打败一个O的话,可能就没有乐趣了。GRE,反之亦然)。

但还有一点需要考虑:也许你不应该使用每种类型的类。如果你能为每个人使用相同的公式,那就更好了——即使他们没有相同的能力。不必在其中编写每个能力的代码,您只需要在一个文件中列出能力及其参数,character类将使用它来进行所有这些计算。这样可以更容易地调整公式(只需查看一个位置),也更容易调整功能(只需更改文件)。写这个公式有点困难,因为你可能想给食人魔一个高强度的奖励,而巫师会得到一个高智力的奖励,但它比有x个几乎相同的公式要好,每个属性一个,这会影响输出。


一种方法是为所有字符类型创建一个新的接口,比如

1
2
3
4
public interface Fightable
{
    public Fightable doBattle(Fightable b);
}

然后,您将在每个类中实现dobattle。例如,在Ogre类中,您可以检查b是否是Ogre的实例(在这种情况下执行一件事)、向导(在这种情况下执行另一件事)等…

问题是,每次添加一个新类型时,都需要将代码添加到每一个字符类中,再添加到每一个其他字符中,这并不是特别可维护的。此外,您还必须强调确保操作得到正确维护,即,如果您更改了Ogre类中与向导相关的Dobattle方法,而不是与Ogres相关的向导类中的Dobattle方法,那么无论您是调用anogre.dobattle(a wizard)还是a wizard.dobattle(anogre),结果都会有所不同。

最好是创建一个接受两个字符并包含战斗逻辑的战斗类,通过查看传递给它的两个类类型:这样,每次添加一个新的字符类型时,您只需要更改战斗类!您希望封装最可能经常更改的行为。


为什么不根据怪物的属性为它创造一些价值,而不是试图解决每一场关于怪物类型和其他怪物类型的战斗。

如果它的价值高于它的战斗,它就会获胜。

为了补偿某些敌人对其他敌人的攻击,对每种攻击类型实施某种防御

例如,弓箭手从远处攻击,食人魔是近战,巫师是魔法。怪物有近战防御,远程防御和魔法防御。

然后,可以根据怪物的攻击和敌人各自的盔甲以及生命值等来计算怪物的价值。

所以你不必为一个个案的基础而烦恼。


无论如何,你必须为每一个战斗组合定义独特的逻辑(假设逻辑是唯一的),这与你选择使用的设计模式无关。唯一的要求是将这个逻辑与食人魔和巫师类分开,并在不同的类中创建战斗方法。我认为你现在所做的是完全正确的(一旦你把战斗逻辑转移到其他地方),而不需要一个访客模式,如果这是一个企业游戏,我将使用这个模式:)

别听这些关于设计模式的废话…


我知道这有点晚了,但是几年前史蒂夫·耶格写了一篇关于这个问题的文章(他甚至用了一个游戏例子!).


试试鲍勃叔叔的三重派遣。请参阅我的答案:管理对象间关系


像这样?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class Trait
{
    enum Type
    {
        HEIGHT,
        WEIGHT,
        IQ
    }
    protected Type type;
    protected int value;

    public Trait(int value, Type type)
    {
        this.type = type;
        this.value = value;
    }

    public boolean compareTo(Trait trait)
    {
        if(trait.type != this.type)
            throw new IllegalArgumentException(trait.type+" and"+this.type+" are not comparable traits");
        else
            return this.value - trait.value;
    }
}

class Character
{
    protected Trait[] traits;

    protected Character(Trait[] traits)
    {
        this.traits = traits;
    }

    public Trait getTrait(Trait.Type type)
    {
        for(Trait t : traits)
            if(t.type == type) return t;
        return null;
    }

    public Character doBattleWith(Character that)
    {
        for(Trait thisTrait : traits)
        {
            otherTrait = that.getTrait(thisTrait.type);
            if(otherTrait != null)
            {
                int comp = thisTrait.compareTo(otherTrait);

                if(comp > 0)
                    return this;
                else if (comp < 0)
                    return that;
            }
        }
        return null;
    }
}

class Ogre extends Character
{
    public Ogre(int height, int weight)
    {
        super(new Trait[]{
            new Trait(Type.HEIGHT,height),
            new Trait(Type.WEIGHT,height)});
    }
}

嗯,首先,你的第一个设计不好,因为你让战士决定谁赢。如果你使用调解人,例如提议的战斗等级,你将能够集中战斗逻辑,并且很容易地在一个地方改变任何战斗规则。想象一下有很多生物…一旦你想改变两个人如何一起战斗,你会去哪里寻找战斗方法?在头等舱还是在二等舱?什么超类?所以调解人是个好主意。另一个问题是决定使用哪个规则。您可能很容易遇到多个调度问题。