Можно ли использовать наследование для реализации ECS в C ++? - PullRequest
1 голос
/ 19 июня 2019

Я пытаюсь спроектировать игру, которую пишу на C ++, как систему Entity / Component (основная идея в том, что у меня будет набор классов «Entity», представляющих различные виды данных, и набор «Component»).классы, представляющие различные виды поведения, которые может иметь данный объект).Я хотел реализовать это, используя виртуальные базовые классы для компонентов.Например, у меня может быть сущность Character:

class Character {
public:
    int armor;
    int health;
    std::string name;
};

, а также компонент Fighter, представляющий что-то, что может использовать оружие ближнего боя:

class Fighter {
public:
    int damage;
    virtual Direction attack() = 0;
}

и Mage компонент, представляющий что-то, что может произносить заклинания

class Mage {
public:
    std::vector<Spell> spellList;
    virtual Spell cast() = 0;
}

Используя множественное наследование для их использования, я бы имел

class ElvenMage : Character, Mage {
public:
    ElvenMage() {
        this->armor = 0;
        this->health = 10;
        this->name = "elven mage";
        this->spellList = ...;
    }
    virtual Spell cast() {
        // some AI logic to figure out which spell to cast
    }
};

и

class Player : Character, Fighter, Mage {
public:
    Player() {
        this->armor = 5;
        this->health = 20;
        this->name = "Player";
    }
    virtual Direction attack() {
        // some logic to handle player melee input
    }
    virtual Spell cast() {
        // some logic to handle player spell casting input
    }
};

Для отслеживанияиз всех текущих символов на карте я могу сделать что-то вроде

class Engine {
public:
    std::vector<std::unique_ptr<Character>> characters;

Я должен хранить их непосредственно как Characters (не как Fighters или Mages), но мне нужно будет обрабатывать бойцови маги отдельно (например, перебирая все Characters и, если они могут разыграть заклинание, они делают это, иначе они атакуют оружием ближнего боя).В реальной игровой логике, как бы я различил экземпляр Character, который также реализует Mage, и экземпляр Character, который также реализует Fighter?

Есть ли простой способделать то, что я пытаюсь, или я должен полностью переосмыслить свой дизайн?

(примечание: это не настоящий код; в реальном мире я бы фактически использовал фабрику или что-то для создания эльфов или чего-то еще вместо этогопопыток поместить всю информацию в конструктор, и я, вероятно, отделил бы логику по-другому, но это иллюстрирует мою проблему).

1 Ответ

0 голосов
/ 19 июня 2019

Вы можете использовать шаблон посетителя в вашем движке:

Каждый символ переопределяет чисто виртуальный метод принятия класса Character.

class Engine;
class Spell {};
class Direction {};

class Character {
 public:
  virtual void accept(Engine& engine) = 0;
};

class Fighter {
 public:
  virtual Direction attack() = 0;
};

class Mage {
 public:
  virtual Spell cast() = 0;
};

Наследование Character в Mage и Fighter будет включать меньше стандартного кода. Если вы хотите придерживаться своей формы наследования, вам необходимо обеспечить перегрузку метода accept для каждого символа, который просто вызывает функцию visit Engine:

class ElvenMage : public Character, Mage {
 public:
  ElvenMage() {}
  virtual Spell cast() {
    std::cout << "Casting from ElvenMage"
              << "\n";
    return Spell{};
  }

  void accept(Engine& visitor) override { visitor.visit(*this); }
};

class Player : public Character, Fighter, Mage {
 public:
  Player() {}
  virtual Direction attack() {
    std::cout << "Attacking from Player"
              << "\n";
    return Direction{};
  }

  virtual Spell cast() {
    std::cout << "Casting from Player"
              << "\n";
    return Spell{};
  }

  void accept(Engine& visitor) override { 
    // cast to Player if it should attack instead of casting
    visitor.visit(static_cast<Mage&>(*this)); 
  }
};

В методе visit происходит магия:

class Engine {
  std::vector<std::unique_ptr<Character>> characters;
  void do_stuff_with_spell(Spell spell) {
    // ...
  }
  void do_stuff_with_attack(Direction direction) {
    // ...
  }

 public:
  void visit(Mage& mage) { do_stuff_with_spell(mage.cast()); }
  void visit(Player& player) { do_stuff_with_attack(player.attack()); }

};

Обратите внимание, что у меня есть циклическая зависимость в приведенном выше коде, которая должна быть решена при разделении объявлений и определений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...