我的C++游戏架构

7
我是一名经验丰富的程序员,但在C++的面向对象架构和设计方面仍然比较新手。我大部分的经验都来自C#和Java。最近,我尝试用C++编写一个简单的游戏引擎。我使用SDL进行图形处理。在这篇文章中,我想讨论我的架构,并获得一些反馈意见。特别是,我遇到了一个设计问题,希望能得到一些帮助。以下是我的疑问:
  • 在我的主函数中,我初始化了所有用于绘制屏幕等的SDL内容。
  • 然后我实例化了所有我打算使用的对象:地板、墙壁、玩家等。
  • 接下来我开始主循环。这个循环执行每个对象的移动、碰撞检测和碰撞处理函数,并重新绘制它们。
  • 主循环一直运行,直到应用程序退出,每次迭代绘制一帧。
我的问题是:我尝试做一种接口式的设计。它包括一系列抽象基类,允许每个对象实现某种行为。例如,如果我想让一个对象可移动,它就必须从可移动基类派生,该基类包含一个名为move()的虚函数和一些位置坐标。如果我希望它可以发生碰撞,那么该对象将从可碰撞抽象类继承,该类包含名为checkCollision()handleCollision()的虚函数以及一个碰撞框成员变量。像玩家这样的对象从这两个基类以及其他几个基类继承。

只要是在主循环中手动进行的操作都可以很好地工作。我可以只说:

player.move();
player.checkCollision();
player.handleCollision();
player.draw(). 

这没问题。但是我希望能够在主循环中拥有一个通用对象的向量或数组,并像这样执行:

for each object in vector
    if object is of type movable
        object.move();
    if object is of type collidable
        object.checkCollision();

我原本以为可以通过动态转换来实现这个,但是我真的没有想到任何方法。我尝试将它们存储为void指针,但这并不像我想要的那样工作。我一直在阅读有关游戏对象-组件架构的内容,可能会尝试一下,但我真的很想挽救我已经写过的内容。我认为这是一个很好的学习机会。如果有人有什么想法,我会非常感激。我的架构与其他简单的游戏引擎设计相比如何?我的接口架构是否合理或完全错误?


Jonathan所说的是动态转换的一个合理替代方案。您可以通过为每个对象提供一个enroll方法(或任何您喜欢的名称)并向其中传递包含这些单独列表的对象注册表来使其“更好”。例如,如果您有一个FlyingSaucer类,则其.enroll(Registry r)方法将调用r.add((Movable) this)r.add((Collidable) this),将FlyingSaucer添加到可移动对象列表和可碰撞对象列表中。确保对象永远不会被部分删除的业务逻辑可以隐藏在Registry::add中。 - user824425
你可以使用 typeid 运算符,http://en.wikipedia.org/wiki/Typeid - Dims
我认为在已有虚函数的情况下,RTTI是不可避免的。 - Dims
@Dims:你真的需要完整的RTTI来处理虚函数表吗?而且,如果你给所有类都添加无操作方法,不仅会破坏接口类的概念,还会引入表达式问题。 - user824425
@Dims:你关于需要一些C ++的RTTI是正确的;我忘记了多重继承 ;) - user824425
显示剩余5条评论
2个回答

9

如果您从事C++编程,可以尝试使用SFML,它比SDL更快。如果您熟悉OpenGL,也可以使用它。
针对您的问题:

class Entity {
     //constructor and other stuff
     void virtual exec() =0; ///<= pure virtual method
};

class Movable : Entity {
    void move(); //do somthing
    void exec() {move();};
};

class Collidable : Entity {
   void col(); //do your job
   void exec(){col();};
};

std::vector<Entity*> e_v;
///push some instance
for (Entity* e : e_v)
   e->exec();

在C++中,std::vector<Entity>不能胜任此工作,因为无论您放入什么类型,它都会被切片成Entity - Mark B
抱歉,我忘记了“*”。vector<Entity*>。 - Krozark
1
使用C++11甚至TR1或boost的智能指针,许多情况下都不再推荐使用裸指针。我更愿意使用std::vector<std::unique_ptr<Entity>> - Lukas

0

使用模板方法模式和基类中的非抽象空虚拟函数怎么样?

class Entity
{
    void update()
    {
        move();
        checkCollision();
    }

    virtual void move() {}
    virtual void checkCollision() {}
};

class Movable : public virtual Entity
{
    virtual void move() {}   // Stuff
};

class Collidable : public virtual Entity
{
    virtual void checkCollision() {}   // Stuff
};

我喜欢虚拟继承的解决方案。我甚至没有意识到有这样的东西。我一直在尝试类似的方法,但遇到了菱形继承问题。 - shwoseph
在游戏开发中,死亡菱形问题已经通过不使用面向对象编程(OOP)的方式解决。尝试搜索“属性中心”或“组件化”架构。 - user1596212

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接