我正在制作 Sdl 游戏,它是 2d 射击游戏。我使用 SDL 导入表面,使用 OpenGL 在屏幕上绘制它们(这样做是因为它比 SDL 工作得更快)。我有两个线程在运行,一个用于处理内容和渲染,另一个用于输入。基本上,处理一个占用我 CPU 的 1-2%,而输入循环占用 25%(在四核上,所以它是 1 个全核)。我尝试在每个 while (SDL_PollEvent(&keyevent))
之前执行 SDL_Delay(1) 并且它有效!将整个过程的 CPU 负载降低到 3%。然而,有一个令人讨厌的副作用。整个程序的输入是有缺陷的:它没有检测到所有按下的键,例如,为了让角色移动,有时需要长达 3 秒的敲击键盘才能使用react。
我也尝试过使用 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 或游戏编程并没有真正的经验,但这里有一些随机的想法:
对状态变化使用react
您的代码:
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
}
这意味着无论您的键盘上没有发生任何事情,您都在计算和设置数据。
如果设置数据很昂贵(提示:同步数据),那么它会花费你更多。
SDL_WaitEvent : 测量状态
您必须等待事件发生,而不是您编写的导致 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...” bool 值的要点是当这些 bool 值之一为真时强制重绘,并且当计时器提出问题时。通常,没有重绘。
我的代码和你的代码之间的区别在于,你绝不会让线程“等待”。
键盘事件
我想,您对键盘事件的处理存在问题。
您的代码:
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.
}
假设您按 LEFT,然后按 RIGHT,然后松开 LEFT。我期望的是:
在你的情况下,你有:
你做错了,因为:
我稍后会找到链接,但您应该做的是为按下的键设置一组 bool 状态。就像是:
// C++ specialized vector<bool> is silly, but...
std::vector<bool> m_aKeyIsPressed ;
您使用可用 key 的大小对其进行初始化:
m_aKeyIsPressed(SDLK_LAST, false)
然后,在 key up 事件上:
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 ;
}
这样,当您定期检查时(以及检查零件的时间很重要),您就可以知道键盘的确切瞬时状态,并且可以对其使用react。
线程
线程很酷,但是,您必须确切地知道您在处理什么。
例如,事件循环线程调用以下方法:
MainSurvivor->SetMovementDirection
解析(渲染)线程调用以下方法:
MainSurvivor->MyTurn(Iterator);
说真的,您是否在两个不同的线程之间共享数据?
如果你是(我知道你是),那么你有:
相反,我会做的是将更改从一个线程传递到另一个线程(例如,通过向同步队列发送消息)。
无论如何,线程是一个沉重的问题,因此在将其与 SDL 和 OpenGL 混合之前,您应该熟悉这个概念。 Herb Sutter's blog 是关于线程的精彩文章集合。
你应该做的是:
PS:你的 bool 值有什么问题?
您显然在使用 C++(例如
void GameWorld::Movement()
),因此使用 1 或 0 而不是 true
或 false
不会使您的代码更清晰或更快。