具有许多属性的Hero的OOP架构


14

我将开始一个简单的浏览器文本RPG,其字符可以(被动地)与其他人抗衡。这涉及大约10种技能的列表,例如力量,灵巧性等,另外还具备使用不同武器的能力。

是否只有将这些技能作为类属性来设计这个角色类,还有更好的方法?看起来很简单,但是我很不情愿,因为它很笨拙。

class Char(self):
    int strength
    int dexterity
    int agility
    ...
    int weaponless
    int dagger
    ...

1
您应该查看有关编写游戏的全部指南,以及某些常见类的链接
龙2014年

@dragons感谢您提供有趣的链接,但是在设计Charactor类时我没有更深入的解释吗?
Sven 2014年

1
您究竟发现该设计“笨拙”的是什么?
2014年

Answers:


17

只要您使系统保持相对简单,它就可以工作。但是,当您添加诸如临时技能修改器之类的内容时,您很快就会看到很多重复的代码。您还将遇到使用不同熟练度使用不同武器的问题。因为每个技能是一个不同的变量,所以您将必须为每种技能类型编写不同的代码,而这些代码基本上可以完成相同的工作(或使用一些丑陋的反射技巧,在您的编程语言支持它们的条件下)。

因此,我建议您将技能和熟练程度都存储在将技能常数映射到值的关联数据结构中。优雅地做到这一点的方法因编程语言而异。如果您的语言支持,则常量应位于enum

举个例子,在实践中,如何计算攻击力的代码如下所示:

int damage = attacker.getSkill(STRENGTH) + 
             attacker.getProficiency(weapon.getProficiencyRequired()) -
             defender.getSkill(TOUGHNESS);

创建类似这样的方法getSkill()违反了面向对象编程基本原理
杰斐尔

4

为什么不使用关联数组?这带来了易于扩展的优势(例如,使用PHP)

$Stats["Strength"] = "8";
$Stats["Dexterity"] = "8";

对于诸如武器之类的东西,您可能想要创建一些基类

武器->近战武器,远程武器

然后从那里制造武器。

我想要的最终结果是一个看起来像这样的课程

class Character
{
    public $Stats;
    public $RightHand;
    public $LeftHand;
    public $Armor;
    public $Name;
    public $MaxHealth;
    public $CurrentHealth;

    public function __construct()
    {
        //Basic
        $this->Name = "Fred";
        $this->MaxHealth = "10";
        $this->CurrentHealth = "10";

        //Stats
        $this->Stats["Strength"] = 8;
        $this->Stats["Dexterity"] = 8;
        $this->Stats["Intellect"] = 8;
        $this->Stats["Constitution"] = 8;

        //Items
        $this->RightHand = NULL;
        $this->LeftHand  = NULL;
        $this->Armor = NULL;

    }
}

如果确实需要,也可以将所有内容存储在数组中。


@Philipp说的差不多了吗?
AturSams 2014年

1
@Philipp建议使用枚举,数组是另一种选择。
Grimston

它实际上说:“ ...将技能常量映射到值的关联数据结构”,字典中的常量可以是字符串。
AturSams 2014年

3

我将尝试以最OOP的方式回答这个问题(或者至少是我认为的那样)。根据您看到的有关统计数据的演变,这可能完全是过分杀伤力的。

您可以想象一个SkillSet(或Stats)类(我在此答案中使用类似C的语法):

class SkillSet {

    // Consider better data encapsulation
    int strength;
    int dexterity;
    int agility;

    public static SkillSet add(SkillSet stats) {
        strength += stats.strength;
        dexterity += stats.dexterity;
        agility += stats.agility;
    }

    public static SkillSet apply(SkillModifier modifier) {
        strength *= modifier.getStrengthModifier();
        dexterity *= modifier.getDexterityModifier();
        agility *= modifier.getAgilityModifier();

    }

}

然后,英雄将具有SkillSet类型的internalStats字段。武器也可以具有修改器skillSet。

public abstract class Hero implements SkillSet {

    SkillSet intrinsicStats;
    Weapon weapon;

