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

C++ 建立游戏对象层次结构模型的最优雅、最有效的方法是什么?(设计困扰),c++,oop,C++,Oop,我有以下代码,想想c++中的simple shooter: // 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 //-------

我有以下代码,想想c++中的simple shooter:

// 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.hpp(后者又包含World.hpp)。这让我感到不舒服——我不觉得world.cpp应该在我更改object.hpp之后重新编译。世界不觉得它应该与对象一起工作。这感觉像是糟糕的设计-如何修复

2,多态性——除了代码重用和游戏实体的逻辑分组(以及如何)之外,它可以也应该被用于任何事情吗?

敌人、子弹和玩家将以不同的方式更新和渲染,因此虚拟更新()和渲染()功能——一个完全相同的界面。它们仍然保存在单独的列表中,原因是它们的交互取决于两个交互对象的列表-两个敌人相互弹跳,一颗子弹和一个敌人相互摧毁等等

这是超越敌人和子弹的功能;这与我无关。我觉得除了他们相同的界面之外,还有一个因素将敌人、子弹和玩家分开,这可以也应该用其他方式表达,让我为他们创建一个单一的列表——因为他们的界面是相同的


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

这可能是我在设计类似程序时遇到的最大问题。我确定的方法是认识到一个对象实际上并不关心它的绝对位置。它所关心的是它周围的一切。因此,
World
对象(我更喜欢对象方法而不是单一对象,原因很多,你可以在互联网上搜索)保持所有对象的位置,他们可以询问世界附近有什么对象,其他对象与之相关的位置,等。
世界
不应该关心
对象的内容
;它几乎可以容纳任何东西,
对象本身将定义它们之间的交互方式。事件也是处理对象交互的一种很好的方式,因为它们为
World
提供了一种方法,可以在不关心
对象是什么的情况下通知
对象

对象
隐藏有关所有对象的信息是一个好主意,不应与隐藏任何
对象
的信息混淆。从人的角度考虑——你了解并保留许多不同的人的信息是合理的,尽管你只能通过遇到他们或让其他人告诉你他们的情况才能找到这些信息

再次编辑:

好的。我很清楚你真正想要的是什么,那就是多重分派——能够对函数调用的多个参数类型进行多态处理,而不仅仅是一个。不幸的是,C++不支持本地的多个调度。
这里有两种选择。您可以尝试使用双重分派或访问者模式复制多个分派,也可以使用
dynamic\u cast
。您想使用哪一种取决于具体情况。如果有很多不同的类型可以使用它,那么
dynamic\u cast
可能是更好的方法。如果你只有几个,双重分派或访客模式可能更合适。

我想你应该把世界的引用交给游戏对象。这是一个相当基本的原则。对于碰撞,世界可以使用来解析特定类型对象的交互方式

这会将不同对象类型的知识排除在主要对象之外,并将其封装在具有特定于交互的知识的对象中

物体必须了解世界 他们来了,询问是否有碰撞 信息和其他对象

简言之,不,他们没有。像World这样的类可以处理其中的大部分,而对象所要做的就是在世界告诉它发生了碰撞时表现出适当的行为


有时,为了做出其他类型的决策,您可能需要对象具有某种上下文。需要时,可以在该点传入世界对象。但是,与其传递世界,不如根据实际执行的查询类型传入一些更小、更相关的对象。很可能您的世界对象正在执行几个不同的角色,并且这些对象只需要暂时访问其中的一个或两个角色。在这种情况下,最好将世界对象拆分为子对象,或者如果不可行,让它实现几个不同的接口。

仔细考虑后,我意识到,尽管对象确实需要访问世界,但将附近的对象提供给对象并不是世界的责任

这就是我所拥有的Arena非对象(从未实例化+所有静态成员,包含所需对象类别的列表-子弹、敌人、视觉效果等),但是它引入了与h类似的依赖结构
// 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
//-----------
#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();
};