ООП архитектура для героя с множеством атрибутов

14

Я собираюсь запустить простую браузерную текстовую RPG с персонажами, которые могут (пассивно) сражаться с другими людьми. Это включает в себя список около 10 навыков, таких как сила, ловкость и так далее, с дополнительными навыками для различных видов оружия.

Есть ли лучший способ создать этот класс персонажей, чем просто использовать эти навыки в качестве атрибута класса? Это кажется легким, но я отказываюсь, потому что это неуклюжесть.

class Char(self):
    int strength
    int dexterity
    int agility
    ...
    int weaponless
    int dagger
    ...
Sven
источник
1
Вы должны проверить это полное руководство для написания игр и того, как некоторые из общих классов могут выглядеть как ссылки
драконы
@dragons Спасибо за интересную ссылку, но я не вижу более глубокого объяснения для разработки Charactorкласса?
Свен
1
Что именно вы считаете «неуклюжим» в этом дизайне?
Томас

Ответы:

17

Пока ваша система относительно проста, это должно работать. Но когда вы добавите такие вещи, как временные модификаторы навыков, вы скоро увидите много повторяющегося кода. Вы также столкнетесь с проблемами с различным оружием, используя различные навыки. Поскольку каждый навык - это отдельная переменная, вам придется писать разные коды для каждого типа навыка, который в основном делает то же самое (или использует несколько мерзких отражений - при условии, что ваш язык программирования поддерживает их).

По этой причине я бы рекомендовал вам хранить навыки и навыки в ассоциативной структуре данных, которая сопоставляет константы навыков со значениями. Как это сделать, элегантно отличается от языка программирования к языку программирования. Когда ваш язык поддерживает это, константы должны быть в enum.

Чтобы дать вам пример того, как это будет работать на практике, ваш код для расчета ущерба от атаки будет выглядеть примерно так:

int damage = attacker.getSkill(STRENGTH) + 
             attacker.getProficiency(weapon.getProficiencyRequired()) -
             defender.getSkill(TOUGHNESS);
Philipp
источник
Создание такого метода 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;

    }
}

Вы можете хранить все в массиве, если вы действительно хотите.

Grimston
источник
Это в значительной степени то, что сказал @ Филипп?
AturSams
1
@Philipp предложил использовать перечисления, массивы - еще один вариант.
Grimston
На самом деле говорится: «... ассоциативная структура данных, которая отображает константы навыков в значения», константами в словаре могут быть строки.
AturSams
3

Я постараюсь ответить на этот вопрос наиболее ООП-способом (или, по крайней мере, так, как я думаю). Это может быть полностью излишним, в зависимости от эволюции, которую вы видите в отношении статистики.

Вы можете представить класс 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();

    }

}

Тогда у героя будет поле intrinsicStats типа SkillSet. Оружие может иметь модификатор 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, чтобы модификаторы статистики работали как «фильтры», которые применяются один за другим…

Пьер Арло
источник
Как это С-как?
Богглез
@bogglez: Я также склонен использовать «C'ish» для обозначения псевдокода, который напоминает язык фигурных скобок, даже если участвуют классы. Тем не менее, у вас есть смысл: это выглядит как более специфический синтаксис - это незначительное изменение или два по сравнению с возможностью компиляции Java.
cHao
Я не думаю, что я здесь слишком строг. Это не просто разница в синтаксисе, но разница в семантике. В Си нет такой вещи, как класс, шаблоны, защищенные квалификаторы, методы, виртуальные функции и т. Д. Мне просто не нравится это случайное злоупотребление терминами.
Богглз
1
Как говорили другие, синтаксис фигурных скобок происходит от C. Синтаксис Java (или C # в этом отношении) очень вдохновлен этим стилем.
Пьер Арло
3

Вероятнее всего, ООП-способ делать что-то с наследованием. Ваш базовый класс (или суперкласс в зависимости от языка) будет персоной, тогда, возможно, злодеи и герои унаследуют от базового класса. Тогда ваши герои, основанные на силе, и герои, основанные на полете, разветвляются, например, потому что у них другой вид транспорта. Это дает дополнительный бонус за то, что ваши компьютерные игроки могут иметь тот же базовый класс, что и игроки-люди, и это, надеюсь, упростит вашу жизнь.

Другая вещь, касающаяся атрибутов, и это менее специфично для ООП, состоит в том, чтобы представлять атрибуты вашего персонажа в виде списка, чтобы вам не нужно было явно определять их все в вашем коде. Поэтому, возможно, у вас будет список оружия и список физических атрибутов. Создайте некоторый базовый класс для этих атрибутов, чтобы они могли взаимодействовать, чтобы каждый из них определялся с точки зрения ущерба, стоимости энергии и т. Д., Поэтому, когда два человека собираются вместе, относительно ясно, как будет происходить взаимодействие. Вы должны были бы перебрать список атрибутов каждого персонажа и вычислить урон, который один наносит другому, со степенью вероятности в каждом взаимодействии.

Использование списка поможет вам избежать переписывания большого количества кода, поскольку для добавления символа с атрибутом, о котором вы еще не думали, вам просто нужно убедиться, что он взаимодействует с вашей существующей системой.

GenericJam
источник
4
Я думаю, что смысл в том, чтобы отделить статистику от класса героя. Наследование не обязательно является лучшим решением ООП (вместо этого я использовал композицию).
Пьер Арло
1
Я второй предыдущий комментарий. Что произойдет, если символ C имеет свойства от символов A и B (оба имеют одинаковый базовый класс)? Вы либо дублируете код, либо сталкиваетесь с проблемами множественного наследования . Я бы предпочел композицию наследству в этом случае.
ComFreek
2
Справедливо. Я просто давал ОП несколько вариантов. Не похоже, что на этом этапе есть много камней, и я не знаю их уровень опыта. Вероятно, было бы немного ожидать, что новичок осуществит полномасштабный полиморфизм, но наследование достаточно просто, чтобы новичок мог понять. Я пытался помочь решить проблему «неуклюжего» кода, который, как я могу только предположить, относится к жестко закодированным полям, которые могут или не могут использоваться. Использование списка для этих типов неопределенных значений является хорошим вариантом.
GenericJam
1
-1: «Наиболее вероятный способ сделать что-либо - это сделать что-то с наследованием». Наследование очень не особо объектно-ориентировано.
Шон Мидлдич
3

Я бы порекомендовал менеджер типов статистики, заполняемый из файла данных (например, я использую XML) и объектов Stat, с типом и значением, хранящимся в символьной таблице в качестве хеш-таблицы, с уникальным идентификатором типа статистики в качестве ключа.

Изменить: Псудо код

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;
    }
}
DFreeman
источник
Я думаю, что StatTypeкласс ненужным, просто используйте nameв StatTypeкачестве ключа. Как сделал Гримстон. То же самое с Stats.
Джаннис Кристофакис
Я предпочитаю использовать класс в подобных случаях, чтобы вы могли свободно изменять имя (пока идентификатор остается постоянным). Избыточность в этом случае? Возможно, но так как этот метод может использоваться для чего-то похожего в другом месте в программе, я бы сделал это для постоянства.
DFreeman
0

Try постараюсь дать вам пример того, как вы можете создать свой арсенал и свой арсенал.

Наша цель - разделить сущности, поэтому оружие должно быть интерфейсом.

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
Этот ответ будет более полезен, когда вы объясните свои аргументы в пользу выбора дизайна.
Филипп