作为一个小练习,我正在尝试编写一个非常小、简单的游戏引擎,它只处理实体(移动、基本 AI 等)
因此,我试图思考游戏如何处理所有实体的更新,但我有点困惑(可能是因为我的处理方式不对)
所以我决定在这里发布这个问题,向您展示我目前的思考方式,看看是否有人可以向我建议更好的方法。
目前,我有一个 CEngine 类,它接受指向它需要的其他类的指针(例如 CWindow 类、CEntityManager 类等)
我有一个游戏循环,在伪代码中会像这样(在 CEngine 类中)
while(isRunning) {
Window->clear_screen();
EntityManager->draw();
Window->flip_screen();
// Cap FPS
}
我的 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;
};
我的 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;
};
之后,我会为敌人创建类,并给它一个 sprite 表,它自己的函数等。
例如:
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();
};
所有这一切都适用于将 Sprite 绘制到屏幕上。
但后来我遇到了使用存在于一个实体中但不存在于另一个实体中的函数的问题。
在上面的伪代码示例中,do_ai_stuff();和 handle_input();
正如您从我的游戏循环中看到的那样,调用了 EntityManager->draw(); 这只是遍历了 entityVector 并调用了 draw();每个实体的功能 - 看到所有实体都有 draw(); 效果很好功能。
但转念一想,如果是需要处理输入的玩家实体呢? 它是如何工作的?
我没有尝试过,但我认为我不能像使用 draw() 函数那样循环遍历,因为像敌人这样的实体不会有 handle_input() 函数。
我可以使用 if 语句来检查实体类型,如下所示:
for(entityVectorIter = entityVector.begin(); entityVectorIter != entityVector.end(); entityVectorIter++) {
if((*entityVectorIter)->get_type() == PLAYER) {
(*entityVectorIter)->handle_input();
}
}
但我不知道人们通常是如何写这些东西的,所以我不确定最好的方法。
我在这里写了很多,我没有问任何具体问题,所以我会在这里澄清我在寻找什么:
- 我布局/设计代码的方式是否可行,实用吗?
- 有没有更好更有效的方法来更新我的实体并调用其他实体可能没有的函数?
- 使用枚举来跟踪实体类型是否是识别实体的好方法?
最佳答案
您已经非常接近大多数游戏实际执行此操作的方式(尽管性能专家脾气暴躁的 Mike Acton often gripes about that)。
通常你会看到这样的东西
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()。
当然,拥有一大堆这样的函数,其中大部分在您调用它们时什么都不做,会非常浪费,尤其是对于虚函数。所以我也看到了其他一些方法。
一种是将输入数据例如存储在全局(或作为全局接口(interface)的成员,或单例等)中。然后重写敌人的 update() 函数,使它们执行 do_ai_stuff()。和玩家的 update() 以便它通过轮询全局来进行输入处理。
另一个是在 Listener pattern 上使用一些变体,这样所有与输入有关的东西都继承自一个通用的监听器类,并且您可以使用 InputManager 注册所有这些监听器。然后inputmanager每帧依次调用每个listener:
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..
}
还有其他更复杂的方法。但所有这些都有效,而且我已经在实际发货和销售的产品中看到了它们中的每一个。
关于c++ - 处理游戏中的实体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4112810/