    public SkillSet getFinalStats() {
        SkillSet finalStats;
        finalStats = intrinsicStats;
        finalStats.add(weapon.getStats());
        foreach(SkillModifier modifier : getEquipmentModifiers()) {
            finalStats.apply(modifier);
        }
        return finalStats;
    }

    protected abstract List<SkillModifier> getEquipmentModifiers();

}

当然,这是一个示例,可以为您提供想法。您也可以考虑使用Decorator设计模式,以便统计信息上的修饰符可以作为“过滤器”使用,并依次应用...


这个C怎么样?
bogglez 2014年

@bogglez:我同样倾向于使用“ C'ish”来指代松散地类似于花括号语言的伪代码,即使涉及到类也是如此。不过,您确实有一点要说:这看起来像是更具体的语法-与可编译Java相比,这是一个很小的更改或两个更改。
cHao 2014年

我不认为我在这里过于严格。这不仅是语法上的差异,而且是语义上的差异。在C语言中,没有类,模板,受保护的限定词,方法,虚函数等。我只是不喜欢这种随意使用术语的行为。
bogglez 2014年

1
就像其他人所说的,花括号语法来自C。Java的语法(或C#)受此样式的启发。
皮埃尔·阿劳德

3

最OOP的处理方式可能是通过继承进行处理。您的基础班级(或根据语言而定的超级班级)将是人,那么反派和英雄可能会从基础班级继承。然后,基于力量的英雄和基于飞行的英雄会分叉,例如他们的运输方式不同。这具有额外的好处,即您的计算机播放器可以具有与人类播放器相同的基类,并有望简化您的生活。

关于属性的另一件事,这与OOP无关,是将您的角色属性表示为列表,因此您不必在代码中明确定义所有属性。因此,也许您将有一个武器清单和一个物理属性清单。为这些属性创建某种基础类,以便它们可以交互,因此每个组件都是根据损坏,能源成本等来定义的。因此,当两个人聚在一起时,相对清晰的是如何进行交互。您将遍历每个角色的属性列表,并计算每次交互中某人对另一人造成的伤害。

使用列表将帮助您避免重写大量代码,因为要添加具有您尚未想到的属性的字符,只需确保它具有与现有系统兼容的交互即可。


4
我认为关键是要使统计数据与英雄的职业脱钩。继承不一定是最佳的OOP解决方案(在我的回答中,我改用了组合)。
皮埃尔·阿劳德

1
我赞同前面的评论。如果角色C具有来自角色A和B(它们都具有相同的基类)的属性,会发生什么?您要么重复代码,要么面临有关多重继承的一些问题。在这种情况下,我更倾向于使用组合而不是继承。
ComFreek

2
很公平。我只是给OP一些选择。在现阶段,似乎还没有什么固定的东西,我也不知道他们的经验水平。期望初学者实现全面的多态性可能要花很多钱,但是继承对于初学者来说足够简单。我试图帮助解决代码感觉“笨拙”的问题,我只能假定这是指使用或未使用硬编码的字段。将列表用于这些类型的未定义值imho是一个不错的选择。
GenericJam 2014年

1
-1:“最OOP的处理方式可能是通过继承进行处理。” 继承不是特别面向对象的。
肖恩·米德迪奇

3

我建议使用从数据文件(例如,我使用XML)和Stat对象填充的统计信息类型管理器,其类型和值存储在字符实例中作为哈希表,并以统计信息类型唯一ID作为键。

编辑:伪代码

Class StatType
{
    int ID;
    string Name;

    public StatType(int _id, string _name)
    {
        ID = _id;
        Name = _name;
    }
}


Class StatTypeManager
{
    private static Hashtable statTypes;

    public static void Init()
    {
        statTypes = new Hashtable();

        StatType type;

        type = new StatType(0, "Strength");
        statTypes.add(type.ID, type);

        type = new StatType(1, "Dexterity");
        statTypes.add(type.ID, type);

        //etc

        //Recommended: Load your stat types from an external resource file, e.g. xml
    }

    public static StatType getType(int _id)
    {
        return (StatType)statTypes[_id];
    }
}

class Stat
{
    StatType Type;
    int Value;

    public Stat(StatType _type, int _value)
    {
        Type = _type;
        Value = _value;
    }
}

Class Char
{
    Hashtable Stats;

    public Char(Stats _stats)
    {
        Stats = _stats;
    }

    public int GetStatValue(int _id)
    {
        return ((Stat)Stats[_id]).Value;
    }
}

我认为StatType类是不必要的,只是用nameStatType是一个关键。就像#Grimston所做的那样。与相同Stats
giannis christofakis 2014年

我更喜欢在这样的实例中使用类,以便您可以自由修改名称(而id保持不变)。在这种情况下过度杀伤力?也许可以,但是由于该技术可能用于程序中其他地方的类似操作,因此我会这样做,以实现一致性。
DFreeman 2014年

0

我将尝试给您一个有关如何设计军械库和军械库的示例。

我们的目标是使实体脱钩,因此武器应该是接口。

interface Weapon {
    public int getDamage();
}

假设每个玩家只能拥有一个武器,我们可以使用Strategy pattern以便轻松更换武器。

class Knife implements Weapon {
    private int damage = 10;
    @Override
    public int getDamage() {
        return this.damage;
    }
}

class Sword implements Weapon {
    private int damage = 40;
    @Override
    public int getDamage() {
        return this.damage;
    }
}

另一个有用的模式是“ 空对象模式”,以防玩家没有武装。

class Weaponless implements Weapon {
    private int damage = 0;
    @Override
    public int getDamage() {
        return this.damage;
    }
}

至于军械库,我们可以穿多种防御装备。

// Defence classes,interfaces

interface Armor {
    public int defend();
}

class Defenseless implements Armor {

    @Override
    public int defend() {
        return 0;
    }
}

abstract class Armory implements Armor {

    private Armor armory;
    protected int defence;

    public Armory() {
        this(new Defenseless());
    }

    public Armory(Armor force) {
        this.armory = force;
    }

    @Override
    public int defend() {
        return this.armory.defend() + this.defence;
    }

}

// Defence implementations

class Helmet extends Armory {
    {
        this.defence = 30;
    }    
}

class Gloves extends Armory {
    {
        this.defence = 10;
    }    
}

class Boots extends Armory {
    {
        this.defence = 10;
    }    
}

为了解耦,我为防御者创建了一个接口。

interface Defender {
    int getDefended();
}

Player班级。

class Player implements Defender {

    private String title;

    private int health = 100;
    private Weapon weapon = new Weaponless();
    private List<Armor> armory = new ArrayList<Armor>(){{ new Defenseless(); }};


    public Player(String name) {
        this.title = name;
    }

    public Player() {
        this("John Doe");
    }

    public String getName() {
        return this.title;
    }


    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    public void attack(Player enemy) {

        System.out.println(this.getName() + " attacked " + enemy.getName());

        int attack = enemy.getDefended() + enemy.getHealth()- this.weapon.getDamage();

        int health = Math.min(enemy.getHealth(),attack);

        System.out.println("After attack " + enemy.getName() + " health is " + health);

        enemy.setHealth(health);
    }

    public int getHealth() {
        return health;
    }

    private void setHealth(int health) {
        /* Check for die */
        this.health = health;
    }

    public void addArmory(Armor armor) {
        this.armory.add(armor);
    }


    @Override
    public int getDefended() {
        int defence = this.armory.stream().mapToInt(armor -> armor.defend()).sum();
        System.out.println(this.getName() + " defended , armory points are " + defence);
        return defence;
    }

}

让我们添加一些游戏玩法。

public class Game {
    public static void main(String[] args) {
        Player yannis = new Player("yannis");
        Player sven = new Player("sven");


        yannis.setWeapon(new Knife());
        sven.setWeapon(new Sword());


        sven.addArmory(new Helmet());
        sven.addArmory(new Boots());

        yannis.attack(sven);      
        sven.attack(yannis);      
    }
}

瞧!


2
当您解释设计选择背后的原因时,此答案会更有帮助。
菲利普
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.