将渲染逻辑移出GameObject类的策略


10

制作游戏时,您通常会创建以下所有实体继承的游戏对象:

public class GameObject{
    abstract void Update(...);
    abstract void Draw(...);
}

因此,在更新循环中,您遍历所有游戏对象,并给它们提供更改状态的机会,然后在下一个绘制循环中,您遍历所有游戏对象,并给它们提供绘制自己的机会。

尽管这在带有简单前向渲染器的简单游戏中效果很好,但通常会导致一些巨大的游戏对象需要存储其模型,多种纹理以及最糟糕的胖子绘制方法,从而在游戏对象之间产生紧密的联系,当前的渲染策略以及任何与渲染相关的类。

如果要将渲染策略从向前更改为推迟,则必须更新很多游戏对象。而且,我制作的游戏对象没有尽可能地可重用。当然,继承和/或组合可以帮助我应对代码重复并使更改实现变得容易一些,但仍然感到缺乏。

也许更好的方法是从GameObject类中完全删除Draw方法并创建一个Renderer类。GameObject仍将需要包含一些有关其视觉效果的数据,例如用来表示它的模型以及应该在模型上绘制哪些纹理,但是如何完成将留给渲染器。但是,渲染时通常会遇到很多边界情况,因此尽管这将消除从GameObject到Renderer的紧密耦合,但Renderer仍必须全部了解所有会使它发胖的游戏对象,所有人都知道并紧密耦合。这将违反许多良好做法。也许面向数据的设计可以解决问题。游戏对象肯定是数据,但是渲染器将如何驱动呢?我不确定。

所以我很茫然,无法想到一个好的解决方案。我尝试使用MVC的原理,过去我对如何在游戏中使用它有一些想法,但是最近它似乎并不像我想的那样适用。我很想知道大家如何解决这个问题。

无论如何,让我们回顾一下,我对如何实现以下设计目标很感兴趣。

  • 游戏对象中没有渲染逻辑
  • 游戏对象与渲染引擎之间的松耦合
  • 没有所有知道的渲染器
  • 最好在渲染引擎之间进行运行时切换

理想的项目设置是一个不需要相互引用的单独的“游戏逻辑”和渲染逻辑项目。

当我听到约翰·卡马克在推特上说他的系统如此灵活,他可以在运行时更换渲染引擎,甚至可以告诉他的系统同时使用渲染器(软件渲染器和硬件加速渲染器)时,这种思想训练就开始了。同时他可以检查差异。到目前为止,我编写的系统还没有这么灵活

Answers:


7

分离的第一步:

游戏对象引用的是视觉效果的标识符,而不是数据的标识符,比如字符串之类的简单对象。示例:“ human_male”

渲染器负责加载和维护“ human_male”引用,并将要使用的句柄传递回对象。

可怕的伪代码示例:

GameObject( initialization parameters )
  me.render_handle = Renderer_Create( parameters.render_string )

- elsewhere
Renderer_Create( string )

  new data handle = Resources_Load( string );
  return new data handle

- some time later
GameObject( something happens to me parameters )
  me.state = something.what_happens
  Renderer_ApplyState( me.render_handle, me.state.effect_type )

- some time later
Renderer_Render()
  for each renderable thing
    for each rendering back end
        setup graphics for thing.effect
        render it

- finally
GameObject_Destroy()
  Renderer_Destroy( me.render_handle )

对不起,对于这种混乱,无论如何,您都可以通过简单的更改来满足您的条件,该更改从基于实际对象之类的纯OOP转变为基于职责的OOP。

  • 游戏对象中没有渲染逻辑(完成后,所有对象都知道是一个手柄,因此可以对其应用效果)
  • 游戏对象与渲染引擎之间的耦合松散(完成,所有接触都是通过抽象手柄进行的,可以应用的状态,而不是对这些状态的处理)
  • 没有所有知道的渲染器(完成,仅了解自身)
  • 最好在渲染引擎之间进行运行时切换(这是在Renderer_Render()阶段完成的,但是您必须同时编写两个后端)

您可以搜索的关键字不仅限于简单的类重构,还可以包括“实体/组件系统”和“依赖注入”以及潜在的“ MVC” GUI模式,以使旧的大脑运转。


这与我之前所做的工作完全不同,听起来好像很有潜力。幸运的是,我不受任何现有引擎的束缚,因此我可以进行修改。我也将查找您提到的术语,尽管依赖项注入总是使我的大脑受伤:P。
罗伊(Roy T.)

2

我为自己的引擎所做的就是将所有内容分组为模块。因此,我上了GameObject课,并且拥有以下内容的句柄:

  • ModuleSprite-绘制精灵
  • ModuleWeapon-射击枪
  • ModuleScriptingBase-脚本编制
  • ModuleParticles-粒子效果
  • ModuleCollision-碰撞检测和响应

所以我有PlayerBullet堂课。两者都衍生自GameObject并添加到中Scene。但是Player具有以下模块:

  • ModuleSprite
  • 模组武器
  • ModuleParticles
  • 模块碰撞

Bullet具有以下模块:

  • ModuleSprite
  • 模块碰撞

这种组织事物的方式避免了“死亡钻石”,即您拥有a Vehicle,a VehicleLand和a,VehicleWater而现在您想要a VehicleAmphibious。相反,您有一个Vehicle,它可以有一个ModuleWater和一个ModuleLand

增加的好处:您可以使用一组属性来创建对象。您只需要知道基本类型(Player,Enemy,Bullet等),然后为该类型所需的模块创建句柄即可。

在我的场景中,我执行以下操作:

  • 调用Update所有GameObject句柄。
  • 对具有ModuleCollision手柄的对象进行碰撞检查和碰撞响应。
  • 呼叫UpdatePost所有GameObject手柄,以了解它们在物理之后的最终位置。
  • 销毁设置了标志的对象。
  • m_ObjectsCreated列表中的新对象添加到m_Objects列表中。

而且我可以进一步组织它:通过模块而不是对象。然后,我将呈现的清单ModuleSprite,更新一堆ModuleScriptingBase清单并使用清单进行碰撞ModuleCollision


听起来就像最大程度的构图!非常好。我在这里看不到太多渲染特定的技巧。您如何通过添加不同的模块来处理该问题?
罗伊(Roy T.)2012年

哦是的 这是该系统的缺点:如果您有特定的要求GameObject(例如,一种渲染Sprite的“蛇”的方式),则需要ModuleSprite为该特定功能创建子项(ModuleSpriteSnake)或一起添加新模块(ModuleSnake)。幸运的是,他们只是指针,但我已经看到代码,其中GameObject从字面上做的一切物体可以做的。
knight666
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.