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

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

 

原文链接:http://www.richardlord.net/blog/what-is-an-entity-framework

原文很长,译文将分开几部分,此为第一部分。

 


 

上周我发布了Ash(一个Actionscript 游戏开发的实体系统框架),后来很多朋友问我说:“什么是实体系统框架(entity system framework)?”。下面是我对此做出的非常长的解答(译者注:确实有些啰嗦。。。):

 

实体系统目前已经越来越流行,比如众所周知的Unity、较少人知道的Ember2Xember和我自己的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...

 

 


 

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

posted @ 2014-07-24 15:21  仙剑无尘  阅读(408)  评论(0编辑  收藏  举报