处理游戏中的实体

Jak*_*cas 11 c++ game-engine entity-system

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

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

所以我决定在这里发布这个问题,向你展示我目前的思考方式,看看是否有人可以向我建议一个更好的方法.

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

我有一个游戏循环,伪代码会像这样(在CEngine类内)

while(isRunning) {
    Window->clear_screen();

    EntityManager->draw();

    Window->flip_screen();

    // Cap FPS
}
Run Code Online (Sandbox Code Playgroud)

我的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;
};
Run Code Online (Sandbox Code Playgroud)

我的CEntity课程看起来像这样:

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;
};
Run Code Online (Sandbox Code Playgroud)

在那之后,我会为敌人创建类,并给它一个精灵表,它自己的功能等.

例如:

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();
};
Run Code Online (Sandbox Code Playgroud)

所有这些都可以很好地将精灵绘制到屏幕上.

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

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

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

但后来我想,如果它是一个需要处理输入的玩家实体呢?这是如何运作的?

我没有尝试,但我认为我不能像使用draw()函数那样循环,因为像敌人这样的实体将没有handle_input()函数.

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

for(entityVectorIter = entityVector.begin(); entityVectorIter != entityVector.end(); entityVectorIter++) {
    if((*entityVectorIter)->get_type() == PLAYER) {
        (*entityVectorIter)->handle_input();
    }
}
Run Code Online (Sandbox Code Playgroud)

但是我不知道人们通常会怎么写这些东西,所以我不确定最好的方法.

我在这里写了很多,我没有问任何具体的问题,所以我将澄清我在这里寻找的东西:

  • 我设计/设计我的代码的方式是否正确,是否实用?
  • 有没有更好的方式让我更新我的实体并调用其他实体可能没有的功能?
  • 使用枚举来跟踪实体类型是识别实体的好方法吗?

Cra*_*rks 11

你已经非常接近大多数游戏的实际操作方式了(虽然性能专家怨恨者迈克·阿克顿经常抱怨这一点).

通常你会看到这样的东西

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
}
Run Code Online (Sandbox Code Playgroud)

然后实体管理器遍历并在世界中的每个实体上调用update(),handleinput()和draw().

当然,拥有大量这些功能,大多数功能在你调用它们时什么都不做,可能会非常浪费,特别是对于虚拟功能.所以我也看到了其他一些方法.

一个是存储例如输入数据中的全局(或作为全局接口的成员,或一个单等).然后覆盖敌人的update()函数,使它们成为do_ai_stuff().和播放器的update(),以便它通过轮询全局来进行输入处理.

另一种方法是在Listener模式上使用一些变体,以便关注输入的所有内容都继承自公共侦听器类,并使用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..
}
Run Code Online (Sandbox Code Playgroud)

还有其他更复杂的方法.但所有这些工作,我已经看到他们每个人实际上发货和销售.

  • 男人,那个环节里的那个人真是太可怕了. (4认同)
  • 也许,但他也是为什么Ratchet&Clank从未低于60fps的原因. (4认同)
  • @zebrabox:在 Cell 开发中,我们称之为 -&gt;“缓存未命中运算符”。 (2认同)

Moo*_*ice 8

您应该查看组件,而不是继承.例如,在我的引擎中,我有(简化):

class GameObject
{
private:
    std::map<int, GameComponent*> m_Components;
}; // eo class GameObject
Run Code Online (Sandbox Code Playgroud)

我有各种不同的组件:

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
Run Code Online (Sandbox Code Playgroud)

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

编辑:抱歉,我会稍微充实一下,但我现在处于中间状态:)


Fli*_*sch 7

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

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();
};
Run Code Online (Sandbox Code Playgroud)