什么是游戏开发实体系统框架(一)
原文链接:http://www.richardlord.net/blog/what-is-an-entity-framework
原文很长,译文将分开几部分,此为第一部分。
上周我发布了Ash(一个Actionscript 游戏开发的实体系统框架),后来很多朋友问我说:“什么是实体系统框架(entity system framework)?”。下面是我对此做出的非常长的解答(译者注:确实有些啰嗦。。。):
实体系统目前已经越来越流行,比如众所周知的Unity、较少人知道的Ember2, Xember和我自己的Ash。它们流行的原因很简单:简单的游戏框架、简明的代码模块以及简易的使用。
这篇文章我将向大家展示,传统的游戏逻辑(game loop)是如何进化为一个实体框架的。由于目前我正在使用Actionscript做开发,所以文章中的实例代码都是用Actionscript的,但是此框架对所有语言都适用。
关于示例
贯穿全文,我将会使用一个简单的Asteroids游戏作为示例,Asteroids就像一个简化版的大型游戏,麻雀虽小五脏俱全,包含一套典型的系统:刷新系统、物理系统、AI系统、输入系统、NPC系统。
游戏主循环(The game loop)
要了解为什么要用实体系统,首先我们需要明白传统的游戏循环是如何工作的。Asteroids游戏的主循环可能是这样工作的:
function update( time:Number ):void { game.update( time ); spaceship.updateInputs( time ); for each( var flyingSaucer:FlyingSaucer in flyingSaucers ) { flyingSaucer.updateAI( time ); } spaceship.update( time ); for each( var flyingSaucer:FlyingSaucer in flyingSaucers ) { flyingSaucer.update( time ); } for each( var asteroid:Asteroid in asteroids ) { asteroid.update( time ); } for each( var bullet:Bullet in bullets ) { bullet.update( time ); } collisionManager.update( time ); spaceship.render(); for each( var flyingSaucer:FlyingSaucer in flyingSaucers ) { flyingSaucer.render(); } for each( var asteroid:Asteroid in asteroids ) { asteroid.render(); } for each( var bullet:Bullet in bullets ) { bullet.render(); } }
这个主循环会以一个特定的周期被循环调用来更新游戏状态,通常是60次/秒或者30次/秒。一般在主循环里我们会处理各种重要的游戏逻辑,比如更新游戏中的各种物件、检测他们之间的碰撞、绘制等等。
上面的代码是一个非常简单的主循环,因为:
1. 游戏本身很简单
2. 游戏只有一种状态
在过去,我曾经在一个电视游戏的主循环一个函数中,塞进了超过3000行的代码。这让它看起来一点都不优美,一点都不简明。这就是过去我们编写游戏的方式,并且不得不伴其一生。
实体系统框架起源于一次对游戏主循环重构的尝试。它假设游戏主循环就是一个游戏的核心,并且在现代游戏框架中简化游戏主循环比其他任何事情都重要,比如比将视图和控制分离更重要。
进化过程(Processes)
进化第一步,要回想一个模块被调用的过程(think about objects called processes)。这件模块可以被初始化、被更新、被销毁。这样的过程接口看起来可能是这个样子的:
interface IProcess { function start():Boolean; function update( time:Number ):void; function end():void; }
我们可以通过分解不同的过程来简化主循环,比如渲染过程、运动过程、碰撞处理等。然后我们可以创建一个过程管理器(process manager)来管理这些过程。
class ProcessManager { private var processes:PrioritisedList; public function addProcess( process:IProcess, priority:int ):Boolean { if( process.start() ) { processes.add( process, priority ); return true; } return false; } public function update( time:Number ):void { for each( var process:IProcess in processes ) { process.update( time ); } } public function removeProcess( process:IProcess ):void { process.end(); processes.remove( process ); } }
这是一个略显简单的过程管理器。这里要重点强调的是,我们必须保证各个过程的调用是按正确的顺序进行的(由add方法中的priority参数决定),并且我们必须处理一个过程(process)被从update循环中移除的情况。这样的话,你可能会想到,如果我们的游戏主循环被分解为多个子过程,那么过程管理器的update方法也就等价于过去的游戏主循环,子过程的集合也就变成了游戏的核心(the core of the game)。
绘制过程(The render process)
让我们以绘制过程为例。我们可以把过去游戏主循环中有关绘制的代码拉取出来,放进一个单独的过程,看起来像这样:
class RenderProcess implements IProcess { public function start() : Boolean { // initialise render system return true; } public function update( time:Number ):void { spaceship.render(); for each( var flyingSaucer:FlyingSaucer in flyingSaucers ) { flyingSaucer.render(); } for each( var asteroid:Asteroid in asteroids ) { asteroid.render(); } for each( var bullet:Bullet in bullets ) { bullet.render(); } } public function end() : void { // clean-up render system } }
使用接口
但是这并不是特别有效。我们仍然需要手动处理各种不同类型的类的刷新。如果我们有个所有可刷新类通用的接口,代码将会更一步的简化。
interface IRenderable { function render(); }
class RenderProcess implements IProcess { private var targets:Vector.<IRenderable>; public function start() : Boolean { // initialise render system return true; } public function update( time:Number ):void { for each( var target:IRenderable in targets ) { target.render(); } } public function end() : void { // clean-up render system } }
然后我们的spaceship类的部分代码将会变成这样:
class Spaceship implements IRenderable { public var view:DisplayObject; public var position:Point; public var rotation:Number; public function render():void { view.x = position.x; view.y = position.y; view.rotation = rotation; } }
这里的代码是以2D游戏为例,但是3D游戏的原理是一样的。我们需要绘制图片,进而需要绘制图片所需要的位置和旋转信息,然后,render方法处理刷新的实现。
To be continued...
以上是此篇译文的第一部分,时间关系先写到这里,下篇继续,敬请期待。
【译】什么是游戏开发实体系统框架(一)