更新方法

“通过对所有对象实例同时进行帧更新来模拟一系列相互独立的游戏对象”

比如有一个NPC,我们的代码肯定不能这样写

while(true)
{
    // Patrol right
    
    for (double x = 0; x < 100; x++) skeleton.setX(x);
    
    // Patrol left

    for (double x = 100; x > 0; x--) skeleton.setX(x);
}

程序被一个死循环锁住,这显然是很差劲的游戏体验,我们所希望的是骷髅兵每一帧走一步。

Entity skeleton; 
bool patrollingLeft = false; 
double x = 0; 

// Main game loop:

while (true) 
{  
 if (patrollingLeft) // 向左巡逻 
 {  
   x--;  
   if (x == 0) patrollingLeft = false; 
  }
  else // 向右巡逻
 {   
  x++;  
  if (x == 100) patrollingLeft = true;  
 } 
 skeleton.setX(x);  

 // Handle user input and render game...
 } 

将游戏逻辑直接塞到一个循环中,很明显没法维护。

更新方法模式在如下情景最为适用:

int numObjectsThisTurn = numObjects_;
for (int i = 0; i < numObjectsThisTurn; i++) 
{  
 objects_[i]>update(); 
}

一个令人担忧的问题是在迭代时移除对象,比如进行下标i的update,然后删除下标i,然后我们i++了,就会造成漏处理一个对象,它被跳过了。

一个简便的解决方法是当你更新时从表的末尾开始遍历,此方法下移除对象,只会让已经更新的物品发生移动,永远向下标小的对象遍历, 我们永远不会提前删除未遍历到的下标小的。

一个Entity类

class Entity
{ 
public:
 Entity()  
 : x_(0), y_(0)  {}  

 virtual ~Entity() {}  
 virtual void update() = 0;  

 double x() const { return x_; }  
 double y() const { return y_; }  

 void setX(double x) { x_ = x; }  
 void setY(double y) { y_ = y; } 

private:
 double x_,y_; 
};

World类

class World
{ 
public:
 World()  
 : numEntities_(0)  {}  

 void gameLoop(); 

private:
 Entity* entities_[MAX_ENTITIES];  
 int numEntities_; 
};

World的gameLoop就是游戏逻辑主循环

void World::gameLoop() 
{  
 while (true)  
 {   
  // Handle user input...



  // Update each entity.


  for (int i = 0; i < numEntities_; i++)  
  {    
   entities_[i]->update();
  }   

  // Physics and rendering...

 
 } 
} 

定义实体继承Entity

定义一个骷髅守卫

class Skeleton : public Entity 
{
public:
 Skeleton()  
 : patrollingLeft_(false) {}  

 virtual void update() 
 {   
  if (patrollingLeft_)   
  {    
   setX(x()1);    
   if (x() == 0) patrollingLeft_ = false;   
  }   
  else
  {    
   setX(x() + 1);    
   if (x() == 100) patrollingLeft_ = true;   
  }
 } 
private:
 bool patrollingLeft_; 
};

定义防守雕像

class Statue : public Entity 
{
public:
 Statue(int delay)  
 : frames_(0),   
  delay_(delay)  
 {}  

 virtual void update()  
 {
    // 跌倒多少逻辑帧然后就调用一下shootLightning
  if (++frames_ == delay_)   
  {    
   shootLightning();    

   // Reset the timer.


   frames_ = 0;   
  }  
 } 

private:
 int frames_; 
 int delay_;
 void shootLightning()  
 {   
  // Shoot the lightning...

 } 
}; 

逝去的时间

这就又扯到前面的游戏循环了,至此我们假设每次对 update 的调用会让整个游戏世界向前推进相同固定的时间长度。

void Skeleton::update(double elapsed) 
{  
 if (patrollingLeft_)  
 {   
  x - = elapsed;   
  if (x <= 0)   
  {    
   patrollingLeft_ = false;    
   x = - x;   
  }  
}  
else
{  
   x += elapsed;   
  if (x >= 100)   
  {    
   patrollingLeft_ = true;   
   x = 100 - (x - 100);   
  }  
 } 
}

现在,骷髅移动的距离随着时间间隔而增长,同样能看到处理变时步长时额外增加的复杂度。骷髅可能在很长的时间差下超出其巡逻范围,我们需要小心地对这一情况进行处理。

update方法依存于何类中

在entity下挂载很多组件,然后Entity的update调用所有组件的update,向下面这样

class Entity{
    int cmptNum;
    Cmpt* cmptArr[MAX_LEN];
};
void Entity::update()
{
    for(int i = 0; i < cmptNum; i++)
    {
        cmptArr[i]->update();
    }
}
void Entity::update()
{
    // Forward to state object
    state_->update();
}

那些未被利用的对象该如何处理

常需要在游戏中维护这样一些对象:不论出于何种原因,它们暂时无需被更新。它们可能被禁用,被移除出屏幕,或者至今尚未解锁。假如大量的对象处于这种状态,则可能会导致CPU每一帧都浪费许多时间来遍历这些对象却毫无作为。

一种方法是单独维护一个需要被更新的“存活”对象表。当一个对象被禁用时,将它从其中移除。当它重新被启用时,把它添加回表中。这样做,你只需遍历那些实际上有作为的对象即可。