建模游戏对象层次结构的最优雅,最有效的方法是什么? (设计困扰)

时间:2009-09-22 21:49:51

标签: c++ oop

我有以下代码,想想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,游戏对象了解其他游戏对象。

必须有一个了解所有物体的设施。它可能会或可能不必暴露所有对象,它必须公开其中的一些,具体取决于询问者的参数(位置,一个)。这个设施应该是世界。

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

这引入了一个依赖项,其中Objects'和World的实现都必须访问对象的头,因此World不会直接包含它自己的头,而不是包含object.hpp(后者又包含world.hpp)。这让我感到不舒服 - 我不觉得world.cpp应该在我对object.hpp进行更改后重新编译。 World不觉得它应该与Object一起使用。这感觉就像糟糕的设计 - 如何解决?

2,多态性 - 除了代码重用和游戏实体的逻辑分组(以及如何)以外,还能用它吗?

Enemies,Bullets和Player将以不同的方式更新和呈现,因此虚拟的Update()和Render()功能 - 一个相同的界面。它们仍然保存在单独的列表中,其原因在于它们的相互作用取决于两个相互作用对象的列表 - 两个敌人互相反弹,一个子弹和一个敌人相互摧毁等。

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

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

4 个答案:

答案 0 :(得分:6)

我认为你想把一个对世界的引用交给游戏对象。这是Dependency Injection的一个非常基本的原则。对于冲突,World可以使用Strategy pattern来解决特定类型的对象如何交互。

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

答案 1 :(得分:5)

这可能是我在设计类似程序时遇到的最大问题。我已经确定的方法是认识到一个对象真的不关心它在绝对意义上的位置。所有它关心的是它周围的东西。因此,World对象(我更喜欢单个对象的方法,出于很多原因,您可以在Internet上搜索)维护所有对象的位置,并且他们可以向全世界询问对象是什么附近,与其相关的其他物品等。World不应该关注Object的内容;它几乎可以容纳任何东西,而Object本身将定义它们如何相互作用。事件也是处理对象交互的好方法,因为它们为World提供了一种方法,可以在不关注Object的情况下通知Object正在发生的事情。

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

再次编辑:

好的。我很清楚你真正想要的是什么,那就是多次调度 - 能够多样化地处理函数调用的许多参数类型的情况,而不仅仅是一个。遗憾的是,C ++本身不支持多个调度。

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

答案 2 :(得分:3)

  

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

简而言之 - 不,他们没有。像世界这样的类可以处理其中的大部分内容,并且当World告诉它发生碰撞时,所有Object必须做的就是表现得恰到好处。

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

答案 3 :(得分:0)

经过深思熟虑之后,我意识到尽管Objects DO 需要访问World,但是 NOT 世界有责任将附近的对象提供给对象。< / p>

这就是我所拥有的Arena非对象(从未实例化+所有静态成员,包含所需对象类别的列表 - 子弹,敌人,视觉效果等),但它引入了类似的依赖结构,因为它具有类别列出作为世界的一部分(这就是为什么它不是一个好的解决方案):

// 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)可以提供“事件处理程序”来命中某些类型的对象;然而,它们不是在对象级别而不是类别级别上声明的。 (例如,我们不关心子弹与子弹碰撞,它们不会被检查,也不会被处理。)

// 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();
};