C++ 在游戏中处理实体

C++ 在游戏中处理实体,c++,game-engine,entity-system,C++,Game Engine,Entity System,作为一个小练习,我正在尝试编写一个非常小、简单的游戏引擎,它只处理实体(移动、基本AI等) 因此,我试图思考一个游戏如何处理所有实体的更新,我有点困惑(可能是因为我用了错误的方式) 所以我决定把这个问题贴在这里,向你们展示我目前的思考方式,看看是否有人能给我一个更好的方法 目前,我有一个CEngine类,它获取指向它需要的其他类的指针(例如CWindow类、CEntityManager类等) 我有一个游戏循环,在伪代码中是这样的(在CEngine类中) 我的CEntityManager课程如下所

作为一个小练习,我正在尝试编写一个非常小、简单的游戏引擎,它只处理实体(移动、基本AI等)

因此,我试图思考一个游戏如何处理所有实体的更新,我有点困惑(可能是因为我用了错误的方式)

所以我决定把这个问题贴在这里,向你们展示我目前的思考方式,看看是否有人能给我一个更好的方法

目前,我有一个CEngine类,它获取指向它需要的其他类的指针(例如CWindow类、CEntityManager类等)

我有一个游戏循环,在伪代码中是这样的(在CEngine类中)

我的CEntityManager课程如下所示:

enum {
    PLAYER,
    ENEMY,
    ALLY
};

class CEntityManager {
    public:
        void create_entity(int entityType); // PLAYER, ENEMY, ALLY etc.
        void delete_entity(int entityID);

    private:
        std::vector<CEntity*> entityVector;
        std::vector<CEntity*> entityVectorIter;
};
class CEntity() {
    public:
        virtual void draw() = 0;
        void set_id(int nextEntityID);
        int get_id();
        int get_type();

    private:
        static nextEntityID;
        int entityID;
        int entityType;
};
之后,我会为敌人创建类,并给它一张精灵表,它自己的函数等等

例如:

class CEnemy : public CEntity {
    public:
        void draw(); // Implement draw();
        void do_ai_stuff();

};

class CPlayer : public CEntity {
    public:
        void draw(); // Implement draw();
        void handle_input();
};
所有这些都可以很好地将精灵绘制到屏幕上

但后来我谈到了使用存在于一个实体中但不存在于另一个实体中的函数的问题

在上面的伪代码示例中,do_ai_stuff();和handle_input()

从我的游戏循环中可以看到,有一个对EntityManager->draw()的调用; 这只是迭代entityVector并调用draw();每个实体的函数-工作正常,因为所有实体都有一个draw();功能

但后来我想,如果它是一个需要处理输入的玩家实体呢? 这是怎么回事

我没有尝试过,但我假设我不能像使用draw()函数那样循环,因为像敌人这样的实体不会有handle\u input()函数

我可以使用if语句检查entityType,如下所示:

for(entityVectorIter = entityVector.begin(); entityVectorIter != entityVector.end(); entityVectorIter++) {
    if((*entityVectorIter)->get_type() == PLAYER) {
        (*entityVectorIter)->handle_input();
    }
}
但我不知道人们通常是如何写这些东西的,所以我不确定最好的方式

我在这里写了很多东西,没有提出任何具体问题,因此我将澄清我在这里寻找的是什么:

  • 我设计代码的方式是否可行,是否实用
  • 有没有更好更有效的方法来更新我的实体和调用其他实体可能没有的函数
  • 使用枚举跟踪实体类型是标识实体的好方法吗

您也可以通过使用虚拟功能来实现此功能:

class CEntity() {
    public:
        virtual void do_stuff() = 0;
        virtual void draw() = 0;
        // ...
};

class CEnemy : public CEntity {
    public:
        void do_stuff() { do_ai_stuff(); }
        void draw(); // Implement draw();
        void do_ai_stuff();

};

class CPlayer : public CEntity {
    public:
        void do_stuff() { handle_input(); }
        void draw(); // Implement draw();
        void handle_input();
};

你已经非常接近大多数游戏的实际操作方式(尽管性能专家迈克·阿克顿(Mike Acton))了

通常你会看到这样的情况

class CEntity {
  public:
     virtual void draw() {};  // default implementations do nothing
     virtual void update() {} ;
     virtual void handleinput( const inputdata &input ) {};
}

class CEnemy : public CEntity {
  public:
     virtual void draw(); // implemented...
     virtual void update() { do_ai_stuff(); }
      // use the default null impl of handleinput because enemies don't care...
}

class CPlayer : public CEntity {
  public:
     virtual void draw(); 
     virtual void update();
     virtual void handleinput( const inputdata &input) {}; // handle input here
}
然后实体管理器对世界上的每个实体调用update()、handleinput()和draw()

当然,有很多这样的函数,其中大多数在调用它们时什么都不做,这会变得非常浪费,特别是对于虚拟函数。所以我也看到了一些其他的方法

一种是将输入数据存储在全局(或作为全局接口的成员或单例等)中。然后重写敌人的update()函数,这样他们就可以做一些东西了。和播放器的update(),以便它通过轮询全局

