我正在做一个Sdl游戏,这是2D射手。 我使用SDL导入曲面和OpenGL以在屏幕上绘制它们(这样做是因为它的工作方式比SDL快)。 我有两个线程运行,一个用于处理东西和渲染,另一个用于input。 基本上,处理一个占用我的CPU的1-2%,而input回路占25%(四核,所以它是1个完整的核心)。 我尝试之前每次while (SDL_PollEvent(&keyevent))
做SDL_Delay(1 while (SDL_PollEvent(&keyevent))
,它的工作原理! 整个过程将CPU负载降低到3%。 但是,有一个令人讨厌的副作用。 整个程序input都是残疾的:它并没有检测到所有的键被按下,例如,为了使angular色移动,它有时需要长达3秒钟的抨击键盘反应。
我也尝试过使用SDL_PeepEvent()
和SDL_WaitEvent()
来解决这个问题,但是,它导致了相同的(非常长的)延迟。
事件循环代码:
void GameWorld::Movement() { SDL_Event keyevent; bool x1, x2, y1, y2, z1, z2, z3, m; // Booleans to determine the x1 = x2 = y1 = y2 = z1 = z2 = z3 = m = 0; // movement direction SDL_EnableKeyRepeat(0, 0); while (1) { while (SDL_PollEvent(&keyevent)) { switch(keyevent.type) { case SDL_KEYDOWN: switch(keyevent.key.keysym.sym) { case SDLK_LEFT: x1 = 1; x2 = 0; break; case SDLK_RIGHT: x1 = 0; x2 = 1; break; case SDLK_UP: y1 = 1; y2 = 0; break; case SDLK_DOWN: y1 = 0; y2 = 1; break; default: break; } break; case SDL_KEYUP: switch(keyevent.key.keysym.sym) { case SDLK_LEFT: x1 = x2 = 0; break; case SDLK_RIGHT: x1 = x2 = 0; break; case SDLK_UP: y1 = y2 = 0; break; case SDLK_DOWN: y1 = y2 = 0; break; default: break; } break; case SDL_QUIT: PrintToFile("The game was closed manually.\n"); CleanUp(); return; break; default: break; } } m = x1 || x2 || y1 || y2; if (m) // if any button is pushed down, calculate the movement { // direction and assign it to the player z1 = (x1 || x2) && (y1 || y2); z2 = !x1 && (x2 || y2); z3 = (!y1 && x1) || (!y2 && x2); MainSurvivor->SetMovementDirection(4 * z1 + 2 * z2 + z3); } else // if no button is pushed down, reset the direction MainSurvivor->SetMovementDirection(-1); } }
计算/渲染循环代码:
void GameWorld::GenerateCycles() { int Iterator = 0; time_t start; SDL_Event event; Render(); _beginthread(MovementThread, 0, this); while (1) { // I know I check this in input loop, but if I comment SDL_PollEvent(&event); // out it from here, that loop cannot if (event.type == SDL_QUIT) // see any of the events (???)! { PrintToFile("The game was closed manually.\n"); CleanUp(); } // It never closes through here though start = clock(); Iterator++; if (Iterator >= 232792560) Iterator %= 232792560; MainSurvivor->MyTurn(Iterator); for (unsigned int i = 0; i < Survivors.size(); i++) { Survivors[i]->MyTurn(Iterator); if (Survivors[i]->GetDiedAt() != 0 && Survivors[i]->GetDiedAt() + 25 < clock()) { delete Survivors[i]; Survivors.erase(Survivors.begin() + 5); } } if (Survivors.size() == 0) SpawnSurvivors(); for (int i = 0; i < int(Zombies.size()); i++) { Zombies[i]->MyTurn(Iterator); if (Zombies[i]->GetType() == 3 && Zombies[i]->GetDiedAt() + 25 < Iterator) { delete Zombies[i]; Zombies.erase(Zombies.begin() + i); i--; } } if (Zombies.size() < 3) SpawnZombies(); // No need to render every cycle, gameplay is slow if (Iterator % 2 == 0) Render(); if (Interval - clock() + start > 0) SDL_Delay(Interval - clock() + int(start)); } }
有没有人有任何想法?
我对SDL或游戏编程并不是很有经验,但是这里有一些随意的想法:
你的代码:
while (1) { while (SDL_PollEvent(&keyevent)) { switch(keyevent.type) { // code to set keyboard state } } // code to calculate movement according to keyboard state // then act on that movement }
这意味着无论您的键盘上没有任何事情发生,您正在计算和设置数据。
如果设置数据是昂贵的(提示:同步数据),那么它将花费你更多。
你必须等待一个事件发生,而不是你写的,导致100%使用一个处理器的旋转。
以下是我在家中为测试所写的事件循环的变体:
while(true) { // message processing loop ::SDL_Event event ; ::SDL_WaitEvent(&event) ; // THIS IS WHAT IS MISSING IN YOUR CODE do { switch (event.type) { // etc. } } while(::SDL_PollEvent(&event)) ; // re-draw the internal buffer if(this->m_isRedrawingRequired || this->m_isRedrawingForcedRequired) { // redrawing code } this->m_isRedrawingRequired = false ; this->m_isRedrawingForcedRequired = false ; }
注意:这是单线程的。 我稍后会谈论线程。
注2:关于这两个“m_isRedrawing …”布尔值的观点是当这些布尔值之一为真时,以及定时器询问问题时强制重画。 通常,没有重绘。
我的代码和你的代码之间的区别是,你从来没有让线程“等待”。
我想,在处理键盘事件时有一个问题。
你的代码:
case SDL_KEYDOWN: switch(keyevent.key.keysym.sym) { case SDLK_LEFT: x1 = 1; x2 = 0; break; case SDLK_RIGHT: x1 = 0; x2 = 1; break; // etc. } case SDL_KEYUP: switch(keyevent.key.keysym.sym) { case SDLK_LEFT: x1 = x2 = 0; break; case SDLK_RIGHT: x1 = x2 = 0; break; // etc. }
比方说,你先按左键,然后按右键,然后按下左键。 我所期望的是:
在你的情况下,你有:
你做错了,因为:
稍后我会找到链接,但是你应该做的是有一个按键的布尔状态数组。 就像是:
// C++ specialized vector<bool> is silly, but... std::vector<bool> m_aKeyIsPressed ;
你用可用键的大小对它进行初始化:
m_aKeyIsPressed(SDLK_LAST, false)
然后,在关键事件:
void MyContext::onKeyUp(const SDL_KeyboardEvent & p_oEvent) { this->m_aKeyIsPressed[p_oEvent.keysym.sym] = false ; }
并在关键时刻:
void MyContext::onKeyDown(const SDL_KeyboardEvent & p_oEvent) { this->m_aKeyIsPressed[p_oEvent.keysym.sym] = true ; }
这样,当您定期检查(以及检查部件的重要性)时,您就知道键盘的确切瞬时状态,您可以对其作出反应。
线程很酷,但是,你必须确切地知道你在处理什么。
例如,事件循环线程调用以下方法:
MainSurvivor->SetMovementDirection
分辨率(呈现)线程调用以下方法:
MainSurvivor->MyTurn(Iterator);
真的,你在两个不同的线程之间共享数据吗?
如果你是(我知道你是),那么你有:
我想要做的是将一个线程的更改传递给另一个线程(例如,通过消息传递给同步队列)。
无论如何,线程是一个很重要的问题,所以在将它与SDL和OpenGL混合之前,您应该熟悉这个概念。 Herb Sutter的博客是一个非常棒的线索文章。
你应该做的是:
你显然使用C ++(例如void GameWorld::Movement()
),所以使用1或0代替true
或false
不会使你的代码更清晰或更快。
如果您在GameWorld::GenerateCycles()
的线程上初始化了SDL,并且MovementThread
正在调用GameWorld::Movement()
那么您有一个问题 :
- 不要从单独的线程调用SDL视频/事件功能
你有尝试过使用像usleep(50000)
而不是delay(1)
?
这将使您的线程在轮询队列之间休眠50 msecs,或等价地,您将每秒检查队列20次。
另外,这是什么平台:Linux,Windows?
在Windows上你可能没有usleep()
,但你可以尝试select()
,如下所示:
struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 50000; select(0, NULL, NULL, NULL, &tv);
另一个建议是尝试轮询,直到它停止返回事件。 一旦没有事件挂起,在轮询之间继续睡眠50毫秒,直到它再次开始返回事件。
我建议看看SDL_EventFilter和相关的功能。 这不是一个轮询队列的输入方法,所以它不需要拖延,但是如果我没有记错的话,它不会发生在主线程上,这可能正是你需要的性能,但可能会使代码复杂化。