【问题标题】:Function pointers and their called parameters函数指针及其调用参数
【发布时间】:2011-04-20 08:53:05
【问题描述】:

这可能是错误的方法,但是当我深入研究开发我的游戏引擎时,我遇到了时间问题。

假设我有一组语句,例如:

for(int i = 0; i < 400; i++)
{
     engine->get2DObject("bullet")->Move(1, 0);
}

子弹会移动,但是在屏幕上不会有任何动画。它基本上会从屏幕的一个部分“扭曲”到下一个部分。

所以,我在想...为每个基础对象(用户继承自)创建一个函数指针向量,当他们调用“移动”时,我实际上不会移动对象,直到游戏的下一次迭代环形。 所以像:

while(game.running())
{
     game.go();
}

go()
{
     for(...)
          2dobjs.at(i)->action.pop_back();
}

或者类似的东西,这样在每次迭代期间它只运行每个对象的一个​​动作(当然我会添加一个检查,看看是否真的有“动作”要运行)。

如果这是个好主意,我的问题是如何存储参数。由于每个对象可以做不止一种类型的“动作”而不是移动(rotateby 就是一个例子),我认为创建一个类似于以下方式的结构是荒谬的:

struct MoveAction {
     MovePTR...;
     int para1;
     int para2;
};

想法?或者这是完全错误的方向?

【问题讨论】:

  • 你是说如果你在一帧中移动你的“子弹”400次,引擎不会绘制中间步骤?我不确定我是否理解您要解决的问题。
  • 认为我明白你在问什么,但我不确定。您希望子弹在屏幕上移动得足够慢以便被看到。你有一个主游戏循环,其中go() 每秒执行 10 次。我在正确的轨道上吗?
  • @Seth 对,因为循环是在下一个循环开始之前执行的,所以引擎不会绘制发生了什么。就本例中的引擎而言,坐标发生了变化,而不是物体实际移动了。 @Beta是的,我希望在执行循环中每次执行一次“移动”功能,这样它就会产生“移动”而不是包装的效果。所以,你说循环将执行是正确的,例如每秒 10 次,但由于“移动循环”在下一个游戏循环之前执行,玩家看不到它“移动”。
  • 要真正迂腐,我认为您的意思是“warp”而不是“wrap”。
  • @JoshD 你知道我的意思 :)...但我还是修正了错字。

标签: c++ design-patterns oop


【解决方案1】:

想法?或者这是完全错误的方向?

方向不对。

实际上,能够对操作或订单进行排队的想法有一些优点。但通常这适用于人工智能和更一般的任务(如“前往位置 X”、“攻击你看到的任何东西”等)。因此,它通常不会涵盖您在此处处理的逐帧移动问题。

对于像子弹这样非常简单的实体,排队任务是相当大的。毕竟,子弹除了每次都向前移动,还能做什么?实现这种简单的动作也是一种尴尬的方式。它提出了一些问题,例如为什么只前进了 400 步?如果前面的区域更长怎么办?如果它更短怎么办?如果仅说 50 步后有其他物体挡住了路怎么办? (那么 350 个排队的移动动作就浪费了。)

这个想法的另一个问题(至少以您提出的简单方式)是您的子弹在游戏循环的每次迭代中都会移动一个固定的量。但并非所有迭代都将花费相同的时间。有些人的计算机显然能够比其他人更快地运行代码。甚至忽略这一点,有时游戏循环可能比其他时间做更多的工作(例如当更多实体在游戏中处于活动状态时)。因此,您确实需要一种方法来考虑每次迭代调整运动所花费的时间,以便无论游戏循环运行多快,所有东西都似乎以一致的速度移动给用户。

因此,每个移动动作不是“移动 X 量”,而是“以 X 速度移动”,然后乘以自上次迭代以来经过的时间。然后你真的永远不会知道有多少移动动作要排队,因为这取决于你的游戏循环在那种情况下运行的速度。但是,这再次表明,排队移动动作是逐帧移动的尴尬解决方案。

大多数游戏引擎的做法是简单地在每个对象上调用某种函数,每帧评估其运动。也许像void ApplyMovement(float timeElapsedSinceLastFrame); 这样的函数。在子弹的情况下,它将子弹每秒应移动的速度乘以自上一帧以来经过的时间,以确定子弹应在这一帧移动的量。对于更复杂的对象,您可能希望使用旋转、加速、减速、目标搜索等进行数学运算。但总体思路是相同的:在游戏循环的每次迭代中调用一个函数来评估该迭代应该发生的情况。

【讨论】:

  • 我认为两个 Lee Reeves 基本上都说了同样的话(使用速度与一系列“动作”),但他的消化要小得多(因此更容易理解 imo)所以我接受了他的但我还是提高了你的水平。
【解决方案2】:

我认为这不是正确的方向。在函数指针向量中存储 400 次移动后,您仍然需要弹出并执行移动、重绘屏幕并重复。移动()、重绘和重复不是更容易吗?

我会说你的子弹变形是因为它每帧移动 400 像素,而不是因为你需要延迟移动计算。

但如果这是适合您架构的正确解决方案,那么在 C++ 中您应该使用类而不是函数指针。例如:

class Action // an abstract interface for Actions
{
public:
    virtual void execute() = 0; // pure virtual function
}

class MoveAction: public Action
{
public:
    MoveAction(Point vel) : velocity(vel) {}
    virtual void execute();
    Point velocity;
    ...
}

std::vector<Action*> todo;

gameloop
{
    ...
    todo.push_back(new MoveAction(Point(1,0))
    ...
}

【讨论】:

    【解决方案3】:

    那么400 来自哪里?为什么不这样做:

    go()
    {
        engine->get2DObject("bullet")->Move(1, 0);
    }
    

    【讨论】:

    • 400 来自想要将子弹移动 400 像素后的程序员,例如,用户按下空格键来“发射”子弹。即在屏幕上“发射”它。
    【解决方案4】:

    我认为获得您想要的东西的一般方法可能会有所不同。我个人更喜欢这样的设置,其中每个 go 循环调用函数来设置包括子弹在内的所有对象的位置,然后绘制场景。所以每个循环它都会把子弹放在它应该在的地方。

    如果你决定走你的路,你可能不得不做你的结构想法。这并不令人愉快,但在我们关闭之前,您必须这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-15
      • 1970-01-01
      • 2018-04-06
      • 2016-10-24
      • 2020-04-14
      相关资源
      最近更新 更多