是否可以使用继承在c ++中实现ECS?

时间:2019-06-19 05:45:20

标签: c++ oop design-patterns entity

我正在尝试将使用C ++编写的游戏设计为实体/组件系统(基本思想是,我将具有一组代表不同类型数据的“ 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(而不是FightersMages),但是我需要分别处理战士和法师(例如,遍历所有{{ 1}},如果他们可以施放咒语,则必须这么做,否则他们将使用近战武器进行攻击。在实际的游戏逻辑中,如何区分也实现Characters的{​​{1}}实例和也实现Character的{​​{1}}实例?

是否有一种简单的方法可以做我想做的事情,还是应该完全重新考虑我的设计?

(注意:这不是实际的代码;在现实世界中,我实际上是使用工厂或某种东西来创建精灵或其他东西,而不是尝试将所有信息都放入构造函数中,并且我可能会将逻辑不同,但这说明了我遇到的问题)。

1 个答案:

答案 0 :(得分:0)

您可以在引擎中使用visitor pattern

每个字符都将覆盖Character类的纯虚拟accept方法。

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;
};

CharacterMage中继承Fighter将包括更少的样板代码。如果要坚持继承形式,则需要在每个字符中提供accept方法的重载,该方法仅调用visit的{​​{1}}函数:

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

请注意,我在上面的代码中有一个循环依赖关系,在拆分声明和定义时应该解决该依赖关系。