使用inheritance和多态来解决常见的游戏问题
我有两节课; 让我们称他们为食人魔和巫师。 (所有字段都是公开的,以便更容易输入示例。)
public class Ogre { int weight; int height; int axeLength; } public class Wizard { int age; int IQ; int height; }
在每个类中,我都可以创建一个名为battle()的方法,该方法将确定如果Ogre遇到Ogre或者向导遇到向导,谁将获胜。 这是一个例子。 如果食人魔遇到食人魔,那么较重的食人魔会获胜。 但是如果重量是相同的,那么具有更长轴的那个获胜。
public Ogre battle(Ogre o) { if (this.height > o.height) return this; else if (this.height o.axeLength) return this; else if (this.axeLength < o.axeLength) return o; else return this; // default case }
我们可以为奇才制作类似的方法。
但是如果巫师遇到食人魔怎么办? 我们当然可以为此做一个方法,比较一下,比如高度。
public Wizard battle(Ogre o) { if (this.height > o.height) return this; else if (this.height < o.height) return o; else return this; }
而且我们会为符合巫师的Ogres做一个类似的。 但是如果我们必须在程序中添加更多字符类型,事情就会失控。
这是我被卡住的地方。 一个显而易见的解决方案是创建具有共同特征的Character类。 Ogre和Wizard从Characterinheritance并扩展它以包含定义每个特征的其他特征。
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; } }
有没有更好的方法来组织课程? 我查看了策略模式和中介模式,但我不确定它们中的任何一个(如果有的话)可以在这里提供帮助。 我的目标是达到某种常见的战斗方法,这样如果Ogre遇到食人魔它会使用Ogre-vs-Ogre战斗,但如果食人魔遇到向导,它会使用更通用的。 此外,如果遇到的人物没有共同的特征怎么办? 我们如何决定谁赢得了一场战斗?
编辑:很多很棒的回复! 我需要消化它们并找出哪种方法最适合我的情况。
访问者模式 “是一种将算法与其操作的对象结构分离的方式”。
举个例子,你可以拥有
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 } ... }
每当发生战斗时:
targetChar.battle(new OgreBattleVisitor(ogre));
定义向导和矮人的访客实现以及出现的任何内容。 另请注意,我将visit
方法的结果定义为boolean
(赢或输),而不是返回获胜者。
因此,添加新类型时,您必须添加:
- 访问者处理新类型的方法。
- 处理新类型战斗的实现
现在,事实certificate,如果“Ogre vs Wizard”==“Wizard vs Ogre”,你将会有一些重复的代码。 我不知道是否是这种情况 – 例如,根据谁先打击,可能会有所不同。 此外,你可能想要提供完全不同的算法,比如说“沼泽与食人魔的战斗”与“与食人魔的村庄战斗”相比。 因此,您可以创建新访问者(或访问者层次结构)并在需要时应用适当的访问者。
在两个食人魔具有相同的身高/体重和相同的斧长的情况下会发生什么? 根据你的例子,那个幸运的人可以获得第一名 。
我不知道这是否是一个合适的选择,但如果你选择了一个完全不同的方案并将“战斗得分”归因于每个角色而不是依赖于比较个体特征,那该怎么办呢? 您可以在公式中使用字符的属性来提供一些可以与另一个字符进行比较的整数。 然后你可以使用通用battle
方法来比较两个分数并将角色返回到较高分数。
例如,如果食人魔的“战斗分数”是通过他的身高加上他的体重乘以他的斧头长度和巫师的分数来计算的,那么他的年龄乘以他的智商来计算呢?
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); } }
听起来你想要双重调度 。
基本上你的Ogre
和Wizard
会(可能)有一个共同的基础。 当你从Ogre
的基地调用battle
方法时,它将在Wizard
的基础上调用并在该基础上调用另一个以Ogre
为参数的函数。 两个函数调用的多态行为有效地同时为这两种类型提供了多态性。
如何将战斗逻辑分离到自己的类中,使用类似的方法
Battle(Ogre ogre, Wizard wizard)
哪个会返回一个包含获胜者的对象(或者胜者本身,无论如何)。 这会将战斗逻辑与战斗者分开,并且还允许你生成,即:
Battle(Creature creat1, Creature creat2)
对于任何生物配对都是一种后备方法(假设Wizard / Ogre / etc都有’Creature’作为基类)没有特定的逻辑。 这将允许您添加/编辑/删除战斗逻辑,而无需修改任何生物本身。
我想你应该重新思考这一切。
让我们把魔兽世界作为战斗如何完成的一个例子,仅仅因为它是一个众所周知的游戏。
你有许多不同的课程,能够做不同的事情,并有自己的优点和缺点。 但是,它们都共享一些常见的统计类型。 例如,法师的智力高于战士,但战士的力量将比法师强很多。
那么他们如何实际战斗呢? 好吧,无论class级如何,每个角色都有许多能力可供他们使用。 每个技能都会造成一定程度的伤害,一旦其中一个角色的HP下降到0,那个角色就会死亡 – 他们就会失去战斗力。
您应该使用类似的方法:定义一个具有适用于每个人的共同属性的公共基类 – 诸如力量,法术能力,防御和耐力之类的东西。 然后,当每种类型的角色进行战斗时,他们可以使用任何一系列攻击或法术 – 每次攻击或法术造成的伤害将取决于攻击者和防御者的统计数据,使用一些合适的公式(具有一些随机性)为了保持它的趣味性,如果巫师不可能击败食人魔,反之亦然,这可能就没那么有趣了。
但是这里还有其他需要考虑的事情:也许你不应该为每种类型使用一个类。 如果你能为每个人使用相同的公式,那就更好了 – 即使他们没有相同的能力。 你不必在那里编写每个能力的代码,而只需要在文件中列出一个能力及其参数列表,而Character类将使用它来完成所有这些计算。 这样可以更容易地调整公式(只有一个地方可以查看),并且更容易调整能力(只需更改文件)。 写这个公式有点困难,因为你可能想给予食人魔一个高强度的奖励,而巫师会获得高智力的奖金,但它比拥有X几乎相同的公式更好,每个一个可以影响输出的stat。
这正是战略旨在解决的问题。
让我们回顾一下Strategy
的各个部分
- 背景:战斗
- 策略:您如何定义哪个会获胜
- 具体战略:战斗发生的地方并作出决定。
所以,不要把这个责任留给自己的角色(因为他们总是说, “我赢了!!,不,我赢了,不,我……” )你可以创建一个RefereeStrategy
。
具体实施将决定谁获胜。
行动战略http://bit.ly/cvvglb
使用http://yuml.me生成的图表
您可以定义所有字符同意响应的常用方法(这看起来不像您想要的,当所有字符具有相同的“属性”或者类似defense():int, attack():int, heal():int
方法时,这很有用defense():int, attack():int, heal():int
)或做“盲”策略。
我正在做第二个(“盲目”策略)
// 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 / elseif / else / if / else”构造,并将你的不同角色彼此隔离 。
一种方法是为所有字符类型创建一个新接口
public interface Fightable { public Fightable doBattle(Fightable b); }
然后从那里开始在每个类中实现doBattle。 例如,在Ogre类中,你可以检查b是否是Ogre的一个实例(在这种情况下做一件事),一个向导(在这种情况下是另一个)等等……
问题在于,每次添加新类型时,都需要将代码添加到每个字符的每个字符类别,这不是特别可维护的。 此外,您必须强调确保操作得到正确维护,即如果您在Ogre类中更改了关于向导的doBattle方法,而不是在关于Ogres的Wizard类中,则可能会出现这样的情况:无论你调用anOgre.doBattle(aWizard)还是aWizard.doBattle(anOgre),结果都会有所不同。
更好的方法是创建一个接受两个角色的Battle类,并通过查看哪两个类类型被传递给它来包含战斗逻辑:这样,每次新的角色类型时你只需要更改你的Battle类被添加! 您希望封装最有可能最常更改的行为。
无论如何,你必须为每一个战斗组合定义独特的逻辑(假设逻辑是唯一的) – 你选择使用什么样的设计模式并不重要。 唯一的要求是将此逻辑与Ogre和Wizard类分开,并在不同的类中创建战斗方法。 我认为你现在正在做的事情完全没问题(一旦你将战斗逻辑移到其他地方)而不需要访问者模式,如果这是一些企业游戏,我将使用它:)
不要听设计模式的所有这些漏洞……
为什么不根据它的属性为怪物创造一些价值,而不是试图解决每种类型的怪物与其他类型的怪物的战斗。
如果它的价值高于它的战斗价值,它就赢了。
为了补偿某些敌人更好地对抗其他敌人,为每种攻击类型实施某种防御
例如射手攻击范围,食人魔是近战,巫师是魔法。 食人魔具有近战防御,远程防御和魔法防御。
然后可以根据它的攻击和敌人各自的装甲,以及HP等等来计算怪物的价值。
因此,您不必根据具体情况而烦恼。
嗯,首先,你的第一个设计并不好,因为你让战士决定谁赢了。 如果您使用Mediator,例如类似于提议的Battle类,您将能够集中战斗逻辑并轻松地在一个地方更改任何战斗规则。 想象一下,有很多生物……一旦你想要改变两个人在一起战斗的方式,你会去哪里寻找战斗方法? 在第一课还是第二课? 一些超类? 所以Mediator是个好主意。 另一个问题是决定使用哪个规则。 您可能很容易遇到多个调度问题。
像这样的东西?
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)}); } }
试试叔叔鲍勃的三重派遣。 请参阅我的回答: 管理对象间关系
我知道这有点晚了,但几年前Steve Yegge写了几篇关于这个确切问题的文章(他甚至用过一个游戏示例!)。