游戏中绘画和逻辑的分离

我是一个刚刚开始混淆游戏开发的开发者。 我是一个.Net人,所以我已经与XNA混为一谈,现在正在为Cocos2d玩iPhone。 我的问题确实比较一般。

假设我正在构建一个简单的Pong游戏。 我会有一个Ball和一个Paddleclass。 从商业世界的发展来看,我的第一本能是在这些类中都没有任何绘图或input处理代码。

 //pseudo code class Ball { Vector2D position; Vector2D velocity; Color color; void Move(){} } 

球类中没有处理input或处理绘图。 然后我会有另外一个class级,我的Game类,或者我的Scene.m (在Cocos2D中),这会增加球的数量,在游戏循环中,它会根据需要操纵球。

不过,在许多XNA和Cocos2D的教程中,我都看到了这样一个模式:

 //pseudo code class Ball : SomeUpdatableComponent { Vector2D position; Vector2D velocity; Color color; void Update(){} void Draw(){} void HandleInput(){} } 

我的问题是,这是正确的吗? 这是人们在游戏开发中使用的模式吗? 它以某种方式违背了我以前的一切,让我的球class做一切。 而且,在第二个例子中,我的Ball知道如何四处移动,我将如何处理碰撞检测? Ball需要掌握Paddle知识吗? 在我的第一个例子中, Game类将引用BallPaddle ,然后将这两个Paddle到某个CollisionDetection管理器或某个东西,但是如何处理各个组件的复杂性,如果每个组件一切都自己? (我希望我有道理…..)

我的问题是,这是正确的吗? 这是人们在游戏开发中使用的模式吗?

游戏开发者倾向于使用任何作品。 这不是严格的,但嘿,它运送。

它以某种方式违背了我以前的一切,让我的球class做一切。

所以,你在那里有一个更广义的成语是handleMessages / update / draw。 在大量使用消息的系统中(游戏实体有其优点和缺点),游戏实体可以抓取它所关心的任何消息,对这些消息进行逻辑处理,然后自行绘制。

请注意,draw()调用可能实际上并不意味着实体调用putPixel或其内部的任何东西; 在某些情况下,这可能意味着更新稍后由负责绘图的代码进行轮询的数据。 在更高级的情况下,它通过调用渲染器提供的方法来“绘制”自己。 在我现在正在使用的技术中,对象将使用渲染器调用来绘制自己,然后每个渲染线程组织所有对象所做的所有调用,优化状态变化等事情,然后绘制整个事物发生在与游戏逻辑分离的线程上,所以我们得到asynchronous渲染)。

责任的授权取决于程序员; 对于一个简单的pong游戏来说,每个对象完全处理自己的绘图(完成低级调用)是有道理的。 这是一个风格和智慧的问题。

而且,在第二个例子中,我的球知道如何四处移动,我将如何处理桨的碰撞检测? 球会需要掌握桨的知识吗?

所以,解决这个问题的一个自然的方法就是让每个对象都把其他对象当作某种参数来检查冲突。 这个规模不好,可能会有奇怪的结果。

让每个类实现某种Collidable接口,然后在游戏的更新阶段拥有一个高层次的代码循环遍历所有Collidable对象,并处理冲突,根据需要设置对象的位置。

再次,你只需要开发一个感觉(或者下定决心),就可以为你的游戏中的不同事物负责。 渲染是一个独立的活动,可以由一个真空中的物体来处理; 碰撞和物理学一般不能。

在我的第一个例子中,Game类将引用Ball和Paddle,然后将这两个引导到某个CollisionDetection管理器或某个东西,但是如何处理各个组件的复杂性,如果每个组件一切都自己?

