2009-09-22 71 views
9

我有下面的代码,认为简单的射击游戏在C++:建模游戏对象层次结构的最优雅和最有效的方法是什么? (设计困扰)

// world.hpp 
//---------- 
class Enemy; 
class Bullet; 
class Player; 

struct World 
{ 
    // has-a collision map 
    // has-a list of Enemies 
    // has-a list of Bullets 
    // has-a pointer to a player 
}; 

// object.hpp 
//----------- 
#include "world.hpp" 

struct Object 
{ 
    virtual ~Object(); 
    virtual void Update() =0; 
    virtual void Render() const =0; 

    Float xPos, yPos, xVel, yVel, radius; // etc. 
}; 

struct Enemy: public Object 
{ 
    virtual ~Enemy(); 
    virtual void Update(); 
    virtual void Render() const; 
}; 

// Bullet and Player are similar (they render and update differently per se, 
/// but the behavior exposed to World is similar) 

// world.cpp 
//---------- 
#include "object.hpp" 

// object.cpp 
//----------- 
#include "object.hpp" 

两个问题:

1,游戏对象知道关于其他游戏对象。

必须有一个知道所有物体的设施。它可能或可能不需要公开所有对象,它必须公开一些,取决于参数(查询者的位置)。这个设施应该是世界。

对象必须知道他们所在的世界,以查询碰撞信息和其他对象。

这引入了一个依赖关系,其中Object和World的实现都必须访问对象的头部,因此World不会直接包含它自己的头部,而不是包含object.hpp(其中包含world.hpp)。这让我感觉不舒服 - 我不觉得world.cpp应该在对object.hpp进行更改后重新编译。世界并不觉得它应该与Object一起工作。这感觉像不好的设计 - 它如何被修复?

2,多态 - 能否也应该用于除代码重用和游戏实体逻辑分组以外的任何事情?

敌人,项目符号和玩家会更新和渲染不同,当然,因此虚拟Update()和Render()功能 - 相同的界面。它们仍然保存在单独的列表中,其原因是它们的交互取决于两个交互对象是哪个列表 - 两个敌人彼此反弹,子弹和敌人相互破坏等。

这是功能这超出了敌人和子弹的实施;这不是我关心的地方。我觉得除了它们相同的界面之外,还有一个因素可以区分敌人,子弹和玩家,这可以并且应该以其他方式表达,允许我为它们创建一个列表 - 因为它们的界面是相同的!

问题是如何判断包含在同一列表中的对象类别。 Typeid或其他形式的类型识别是不可能的 - 但接下来还有什么其他方式可以做到这一点?或者,这种方法没有错?

回答

5

这可能是我在设计类似程序时遇到的最大问题。我已经解决的方法是意识到一个对象实际上并不关心它的绝对位置。它所关心的只是它周围的事物。结果,World对象(并且我更喜欢对单身人士的对象方法,因为可以在Internet上搜索的许多好理由)保留了所有对象的位置,并且他们可以问世界哪些对象在附近,其他位置对象是与它有关的,等等。World不应该关心Object的内容;它将持有几乎任何东西,而且它们自己将定义它们如何相互交互。事件也是处理对象交互的好方法,因为它们为World提供了一种方式,告知Object发生了什么,而不关心Object是什么。

Object隐藏信息有关所有对象是一个好主意,不应该与隐藏有关任何Object信息混淆。从人的角度来看 - 对于你来说,了解和保留关于许多不同人的信息是合理的,尽管你只能通过遇到他们或让其他人告诉你这些信息来找到这些信息。

编辑再次:

好的。我很清楚你真正想要什么,这就是多重​​调度 - 能够在函数调用的多个参数类型上多态地处理一个情况,而不仅仅是一个。不幸的是,C++本身不支持多次调度。

这里有两个选项。您可以尝试使用双派或副访问者模式重现多个派遣,也可以使用dynamic_cast。你想使用哪个取决于具体情况。如果你有很多不同的类型可以使用它,dynamic_cast可能是更好的方法。如果你只有几个,双重派遣或访问者模式可能更合适。

+0

谢谢你前两段;这让我感觉很有意义,也有助于清除我的头脑。我已经重写了我的问题,以澄清我想用多态性做什么。 – zyndor 2009-09-23 09:37:30

+0

访客模式很棒,谢谢指出。我猜OnHit()方法类似于模式的Visit()方法,而Objects就是他们自己的访问者,对吧? – zyndor 2009-09-24 15:59:18

+1

选择一个答案接受为最好的答案是困难的,因为他们都有很好的积分,但没有一个是全能的。我觉得关于对象 - 世界交互的设计大纲是最有用的一点信息,并且与我的问题最相关。 - – zyndor 2009-10-14 08:00:38

6

