C++ 避免在虚拟调用上循环的模式

C++ 避免在虚拟调用上循环的模式,c++,design-patterns,C++,Design Patterns,在我的游戏中,我通常让每个NPC/物品等从基类“实体”派生。然后它们基本上都有一个叫做“更新”的虚拟方法,我会在每一帧为游戏中的每个实体分类。我假设这种模式有很多缺点。 在整个游戏中,还有哪些其他方法可以管理不同的“游戏对象”?这方面还有其他众所周知的模式吗? 我基本上是想找到更好的模型,而不是从基类派生所有东西,基类将跨越巨大的继承树和无处不在的虚拟函数。还有另一种模式涉及行为继承 例如:一把剑(在一个幻想风格的游戏中)可以继承自descripable,以便能够向玩家描述它;它还将继承处理昏迷

在我的游戏中,我通常让每个NPC/物品等从基类“实体”派生。然后它们基本上都有一个叫做“更新”的虚拟方法,我会在每一帧为游戏中的每个实体分类。我假设这种模式有很多缺点。 在整个游戏中,还有哪些其他方法可以管理不同的“游戏对象”?这方面还有其他众所周知的模式吗?
我基本上是想找到更好的模型,而不是从基类派生所有东西,基类将跨越巨大的继承树和无处不在的虚拟函数。

还有另一种模式涉及行为继承

例如:一把剑(在一个幻想风格的游戏中)可以继承自
descripable
,以便能够向玩家描述它;它还将继承处理昏迷伤害的DoDamage;它进一步继承了
可抓取的
,因此玩家可以“抓取”它

反过来,一件盔甲可以从
伤害减免中继承,以降低给定的伤害,以及
可穿戴
,因此玩家可以佩戴它


您得到的不是一个大而深的继承树,而是许多小而浅的继承树。缺点当然是您必须创建大量的类,并考虑可能发生的不同行为和事件。

还有另一种模式涉及行为继承

例如:一把剑(在一个幻想风格的游戏中)可以继承自
descripable
,以便能够向玩家描述它;它还将继承处理昏迷伤害的DoDamage;它进一步继承了
可抓取的
,因此玩家可以“抓取”它

反过来,一件盔甲可以从
伤害减免中继承,以降低给定的伤害,以及
可穿戴
,因此玩家可以佩戴它


您得到的不是一个大而深的继承树,而是许多小而浅的继承树。缺点当然是您必须创建大量的类,并考虑可能发生的不同行为和事件。

让您的对象在将来的某个时间点请求更新

struct UpdateToken {
  UpdateToken() {}
  /* ... */
};
class Updater {
  // ...
  struct UpdateEntry
  {
    UpdateToken token;
    Time when;
    bool repeats;
    Time frequency
    std::function<void()> callback;
  };
  // Various indexes into a collection of UpdateEntries
  // sorted by when and by token so you can look up both ways quickly
public:
  UpdateToken RegisterUpdate( Time t, std::function<void()> callback ); // once after time t
  UpdateToken RegularUpdate( Time t, std::function<void()> callback ); // every time t

  void UnregisterUpdate( UpdateToken );
};
Updater* GetUpdater();

// use:
class Foo
{
  UpdateToken token;

  void DoUpdate()
  {
    std::cout << "I did it!\n";
  }

  void PlanRepeatingUpdate( Time t )
  {
    if (!GetUpdater())
      return;
    if (token.valid())
      GetUpdater()->UnregisterUpdate(token);
    token = GetUpdater()->RegularUpdate( t, [&]()->void
    {
      this->DoUpdate();
    });
  }
  ~Foo() { if (token.valid() && GetUpdater()) GetUpdater()->UnregisterUpdate(token); }
};
struct UpdateToken{
UpdateToken(){}
/* ... */
};
类更新程序{
// ...
结构更新尝试
{
UpdateToken令牌;
时间;
布尔重复;
时频
函数回调;
};
//将各种索引合并到UpdateEntry集合中
//按时间和令牌排序,以便您可以快速查找两种方式
公众:
UpdateToken RegisterUpdate(时间t,std::函数回调);//在时间t之后一次
UpdateToken RegularUpdate(时间t,std::函数回调);//每次t
作废注销更新(UpdateToken);
};
更新程序*GetUpdater();
//使用:
福班
{
UpdateToken令牌;
void DoUpdate()
{
std::cout注销更新(令牌);
token=GetUpdater()->RegularUpdate(t,[&]()->void
{
这->更新();
});
}
~Foo(){if(token.valid()&&GetUpdater())GetUpdater()->注销更新(token);}
};
在这里,我们有一个未来事件的源(Updater())和j随机类Foo,它可以在其中注册一个回调以进行一次重复或一系列重复