看到上面的这个探索。 如果您的语言支持接口(即Java,C#),这很容易。 如果你在C ++中,这可能是…有趣的。 我喜欢使用消息传递来解决这些问题(类处理消息,他们可以忽略其他),一些其他人喜欢组件组成。

再次,无论什么作品,最简单的你。

我最近做了一个简单的太空Invadors游戏使用“实体系统”。 这是一种非常好的分离属性和行为的模式。 我花了一些时间去完全理解它,但是一旦你devise了一些组件,使用你现有的组件来编写新的对象变得非常简单。

你应该读这个:

http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/

这是一个非常有知识的人经常更新。 这也是唯一的实体系统讨论与具体代码的例子。

我的迭代如下:

第一次迭代有一个像Adam描述的“EntitySystem”对象; 但是我的组件仍然有方法 – 我的“可renderable”组件有一个paint()方法,我的位置组件有一个move()方法等。当我开始充实实体时,我意识到我需要开始传递消息组件和订购组件更新的执行….方式太凌乱了。

所以,我回去重新阅读T-machine博客。 评论线程中有很多信息,而且他们真的强调组件没有行为 – 行为是由实体系统提供的。 通过这种方式,您不需要在组件之间传递消息并对组件更新进行sorting,因为sorting由系统执行的全局顺序决定。 好。 也许这太抽象了。

无论如何,迭代#2这是我从博客收集:

EntityManager – 作为组件“数据库”,可以查询包含某些types组件的实体。 这甚至可以通过快速访问的内存数据库来支持…更多信息请参见t-machine第5部分。

实体系统(EntitySystem) – 每个系统本质上只是一个在一组实体上运行的方法。 每个系统将使用一个实体的组件x,y和z来完成它的工作。 因此,您将查询管理器的组件x,y和z的实体,然后将该结果传递给系统。

实体 – 只是一个ID,像一个长。 该实体将一组组件实例组合成一个“实体”。

组件 – 一组领域….没有行为! 当你开始添加行为时,它开始变得混乱……即使在一个简单的太空Invadors游戏。

编辑 :顺便说一下,“dt”是自上一次主循环调用以来的增量时间

所以我的主要invadors循环是这样的:

 Collection<Entity> entitiesWithGuns = manager.getEntitiesWith(Gun.class); Collection<Entity> entitiesWithDamagable = manager.getEntitiesWith(BulletDamagable.class); Collection<Entity> entitiesWithInvadorDamagable = manager.getEntitiesWith(InvadorDamagable.class); keyboardShipControllerSystem.update(entitiesWithGuns, dt); touchInputSystem.update(entitiesWithGuns, dt); Collection<Entity> entitiesWithInvadorMovement = manager.getEntitiesWith(InvadorMovement.class); invadorMovementSystem.update(entitiesWithInvadorMovement); Collection<Entity> entitiesWithVelocity = manager.getEntitiesWith(Velocity.class); movementSystem.move(entitiesWithVelocity, dt); gunSystem.update(entitiesWithGuns, System.currentTimeMillis()); Collection<Entity> entitiesWithPositionAndForm = manager.getEntitiesWith(Position.class, Form.class); collisionSystem.checkCollisions(entitiesWithPositionAndForm); 

起初看起来有些不可思议,但它非常灵活。 这也很容易优化; 对于不同的组件types,您可以拥有不同的备份数据存储,以便更快地检索。 对于“表单”类,您可以使用四叉树来加速碰撞检测。

我像你; 我是一个经验丰富的开发人员,但没有经验写游戏。 我花了一段时间研究了开发模式,这引起了我的注意。 这绝不是唯一的办法,但是我发现它非常直观和强大。 我相信这个模式已经在“Game Programming Gems”系列的第6集中正式讨论过了 – http://www.amazon.com/Game-Programming-Gems/dp/1584500492 。 我自己没有读过任何书,但我听说他们是游戏编程的事实上的参考。

编程游戏时没有明确的规则。 我发现这两种模式完全可以接受,只要你在游戏中遵循相同的devise模式。 你会惊讶的发现,还有更多的游戏devise模式,其中有些甚至不在OOP之上。

现在,根据我的个人偏好,我更喜欢用行为分隔代码(一个类/线程绘制,另一个更新),而最小化对象。 我发现它更容易并行化,而且我经常发现自己在同一时间处理更less的文件。 我会说这更像是一种程序性的做事方式。

但是如果你来自商业世界,那么你可能更习惯于编写知道如何为自己做所有事情的课程。

再一次,我建议你写你觉得最自然的东西。

“球”对象具有属性和行为。 我觉得把对象的独特属性和行为包含在自己的类中是有意义的。 Ball对象更新的方式与更新桨的方式不同,因此这些都是唯一的行为,应该被包含在类中。 和绘图一样。 桨的绘制方式和球的方式往往有独特的区别,我发现修改单个对象的绘制方法以适应这些需求比在另一个类中进行有条件的评估来解决它们更容易。

Pong在对象和行为的数量上是一个相对简单的游戏,但是当你进入数十个或数百个独特的游戏对象时,你可能发现你的第二个例子更容易模块化一切。

大多数人使用一个input类,其结果通过服务或静态类可用于所有的游戏对象。

我认为你在商业世界中可能使用的是抽象和实现。

在游戏开发的世界里,你通常想用速度来思考。 更多的对象你有更多的上下文切换会发生这可能会减慢速度取决于当前的负载。

这只是我的猜测,因为我是一个崇拜游戏的开发者,但是一个专业的开发者。

不,这不是“正确的”或“错误的”,只是一种简化。

一些业务代码是完全一样的,除非你正在使用强制分离表示和逻辑的系统。

在这里,一个VB.NET的例子,其中“业务逻辑”(添加一个数字)写在一个GUI事件处理程序 – http://www.homeandlearn.co.uk/net/nets1p12.html

如何处理各个组件的复杂性,如果每个组件自己做所有事情?

如果当它变得复杂,那么你可以把它分解出来。

 class Ball { GraphicalModel ballVisual; void Draw() { ballVisual.Draw(); } }; 

从我的发展经验来看,除非您所在的小组有一个公认的做法,否则没有正确或错误的做法。 我遇到了反对开发者的反对,他们反对分离出行为,皮肤等等。我相信他们会对我的保留写同样的东西,写下一个包括日光下所有东西的课程。 记住,你不仅要写它,而且要能够在明天和未来的date读取你的代码,在下一次迭代中对其进行debugging,并且可能对它进行构建。 你应该select一个适合你的path(和团队)。 select别人使用的路线(对你来说是违反直觉的)会使你放慢脚步。