【问题标题】:Another design-related C++ question另一个与设计相关的 C++ 问题
【发布时间】:2010-04-21 20:45:36
【问题描述】:

嗨!

我正在尝试在 C++ 编码模式中找到一些最佳解决方案,这是我的 游戏引擎 相关问题之一。

看一下游戏对象声明(我删除了几乎所有与问题无关的内容)

// Abstract representation of a game object
class Object : 
public Entity, 
       IRenderable, ISerializable {

   // Object parameters
   // Other not really important stuff

public:
   // @note Rendering template will never change while
   // the object 'lives'
   Object(RenderTemplate& render_template, /* params */) : /*...*/ { }

private:
   // Object rendering template
   RenderTemplate render_template;

public:
   /**
    * Default object render method
    * Draws rendering template data at (X, Y) with (Width, Height) dimensions
    *
    * @note If no appropriate rendering method overload is specified 
    * for any derived class, this method is called
    *
    * @param  Backend & b  
    * @return void
    * @see         
    */
   virtual void Render(Backend& backend) const {
      // Render sprite from object's
      // rendering template structure
      backend.RenderFromTemplate(
         render_template, 
         x, y, width, height
         );
   }
};

这也是 IRenderable 接口声明:

// Objects that can be rendered
interface IRenderable {
   /**
    * Abstract method to render current object
    *
    * @param  Backend & b  
    * @return void
    * @see
    */
   virtual void Render(Backend& b) const = 0;
}

以及源自Object 的真实对象样本(经过严格简化:)

// Ball object
class Ball : public Object {
   // Ball params
public:
   virtual void Render(Backend& b) const {
      b.RenderEllipse(/*params*/);
   }
};

我想要的是拥有某种标准函数的能力,如果没有适当的重载,它将为对象绘制精灵(这是Object::Render)。

因此,可以在没有Render(...) 方法的情况下拥有对象,如果您尝试渲染它们,则会调用此默认的精灵渲染内容。而且,可以有专门的对象,定义自己的渲染方式。

我觉得,这种做事方式还是挺不错的,但是我想不通—— 有什么方法可以将对象的“正常”方法(如Resize(...)Rotate(...))实现与其渲染实现分开?

因为如果一切都按照前面描述的方式完成,实现任何类型对象的通用 .cpp 文件通常会混合 Resize(...) 等方法实现和 virtual Render(...) 方法,这似乎是一团糟。我实际上想在一个地方为对象提供渲染过程,并在另一个地方提供它们的“逻辑实现”。

有没有办法可以做到这一点(可能是替代模式或技巧或提示)或者这是所有这些 多态虚拟 东西在代码放置方面很糟糕的地方?

【问题讨论】:

  • 这并不能回答您的问题,但您的 IRenderable 代码中的虚拟析构函数在哪里?没有它,您将泄漏大量内存。
  • 当我在实际代码中通过new 分配内存时,我会添加它。我现在正在做的事情(仅用于测试和原型制作)通常就像创建Ball ball 并调用ball.Render(backend);。我知道在这种情况下,我什至不需要virtual 的东西,但我想我很快就会制作类似std::vector<IRenderable*> 的东西,然后我实际上需要一个虚拟析构函数。无论如何,谢谢你的指点:)
  • 最好一开始就做,然后结束;为什么要冒着以后忘记的风险?它只有一行代码。

标签: c++ polymorphism virtual design-patterns


【解决方案1】:

在这个特定示例中,我不确定是否需要将ResizeRender 分开,因为Resize 对渲染图像有直接影响。

我的做法是让Entitys 包含 Renderable。例如。 Entity 会有一个成员变量,它是要渲染的精灵。当实体变得可见时,它将精灵交给渲染系统并说,基本上,“好的,每一帧都画这个,直到我告诉你停止”,尽管渲染引擎会剔除屏幕外的对象和其他疯狂的东西。也许很明显,渲染引擎会维护一个可渲染事物的列表(或者,如果你想从线条或粒子中分割精灵以提高渲染效率,可能会维护几个列表),然后它只是在需要的时候翻阅那些不太复杂的项目列表抽奖。

这样,实体可以控制可渲染对象,它可以缩放或旋转它(即操作它的渲染矩阵),但它不需要决定什么时候适合将它绘制到外面它是否“活着”。但是,渲染引擎只知道如何绘制相关项目,而不需要了解您的实体图。

【讨论】:

  • 我可能应该提到 - 使可渲染成为实体的组件而不是基类,使得在任何给定实体类型的实例之间共享可渲染表示成为可能。即使您的关卡中有数十个僵尸,您也只需要加载一组僵尸图形。
  • 这种方法看起来不错,我会尝试重新组织我的代码,看看会发生什么以及我将面临哪些其他问题。非常感谢。
【解决方案2】:

将 IRenderable 从 Entity 中拉出,仅在需要时从该接口继承。

然后使用访问者:



struct rendering_visitor : visitor<Entity>, visitor<IRenderable>
{
  void visit(Entity & e) { ... do default stuff ... }
  void visit(IRenderable & renderable) { renderable.Render(); }
};

请参阅“现代 C++ 设计”以获得良好的访问者实现。你的 Renderable 需要是可访问的,你的实体也是如此。你有一些更高层次的东西,推荐的书没有提到,但并不难弄清楚。

还有其他实现访问者的方法。任君挑选。

【讨论】:

  • 当设计指定对象同时继承自 Entity 和 IRenderable 时,这是如何工作的?我认为在 C++ 中这将是一个模棱两可的调用? (但 C++ 没有“接口”关键字,是吗?)
  • 在 C++ 中将 struct 重新定义为接口以保留一些代码逻辑有点常见......虽然我不确定在这种多重继承情况下应该做什么。
  • @dash-tom-bang - 是的,这可能是个问题。如果两个基类都实现了相同的功能(并且您会使用一些访问者模式),那么您可能会遇到问题。只要您要覆盖的功能来自同一个祖父母,尽管它似乎没问题。无论如何,我都是在一个平台上做的。我遇到麻烦的唯一一次是两个基地都声明了同名的新虚拟。发生这种情况时,您将无法提出不同的想法和/或重新设计树/网络。
  • 我的观点是专门针对访客的。调用 myVisitor.visit(myObject) 是模棱两可的,因为 Object 派生自两个访问的类。您必须改为 myVisitor.visit(static_cast&lt;Entity&gt;(myObject)),从而在这种情况下失去访问者的权力。
猜你喜欢
  • 2011-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多