您可以在0.1秒、1秒或1小时内请求更新。如果您碰巧被销毁,或者不希望更新,您可以注销它,它永远不会到达

必须在更新程序中投入一些精力,以便它既可以快速找到要调用的下一个事件,也可以通过令牌找到更新。我见过一个Boost双索引容器,或者您可以手动处理它,方法是在第一个集合上设置一个主std::集合(或无序集合),以及一个具有唯一顺序的迭代器的辅助集合,您可以非常小心地管理它(您必须注意是什么使迭代器无效,并确保迭代器集不包含任何无效内容)


上面还有效地使用了一个global Updater()实例,这通常并不理想。

让您的对象在将来的某个时间点请求更新

struct UpdateToken {
  UpdateToken() {}
  /* ... */
};
class Updater {
  // ...
  struct UpdateEntry
  {
    UpdateToken token;
    Time when;
    bool repeats;
    Time frequency
    std::function<void()> callback;
  };
  // Various indexes into a collection of UpdateEntries
  // sorted by when and by token so you can look up both ways quickly
public:
  UpdateToken RegisterUpdate( Time t, std::function<void()> callback ); // once after time t
  UpdateToken RegularUpdate( Time t, std::function<void()> callback ); // every time t

  void UnregisterUpdate( UpdateToken );
};
Updater* GetUpdater();

// use:
class Foo
{
  UpdateToken token;

  void DoUpdate()
  {
    std::cout << "I did it!\n";
  }

  void PlanRepeatingUpdate( Time t )
  {
    if (!GetUpdater())
      return;
    if (token.valid())
      GetUpdater()->UnregisterUpdate(token);
    token = GetUpdater()->RegularUpdate( t, [&]()->void
    {
      this->DoUpdate();
    });
  }
  ~Foo() { if (token.valid() && GetUpdater()) GetUpdater()->UnregisterUpdate(token); }
};
struct UpdateToken{
UpdateToken(){}
/* ... */
};
类更新程序{
// ...
结构更新尝试
{
UpdateToken令牌;
时间;
布尔重复;
时频
函数回调;
};
//将各种索引合并到UpdateEntry集合中
//按时间和令牌排序,以便您可以快速查找两种方式
公众:
UpdateToken RegisterUpdate(时间t,std::函数回调);//在时间t之后一次
UpdateToken RegularUpdate(时间t,std::函数回调);//每次t
作废注销更新(UpdateToken);
};
更新程序*GetUpdater();
//使用:
福班
{
UpdateToken令牌;
void DoUpdate()
{
std::cout注销更新(令牌);
token=GetUpdater()->RegularUpdate(t,[&]()->void
{
这->更新();
});
}
~Foo(){if(token.valid()&&GetUpdater())GetUpdater()->注销更新(token);}
};
在这里,我们有一个未来事件的源(Updater())和j随机类Foo,它可以在其中注册一个回调以进行一次重复或一系列重复

您可以在0.1秒内、1秒内或1小时内请求更新。如果您碰巧被销毁,或者不希望更新,您可以注销它,但它永远不会到达

更新程序需要付出一些努力,这样它既可以快速找到下一个要调用的事件,也可以通过令牌找到更新。我看到了一个Boost双索引容器,或者您可以通过拥有一个主std::set(或无序集)手动处理它,以及具有唯一顺序的第一个迭代器集的第二个迭代器集,您需要非常小心地进行管理(您必须注意迭代器无效的原因,并确保迭代器集不包含任何无效内容)

上面还有效地使用了全局更新程序()实例