我想你会想把对世界的引用交给游戏对象。这是一个非常基本的原则Dependency Injection。对于碰撞,世界可以使用Strategy pattern来解析特定类型的对象如何相互作用。

这样可以将主要对象之外的不同对象类型的知识保存在对象中,并将其封装在具有特定于交互的知识的对象中。

+0

这是一个解决方案(OO设计和性能方面)太糟糕了,而不是每次将参考传递给World,只是将一个静态成员指针存储到World,可供所有对象访问? (不是以原始公共形式提交的,也许 - 可能是一个受保护的指针和一个用于设置它的公共静态方法。) – zyndor 2009-09-24 15:53:46

+0

如何存储对World对象的引用取决于您。由于这些对象可能不会同时在多个世界中实例化,因此在类上存储引用可能没问题。 关键的想法是,世界仍然在实例化时被提供给单个对象,所以如果你想在不同世界中拥有两个相同类型的对象,那么实例API不会改变,唯一需要重构的重构是在对象本身的实现范围内。 – 2009-09-24 20:51:05

3

对象必须了解世界 他们所在,来查询碰撞 信息和其他对象。

总之 - 不,他们不。像世界这样的类可以处理大部分的事情,当世界告诉它发生了碰撞时,对象所要做的就是适当的行为。

有时您可能需要Object才能拥有某种上下文才能做出其他类型的决定。您可以在需要时传递World对象。但是,我不想传递世界,最好传递一些更小,更相关的对象,这取决于实际执行哪种查询。很有可能您的World对象执行多种不同的角色,对象只需要对这些角色中的一个或两个角色进行临时访问。在这种情况下,将World对象拆分为子对象是很好的做法,或者如果这样做不切实际,可以让它实现几个不同的接口。

0

给它左思右想,我意识到,尽管对象DO需要访问一个世界,它是世界的责任就可以提供附近的对象到对象。

这就是我的竞技场非对象(从未实例化过的所有静态成员,包含所需对象类别列表 - 子弹,敌人,视觉效果等)。)的,但它推出了类似的依存结构为具有类别列表作为世界的一部分(这就是为什么它不觉得自己是一个很好的解决方案):

// object.hpp 
//----------- 
#include "world.hpp" 

// NOTE: the obvious virtual and pure virtual methods are omitted in the following code 
class Object 
{...}; 

class Enemy: public Object 
{...}; 

class Bullet: public Object 
{...}; 

class Player: public Object 
{...}; 

// arena.hpp 
//----------- 
#include "object.hpp" 

struct Arena 
{ 
    // has-a lists of object categories and a (pointer to a) player 
} 

// object.cpp 
//----------- 
#include "arena.hpp" // for the damn dependencies 

// arena.cpp 
//----------- 
#include "arena.hpp" 

因此,解决方案,或好像有什么在这一点上,是让整个对象(分类)不是一个编译级别高于或低于对象的声明,而是在相同级别上的

// object.hpp 
//----------- 
#include "world.hpp" 

class Object 
{ 
    static World *pWorld; 
    ... 
}; 

class Enemy: public Object 
{ 
    typedef std::list<Enemy*> InstList; 
    static InstList insts; 
    ... 
}; 

class Bullet: public Object 
{ 
    typedef std::list<Bullet*> InstList; 
    static InstList insts; 
    ... 
}; 

class Player: public Object 
{ 
    static Player *pThePlayer; 
    ... 
}; 

// object.cpp 
//----------- 
#include "object.hpp" 

即使有对专业的敌人,子弹等他们(和其他人的)另外的标题类别名单完全为他们提供通过包括object.hpp他们显然必须反正。

关于多态性位以及为什么不同类别保存在单独的列表中:对象类别的基类(Bullet,Enemy,Player)可以提供“事件处理程序”来触击某些类型的对象;但是,它们不是在对象级别上而是在类别级别上声明的。 (例如,我们不关心子弹VS子弹的碰撞,他们没有检查,没有处理。)

// object.hpp 
//----------- 
#include "world.hpp" 

class Object 
{ 
    static World *pWorld; 
    ... 
}; 

class Bullet: public Object 
{ 
    typedef std::list<Bullet*> InstList; 
    static InstList insts; 
    ... 
}; 

class Player: public Object 
{ 
    static Player *pThePlayer; 

    void OnHitBullet(const Bullet *b); 
    ... 
}; 

class Enemy: public Object 
{ 
    typedef std::list<Enemy*> InstList; 
    static InstList insts; 

    virtual void OnHitBullet(const Bullet *b); 
    virtual void OnHitPlayer(const Player *p); 
    ... 
}; 

编辑,为了完整性:

// game.hpp 
//----------- 
#include "object.hpp" 

struct Game 
{ 
    static World world; 

    static void Update(); // update world and objects, check for collisions and 
         /// handle them. 
    static void Render(); 
}; 
相关问题