另一种方法是在上使用一些变体,以便所有与输入有关的内容都继承自一个公共侦听器类,并向InputManager注册所有这些侦听器。然后inputmanager在每个帧中依次调用每个侦听器:

class CInputManager
{
  AddListener( IInputListener *pListener );
  RemoveListener( IInputListener *pListener );

  vector<IInputListener *>m_listeners;
  void PerFrame( inputdata *input ) 
  { 
     for ( i = 0 ; i < m_listeners.count() ; ++i )
     {
         m_listeners[i]->handleinput(input);
     }
  }
};
CInputManager g_InputManager; // or a singleton, etc

class IInputListener
{
   virtual void handleinput( inputdata *input ) = 0;
   IInputListener() { g_InputManager.AddListener(this); }
   ~IInputListener() { g_InputManager.RemoveListener(this); }
}

class CPlayer : public IInputListener
{
   virtual void handleinput( inputdata *input ); // implement this..
}
class-CInputManager
{
AddListener(IInputListener*pListener);
RemovelListener(IIInputListener*pListener);
vectorm_听众;
void PerFrame(输入数据*输入)
{ 
对于(i=0;ihandleinput(输入);
}
}
};
CInputManager g_InputManager;//或独生子女等
类IIInputListener
{
虚空handleinput(inputdata*input)=0;
IIInputListener(){g_InputManager.AddListener(this);}
~IInputListener(){g_InputManager.RemovelListener(this);}
}
类CPlayer:公共IIInputListener
{
虚拟void handleinput(inputdata*input);//实现此。。
}

还有其他更复杂的方法。但所有这些都很有效,我在实际发货和销售的产品中都看到了它们。

您应该关注组件,而不是继承。例如,在我的引擎中,我(简化了):

这些组件可以添加到游戏对象以诱导行为。它们可以通过消息传递系统进行通信,在主循环期间需要更新的内容可以注册一个帧侦听器。它们可以独立运行,并且可以在运行时安全地添加/删除。我发现这是一个非常可扩展的系统


编辑:道歉,我会把它充实一点,但是我现在正处于某个中间:(

< P> <强> 1 < /强>一个小东西-为什么要改变实体的ID?通常,这是常量,在构造过程中初始化,就是这样:

class CEntity
{ 
     const int m_id;
   public:
     CEntity(int id) : m_id(id) {}
}

对于其他方面,有不同的方法,选择取决于有多少类型特定的函数(以及您可以如何很好地再现它们)


添加到所有

最简单的方法就是将所有方法添加到基接口,并在不支持它的类中实现为无操作。这听起来可能是一个糟糕的建议,但如果有很少的方法不适用,那么这是一个可接受的非规范化,并且您可以假设这组方法不会随着未来的需求而显著增长

你甚至可以实现一种基本的“发现机制”,例如

不要过度以这种方式开始很容易,即使它会给您的代码带来巨大的混乱,也要坚持下去。它可以被粉饰为“类型层次结构的有意非规范化”——但最终它是jsut——一种可以让您快速解决一些问题的黑客,但在应用程序增长时很快就会受到伤害


真实类型发现

使用和
动态
class GameObject
{
private:
    std::map<int, GameComponent*> m_Components;
}; // eo class GameObject
class GameComponent
{
}; // eo class GameComponent

class LightComponent : public GameComponent // represents a light
class CameraComponent : public GameComponent // represents a camera
class SceneNodeComponent : public GameComponent // represents a scene node
class MeshComponent : public GameComponent // represents a mesh and material
class SoundComponent : public GameComponent // can emit sound
class PhysicsComponent : public GameComponent // applies physics
class ScriptComponent : public GameComponent // allows scripting
class CEntity
{ 
     const int m_id;
   public:
     CEntity(int id) : m_id(id) {}
}
 class CEntity
 {
   public:
     ...
     virtual bool CanMove() = 0;
     virtual void Move(CPoint target) = 0;
 }
CFastCat * fastCat = dynamic_cast<CFastCat *>(entity) ;
if (fastCat != 0)
   fastCat->Meow();
// -----BAD BAD BAD BAD Code -----
CFastCat * fastCat = dynamic_cast<CFastCat *>(entity) ;
if (fastCat != 0)
   fastCat->Meow();

CBigDog * bigDog = dynamic_cast<CBigDog *>(entity) ;
if (bigDog != 0)
   bigDog->Bark();

CPebble * pebble = dynamic_cast<CPebble *>(entity) ;
if (pebble != 0)
   pebble->UhmWhatNoiseDoesAPebbleMake();
class IMovable
{
   virtual void SetSpeed() = 0;
   virtual void SetTarget(CPoint target) = 0;
   virtual CPoint GetPosition() = 0;
   virtual ~IMovable() {}
}

class IAttacker
{
   virtual int GetStrength() = 0;
   virtual void Attack(IAttackable * target) = 0;
   virtual void SetAnger(int anger) = 0;
   virtual ~IAttacker() {}
}
class CHero : public CEntity, public IMovable, public IAttacker