首页 > 代码库 > 【译】什么是游戏开发实体系统框架(一)

【译】什么是游戏开发实体系统框架(一)

什么是游戏开发实体系统框架(一)

 

原文链接: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...

 

 


 

以上是此篇译文的第一部分,时间关系先写到这里,下篇继续,敬请期待。