如何在C++ raylib游戏中实现高效的独立固定帧率更新循环?
固定间隔游戏更新循环的高效实现(Raylib + C++ Windows环境)
问题背景
我用C++结合Raylib开发游戏,运行环境为Windows x64,通过Eclipse搭配MinGW编译。
常规Raylib游戏循环示例如下:
int main() { InitWindow(800, 600, ("Window Title").c_str()); SetWindowState(FLAG_VSYNC_HINT); while (!WindowShouldClose()) { Update(); // 游戏更新逻辑 BeginDrawing(); ClearBackground((Color){0, 0, 0, 255}); Draw(); // 游戏绘制逻辑 EndDrawing(); } }
这种写法存在明显弊端:开启VSYNC后渲染帧率与显示器刷新率绑定,但更新逻辑和渲染逻辑在同一循环内,导致更新帧率也被限制在刷新率上。不同用户的显示器刷新率(60Hz、50Hz、120Hz等)会让游戏运行速度差异极大,不符合需求。
我希望更新逻辑始终以16ms间隔(约62.5FPS)独立运行,渲染逻辑保持VSYNC同步。尝试用多线程实现后,代码能正常运行,但更新线程CPU占用极高(4核系统占25%)。已知sleep和std::thread::sleep_for精度不足,且不想用基于deltaTime的插值方案,求高效的固定间隔独立更新循环实现方式。
我的多线程尝试代码:
std::thread ULT(scr_run_updateloop); // 启动更新线程 while (!WindowShouldClose()) { BeginDrawing(); ClearBackground((Color){0, 0, 0, 255}); Draw(); // 绘制逻辑 EndDrawing(); }
更新线程函数:
void scr_run_updateloop() { auto time_current = chrono::steady_clock::now(); auto time_last = time_current; uint64_t time_d; while (true) { time_current = chrono::steady_clock::now(); time_d = duration_cast<milliseconds>(time_current - time_last).count(); if (time_d >= 16) // 16ms间隔 { Update(); time_last = time_current; } } }
高效解决方案
方案1:Windows原生Waitable Timer API
Windows的Waitable Timer可以实现高精度定时触发,线程等待期间会进入休眠状态,几乎不占用CPU。
#include <windows.h> #include <chrono> #include <atomic> std::atomic<bool> shouldExit = false; void scr_run_updateloop() { HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL); if (!hTimer) return; // 设置定时参数:立即启动,周期16ms LARGE_INTEGER liDueTime; liDueTime.QuadPart = 0; SetWaitableTimer(hTimer, &liDueTime, 16, NULL, NULL, FALSE); while (!shouldExit) { // 等待定时器触发,线程休眠不占用CPU WaitForSingleObject(hTimer, INFINITE); Update(); // 执行更新逻辑 } CloseHandle(hTimer); } int main() { InitWindow(800, 600, "Window Title"); SetWindowState(FLAG_VSYNC_HINT); std::thread updateThread(scr_run_updateloop); while (!WindowShouldClose()) { BeginDrawing(); ClearBackground(BLACK); Draw(); EndDrawing(); } // 安全终止更新线程 shouldExit = true; // 手动触发定时器,让线程从等待状态退出 SetWaitableTimer(reinterpret_cast<HANDLE>(updateThread.native_handle()), NULL, 0, NULL, NULL, FALSE); updateThread.join(); CloseWindow(); return 0; }
说明:
- Waitable Timer精度可达毫秒级,完全满足16ms间隔需求
- 线程等待时处于休眠状态,CPU占用极低
- 通过
shouldExit原子变量实现线程安全退出,避免主线程退出后更新线程残留
方案2:std::this_thread::sleep_until 精准休眠
利用std::chrono计算下一次更新的时间点,用sleep_until休眠到该时间点,相比sleep_for能自动修正误差,精度更高。
#include <chrono> #include <thread> #include <atomic> std::atomic<bool> shouldExit = false; void scr_run_updateloop() { using namespace std::chrono; auto nextUpdateTime = steady_clock::now(); const auto updateInterval = milliseconds(16); while (!shouldExit) { nextUpdateTime += updateInterval; // 休眠到下一次更新时间点 std::this_thread::sleep_until(nextUpdateTime); Update(); } } int main() { InitWindow(800, 600, "Window Title"); SetWindowState(FLAG_VSYNC_HINT); std::thread updateThread(scr_run_updateloop); while (!WindowShouldClose()) { BeginDrawing(); ClearBackground(BLACK); Draw(); EndDrawing(); } shouldExit = true; updateThread.join(); CloseWindow(); return 0; }
说明:
sleep_until直接休眠到目标时间点,能累积修正每次休眠的微小误差,保证长期间隔稳定- Windows下该方法精度一般在1ms左右,足够满足需求
- 同样通过原子变量实现线程安全退出
关键注意事项
- 线程同步:更新线程和渲染线程共享的游戏数据(如实体位置、游戏状态)必须用
std::mutex或原子变量保护,避免数据竞争导致异常。 - Raylib API线程安全:Raylib的渲染相关API(如
Draw*系列)只能在主线程调用;输入检测API(如IsKeyPressed)建议放在主线程,将输入状态传递给更新线程,避免跨线程调用问题。
内容的提问来源于stack exchange,提问作者DrZ214
相关产品推荐
相关产品推荐

