理论要点
什么是更新方法模式:通过每次处理一帧的行为模拟一系列独立对象。通俗地讲就是每帧更新游戏中我们看到的所有对象的方法(npc,monster,player…)。
要点
1,更新方法模式:在游戏中保持游戏对象的集合。每个对象实现一个更新方法,以处理对象在一帧内的行为。每一帧中,游戏循环对集合中的每一个对象进行更新。2,当离开每帧时,我们也许需要存储下状态,以备不时之需。
使用场合
在游戏中,更新方法和游戏循环模式一般一起使用。更新方法适应以下情况:
1,游戏中有很多对象或系统需要同时运行。
2,每个对象的行为都与其他的大部分独立。
3,游戏中的对象需要随时间模拟。
代码分析
这个模式很简单,所以没有太多值得发现的惊喜。它因为简单而有用:这是一个无需装饰的干净解决方案。它以及游戏循环模式和组件模式,是构建游戏引擎核心的铁三角。
假设这样个游戏情境:在副本门口,一些骷髅在左右循环,旁边还有雕像可以对进入它攻击范围的敌人发射魔法攻击。
好,我们先从代表骷髅和雕像的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_;
double y_;
};
我们这里只是实现update的思路,真实代码中可以想象的应该还会有很多图形和物理的操作。
游戏管理实体的集合。在我们的示例中,我会把它放在一个代表游戏世界的类中。
class World
{
public:
World()
: numEntities_(0)
{}
void gameLoop();
private:
Entity* entities_[MAX_ENTITIES];
int numEntities_;
};
现在,万事俱备,游戏通过每帧更新每个实体来实现更新方法模式:
void World::gameLoop()
{
while (true)
{
// 处理用户输入……
// 更新每个实体
for (int i = 0; i < numEntities_; i++)
{
entities_[i]->update();
}
// 物理和渲染……
}
}
更新方法就只剩一个具体子类的实现了。实现之前我必须说明下:继承的方式往往会是个糟糕的点子,没人可以不拆解它们来管理庞杂的对象层次。所以我们应该多用“对象组合”,而非“类继承”。
如果我真正在做游戏我应该会使用组件模式。 这样,update()是实体的组件而不是在Entity基类中。 这让你避开了为了定义和重用行为而创建实体所需的复杂类继承层次。相反,你只需混合和组装组件。
这节不是讲关于组件的, 而是关于update()方法。为了最简单,最少牵连其他部分的介绍方法, 这里就把更新方法放在Entity中然后创建一些子类。
好了,让我们从我们的骷髅朋友开始吧。 为了定义它的巡逻行为,我们这样实现update():
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()
{
if (++frames_ == delay_)
{
shootLightning();
// 重置计时器
frames_ = 0;
}
}
private:
int frames_;
int delay_;
void shootLightning()
{
// 火光效果……
}
};
这个模式让我们分离了游戏世界的构建和实现。 这同样能让我们灵活地使用分散的数据文件或关卡编辑器来构建游戏世界。
好,更新方法就先讲到这了,结束~