7

我正在编写一个当前在 Windows 和 Mac OS X 上运行的游戏。我的主游戏循环如下所示:

while(running)
{
    ProcessOSMessages(); // Using Peek/Translate message in Win32
                         // and nextEventMatchingMask in Cocoa
    GameUpdate();
    GameRender();
}

这显然简化了一点,但这就是它的要点。在我可以完全控制应用程序的 Windows 中,它运行良好。不幸的是,Apple 在 Cocoa 应用程序中拥有自己的做事方式。

当我第一次尝试在 Cocoa 中实现我的主循环时,我不知道把它放在哪里,所以我NSApplication根据这篇文章创建了自己的循环。我把我的GameFrame()权利投入到我的run工作中,一切正常。

但是,我不觉得它是“正确”的做法。我想在 Apple 的生态系统中很好地发挥作用,而不是试图破解一个可行的解决方案。

苹果的这篇文章NSTimer描述了使用 . 的旧方法和使用CVDisplayLink. 我已经连接了CVDisplayLink版本,但它只是感觉....奇怪。我不喜欢我的游戏由显示器驱动而不是相反的想法。

我只有两个选择来使用CVDisplayLink或覆盖我自己的NSApplication吗?这些解决方案中的任何一个都感觉不太对。

4

3 回答 3

2

我很想知道是否有真正做过这件事的人愿意参与进来,但这是我的理解:

Apple 将CVDisplayLink解决方案推向在使用的主线程上执行循环,-nextEventMatchingMask:untilDate:inMode:dequeue:因为我认为它为 UI 控件提供了更好的响应能力。这可能与全屏游戏无关。NSApplication(注意:使用这种形式的游戏循环不需要替换。)我认为使用的主要潜在问题CVDisplayLink是它只会提前运行一帧并且它会提前确定,这甚至比垂直更强大同步。从好的方面来说,它可能会改善延迟。

其他解决方案包括将渲染与游戏逻辑分离,并在主线程上定期运行游戏逻辑并在线程上渲染CVDisplayLink。但是,如果您遇到游戏驱动显示范式的问题,我可能只会推荐这个。

于 2012-03-25T23:00:46.577 回答
1

您不必创建自己的基于 NSApplication 的类或使用 CVDisplayLink 来解决应用程序的运行循环在 Cocoa 中对您隐藏的事实。

您可以只创建一个线程并将运行循环放在那里。

不过,对于它的价值,我只使用 CVDisplayLink。

于 2012-03-26T10:16:59.823 回答
0

我在这里贴一些东西来恢复这个问题......主要是出于便携性。我通过研究 OLC Pixel Game Engine 发现,它与 do{}while 循环和 std::chrono 一起使用来检查帧的时间以计算 fElapsed Time。下面是我写的一些代码来做同样的事情。它还添加了一个化妆部分,以控制高于某个值的拍摄帧率,在本例中为 60 FPS。

c++代码

int maxSpeedMicros = 16700;
float fTimingBelt; //used to calculate fElapsedTime for internal calls.
std::chrono::steady_clock::time_point timingBelt[2];
bool engineRunning = false; //always have it true, until the engine stops.
bool isPaused = false;

do {
    timingBelt[1] = std::chrono::steady_clock::now();
    fTimingBelt = std::chrono::duration_cast<std::chrono::microseconds>(timingBelt[1] - timingBelt[0]).count() * 0.000001;
    if (isPaused) {
        do {
            std::this_thread::sleep_for (std::chrono::milliseconds(100));
            timingBelt[1] = std::chrono::steady_clock::now();
        } while (isPaused);
    }
    timingBelt[0] = std::chrono::steady_clock::now();
    // do updating stuff here.
    
    timingBelt[1] = std::chrono::steady_clock::now();
    int frameMakeup = std::chrono::duration_cast<std::chrono::microseconds>(timingBelt[1] - timingBelt[0]).count();
    if (frameMakeup < maxSpeedMicros) {
        int micros = maxSpeedMicros - frameMakeup;
        std::this_thread::sleep_for (std::chrono::microseconds(micros));
    }
} while (engineRunning);

但是,该代码与 Cocoa 的事​​件驱动模型直接冲突。 可可中的自定义主应用程序循环 因此,作为一个创可贴,我注释掉了整个循环,并创建了一个运行循环一次迭代的新方法。然后我在我的 AppDelegate 中实现了这个:

目标 C 代码

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
    engine->resetTimer();
    
    [NSTimer scheduledTimerWithTimeInterval:0.016666666667 target:self selector:@selector(engineLoop) userInfo:nil repeats:YES];
}
-(void) engineLoop { //Let's handle this by the engine object.  That's too complicated!
    engine->updateState();
    [glView update]; //Since the engine is doing all of its drawing to a GLView
    [[glView openGLContext] flushBuffer];
}

仍然要做的是调整计时器对象的容差。Apple Developer 文档指出,如果计时器对象错过了下一个窗口,它将等待下一个帧时间。但是,容差允许它改变未来事件的时间,以实现更平滑的帧速率转换和更好地利用 CPU 能力。

所以在这一点上,我愿意接受其他人为制作更便携的代码所做的建议和意见。我计划在名为“eventDriven”的引擎的构造函数中使用一个布尔参数,如果为 false,将启动自己的游戏循环线程,然后拆分顶部事件循环以调用处理所有代码的“engineUpdate”方法可以是事件驱动的。然后,在构建事件驱动系统的情况下,代理可以只使用 engineUpdate = TRUE 构建引擎,并让他们的事件驱动游戏更新。

有人做过吗?如果是这样,它如何执行跨平台?

于 2021-02-25T16:14:40.937 回答