【问题标题】:Is it possible to use inheritance to implement an ECS in c++?是否可以使用继承在 C++ 中实现 ECS?
【发布时间】:2019-06-19 05:45:20
【问题描述】:

我正在尝试将我用 C++ 编写的游戏构建为实体/组件系统(基本想法是我将拥有一组代表不同类型数据的“实体”类和一组“组件”表示给定实体可能具有的各种行为的类)。我想使用组件的虚拟基类来实现它。例如,我可以有一个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),但我需要分别处理战士和法师(例如,循环遍历所有Characters 以及他们是否可以施放他们这样做的咒语,否则他们会用近战武器攻击)。在实际的游戏逻辑中,如何区分同样实现了MageCharacter 实例与同样实现FighterCharacter 实例?

有没有一种简单的方法来做我想做的事情,或者我应该完全重新考虑我的设计?

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

【问题讨论】:

  • ECS 是字面上没有继承,如果这是您的目标,请再次阅读 ECS。游戏引擎通常需要检查对象的类型,因此您将在当前设计中拥有类似 dynamic_cast&lt;Mage*&gt;(&amp;someCharachter) 的内容,以及使 Character 具有多态性。
  • @PasserBy 我知道它应该避免复杂的继承层次结构,但“所有Characters 可以Mages 可以cast”的概念基本上与java 的interface 或c++ 的概念相同虚拟类范式。另外我知道我必须进行动态转换,我的问题是我怎么知道刚刚给定一个 Character 实例它实现了哪些组件才能知道要转换什么
  • @Raghav Malik 在这种特定情况下,它们非常相似。问题不在于对一个对象调用一种方法,而在于如何处理数千个对象,每个对象可能包含数百个组件。在这种情况下,它们是非常不同的。实现组件系统的最简单方法是创建一个GameObject 类,并为它提供一个指向每个可能的组件类型的指针。如果您想要所有法师,只需遍历所有对象并获取那些具有指向 MageComponent 的非空指针的对象

标签: c++ oop design-patterns entity


【解决方案1】:

您可以在引擎中使用visitor pattern

每个字符都会覆盖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;
};

MageFighter 中继承Character 将包含更少的样板代码。如果你想坚持你的继承形式,你需要在每个字符中提供accept方法的重载,它只调用Enginevisit函数:

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

};

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-23
    • 1970-01-01
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 1970-01-01
    • 2012-01-20
    相关资源
    最近更新 更多