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

标签 c++ oop design-patterns entity

我正在尝试构建一个我用 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
    }
};

要跟踪 map 上的所有当前字符,我可以做类似的事情

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

我必须将它们直接存储为 Characters(而不是 FightersMages),但我需要分别处理战士和法师(例如,遍历所有 Characters,如果他们可以施法,他们就会施法,否则他们会使用近战武器攻击)。在实际的游戏逻辑中,我如何区分同时实现 MageCharacter 实例与同时实现 Character 的实例 斗士?

是否有一种简单的方法可以完成我正在尝试做的事情,或者我应该完全重新考虑我的设计?

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

最佳答案

您可以使用 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 方法的重载,它只是调用 visit 函数引擎:

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

};

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

关于c++ - 是否可以使用继承在 C++ 中实现 ECS?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56660747/

相关文章:

Java 提取出初始化类的私有(private)字段

c++ - 如何修复外部未解析的代码块?

c# - 为什么将基类型实例转换为派生类型对象/引用不是类型安全的

oop - 我应该更关心包之间的耦合还是分发单元之间的耦合?

c# - 混淆 : Internal, 保护和保护内部

java - 实体组件系统删除实体

design-patterns - 您使用什么设计模式,它有什么好处?

c++ - ASIO 聊天 session 类在销毁时抛出异常 : C++ ASIO

c++ - 逻辑扩展文件失败

从抽象基类派生并调用另一个类中的方法的 C++ 包装类,该类也从抽象基类派生